Linux 内核 KASAN 内存检查工具

开启了 kasan 功能以后,每次分配内存,会有额外的 shadow memory,大小为 1/8,用来标记内存分配的信息。每连续 8 个字节,如果都可以访问,shadow byte 标记为 0;如果 1、2、……、7 个字节可以访问,shadow byte 标记为 1、2、……、7;否则标记为一些预定义的标记值,这些值都小于 0,且代表了具体的内存分配状态:

#define KASAN_FREE_PAGE         0xFF  /* page was freed */
#define KASAN_PAGE_REDZONE      0xFE  /* redzone for kmalloc_large allocations */
#define KASAN_KMALLOC_REDZONE   0xFC  /* redzone inside slub object */
#define KASAN_KMALLOC_FREE      0xFB  /* object was freed (kmem_cache_free/kfree) */
#define KASAN_KMALLOC_FREETRACK 0xFA  /* object was freed and has free track set */

在每次访存的代码里,编译器会插入特别指令 __asan_loadXX 或者 __asan_storeXX,这些指令在经过一些封装后,会调用到 check_region_inline() 函数:

static __always_inline bool check_region_inline(unsigned long addr,
                                                size_t size, bool write,
                                                unsigned long ret_ip)
{
        if (!kasan_arch_is_ready())
                return true;

        if (unlikely(size == 0))
                return true;

        if (unlikely(addr + size < addr))
                return !kasan_report(addr, size, write, ret_ip);

        if (unlikely((void *)addr <
                kasan_shadow_to_mem((void *)KASAN_SHADOW_START))) {
                return !kasan_report(addr, size, write, ret_ip);
        }

        if (likely(!memory_is_poisoned(addr, size)))
                return true;

        return !kasan_report(addr, size, write, ret_ip);
}

这个函数里,会依次进行检查:

  1. 检查 KASAN 是否启用;
  2. 检查访存大小是否为 0,这是不合理的;
  3. 检查访存区域的结尾处是否在起始处前面,这也是不合理的;
  4. 检查访存区域与 shadow byte 里的标记是否有冲突,比如 "out-of-bounds"、"use-after-free",等等;
  5. 如果有不合理的访存发生,会通过 kasan_report() 输出访存错误的类型、当时的调用栈、以及这片内存分配时的调用栈等等信息;

实现代码在 mm/kasan/generic.c 以及其他一些文件里。

参考资料

Read More: