编辑
2025-10-04
记录知识
0

目录

kmem_cache 布局
kmalloc poison
kfree poison
总结
参考文档

在上一个文章中详细介绍了kasan影子内存的布局情况,本文根据最重要的一个常见就是slab下的kasan的实现原理进行解析。

kmem_cache 布局

对于开了kasan的slab的布局,结构体发生了一点变化,如下

struct kmem_cache { ...... #ifdef CONFIG_KASAN struct kasan_cache kasan_info; #endif ...... }; struct kasan_cache { int alloc_meta_offset; int free_meta_offset; bool is_kmalloc; };

上面的结构体成员是留给大家调试slab时的调试信息,下面分析一下slab创建时的代码调用。

kmem_cache_create kmem_cache_create_usercopy create_cache __kmem_cache_create kmem_cache_open calculate_sizes kasan_cache_create __kasan_cache_create

我们重点关注的是kmalloc的slab创建,所以是从开机时创建,其代码调用如下

start_kernel mm_init kmem_cache_init create_kmalloc_caches create_boot_cache __kmem_cache_create kmem_cache_open calculate_sizes kasan_cache_create __kasan_cache_create

我们关注重点函数__kasan_cache_create,其源码如下

void __kasan_cache_create(struct kmem_cache *cache, unsigned int *size, slab_flags_t *flags) { unsigned int ok_size; unsigned int optimal_size; *flags |= SLAB_KASAN; if (!kasan_stack_collection_enabled()) return; ok_size = *size; /* Add alloc meta into redzone. */ cache->kasan_info.alloc_meta_offset = *size; *size += sizeof(struct kasan_alloc_meta); if (*size > KMALLOC_MAX_SIZE) { cache->kasan_info.alloc_meta_offset = 0; *size = ok_size; /* Continue, since free meta might still fit. */ } optimal_size = cache->object_size + optimal_redzone(cache->object_size); /* Limit it with KMALLOC_MAX_SIZE (relevant for SLAB only). */ if (optimal_size > KMALLOC_MAX_SIZE) optimal_size = KMALLOC_MAX_SIZE; /* Use optimal size if the size with added metas is not large enough. */ if (*size < optimal_size) *size = optimal_size; }

可以看到,这里主要修改了三个地方

  • flags
  • kasan_info
  • size

为了验证,可以在crash中确认一下,如下

crash> kmem -S kmalloc-128 CACHE OBJSIZE ALLOCATED TOTAL SLABS SSIZE NAME ffffff8007003c80 128 140707 141056 4408 8k kmalloc-128 crash> struct kmem_cache.name ffffff8007003c80 name = 0xffffffd00c59e708 "kmalloc-128" crash> struct kmem_cache ffffff8007003c80 struct kmem_cache { cpu_slab = 0xffffffd00ca37070, flags = 1207959552, min_partial = 5, size = 256, object_size = 128, reciprocal_size = { m = 1, sh1 = 1 '\001', sh2 = 7 '\a' }, offset = 64, cpu_partial = 13, oo = { x = 65568 }, max = { x = 65568 }, min = { x = 16 }, allocflags = 262144, refcount = 1, ctor = 0x0, inuse = 128, align = 128, red_left_pad = 0, name = 0xffffffd00c59e708 "kmalloc-128", list = { next = 0xffffff8007003e68, prev = 0xffffff8007003b68 }, kobj = { name = 0xffffffd00c59e708 "kmalloc-128", entry = { next = 0xffffff8007003e80, prev = 0xffffff8007003b80 }, parent = 0xffffff800a200228, kset = 0xffffff800a200200, ktype = 0xffffffd00ddee9a8 <slab_ktype>, sd = 0xffffff800a2799c0, kref = { refcount = { refs = { counter = 1 } } }, state_initialized = 1, state_in_sysfs = 1, state_add_uevent_sent = 0, state_remove_uevent_sent = 0, uevent_suppress = 0 }, kasan_info = { alloc_meta_offset = 128, free_meta_offset = 0, is_kmalloc = true }, useroffset = 0, usersize = 128, node = {0xffffff8007000f80} }

取出我们关心的数据,如下解释

  • size: 256 cache->object_size + optimal_redzone(cache->object_size);所得
  • alloc_meta_offset: cache->kasan_info.alloc_meta_offset = *size;所得
  • is_kmalloc: cache->kasan_info.is_kmalloc = true;所得
  • flags: *flags |= SLAB_KASAN;所得,这里1207959552是48000000,SLAB_KASAN是8000000

kmalloc poison

为了了解kmalloc的下毒步骤,首先可以分析一下kmalloc的代码流程,如下

kmalloc __kmalloc kasan_kmalloc ____kasan_kmalloc slab_alloc slab_alloc_node __slab_alloc ___slab_alloc new_slab_objects new_slab allocate_slab kasan_poison_slab

如果需要新增一个slab,则其调用流程如下

这里____kasan_kmalloc的主要作用就是为影子区域的内存下毒,同样allocate_slab调用了kasan_poison_slab用来下毒,kasan_poison_slab比较简单,我们看____kasan_kmalloc的实现即可

static inline void *____kasan_kmalloc(struct kmem_cache *cache, const void *object, size_t size, gfp_t flags) { unsigned long redzone_start; unsigned long redzone_end; if (gfpflags_allow_blocking(flags)) kasan_quarantine_reduce(); if (unlikely(object == NULL)) return NULL; if (is_kfence_address(kasan_reset_tag(object))) return (void *)object; /* * The object has already been unpoisoned by kasan_slab_alloc() for * kmalloc() or by kasan_krealloc() for krealloc(). */ /* * The redzone has byte-level precision for the generic mode. * Partially poison the last object granule to cover the unaligned * part of the redzone. */ if (IS_ENABLED(CONFIG_KASAN_GENERIC)) kasan_poison_last_granule((void *)object, size); /* Poison the aligned part of the redzone. */ redzone_start = round_up((unsigned long)(object + size), KASAN_GRANULE_SIZE); redzone_end = round_up((unsigned long)(object + cache->object_size), KASAN_GRANULE_SIZE); kasan_poison((void *)redzone_start, redzone_end - redzone_start, KASAN_KMALLOC_REDZONE, false); /* * Save alloc info (if possible) for kmalloc() allocations. * This also rewrites the alloc info when called from kasan_krealloc(). */ if (kasan_stack_collection_enabled()) set_alloc_info(cache, (void *)object, flags, true); /* Keep the tag that was set by kasan_slab_alloc(). */ return (void *)object; }

此函数重点关注两点

  1. kasan_poison_last_granule: 给不足8个字节的影子区域下毒k
  2. kasan_poison: 给其他区域下毒红区0xfc

根据自己测试代码《KASAN(2)-自测试模块》的日志,我们关注kmalloc_oob_right函数下的影子区域的打印,如下

[ 10.441646] Memory state around the buggy address: [ 10.441684] ffffff8011758000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 10.441723] ffffff8011758080: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [ 10.441760] >ffffff8011758100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 [ 10.441792] ^ [ 10.441829] ffffff8011758180: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [ 10.441866] ffffff8011758200: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc

可以看到这里0xfc和0x3是由上面代码写入的下毒值。

所以总结就是,每一个slab cache的申请,都默认下毒过了,然后每个分配的object也都已经提前解毒了,然后每次kmalloc的时候,会重新根据实际情况下毒。这样就能够有效的检查到oob的问题,我们看到地址ffffff8011758080上的fc的值,就是申请slab的时候帮我们下好的毒,kmalloc只是在内存真正申请时重新下毒。

kfree poison

对于kfree的页面,主要可以检测uaf的问题,暂时只基于内核free的一条路径进行代码分析,如下

kfree slab_free slab_free_freelist_hook slab_free_hook kasan_slab_free __kasan_slab_free ____kasan_slab_free

____kasan_slab_free的代码如下

static inline bool ____kasan_slab_free(struct kmem_cache *cache, void *object, unsigned long ip, bool quarantine, bool init) { u8 tag; void *tagged_object; tag = get_tag(object); tagged_object = object; object = kasan_reset_tag(object); if (is_kfence_address(object)) return false; if (unlikely(nearest_obj(cache, virt_to_head_page(object), object) != object)) { kasan_report_invalid_free(tagged_object, ip); return true; } /* RCU slabs could be legally used after free within the RCU period */ if (unlikely(cache->flags & SLAB_TYPESAFE_BY_RCU)) return false; if (!kasan_byte_accessible(tagged_object)) { kasan_report_invalid_free(tagged_object, ip); return true; } kasan_poison(object, round_up(cache->object_size, KASAN_GRANULE_SIZE), KASAN_KMALLOC_FREE, init); if ((IS_ENABLED(CONFIG_KASAN_GENERIC) && !quarantine)) return false; if (kasan_stack_collection_enabled()) kasan_set_free_info(cache, object, tag); return kasan_quarantine_put(cache, object); }

主要逻辑如下

  1. 忽略kfence
  2. 不在一个object中的无效free (KASAN(3)-测试分析 有例子)
  3. rcu宽限期内 (KASAN(3)-测试分析 有例子)
  4. 不在内核范围内的指针
  5. 给object下毒KASAN_KMALLOC_FREE
  6. 如果支持栈回溯,下毒KASAN_KMALLOC_FREETRACK
  7. 创建了一个quarantine list,延迟调用___cache_free释放内存

至此,我们可以看到影子区域的值如下

[ 10.444125] Memory state around the buggy address: [ 10.444162] ffffff8011758080: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [ 10.444199] ffffff8011758100: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 10.444237] ffffff8011758180: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [ 10.444269] ^ [ 10.444306] ffffff8011758200: 00 07 fc fc fc fc fc fc fc fc fc fc fc fc fc fc [ 10.444343] ffffff8011758280: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc

看到ffffff8011758100的位置上,设置了fa和fb,正是这段代码对free区域的下毒值

总结

本文通过阅读代码的方式,解析了kmalloc和kfree两个函数上kasan如何进行下毒的,可以了解到,在slab分配器中,kasan对需要的object都下毒了,这样使得当程序进行load/store的时候,正常触发指令插桩,从而正确的判断当前代码是否出现oob和uaf的错误。关于指令插桩可以查看《KASAN(4)-INLINE插桩分析》和《KASAN(5)-OUTLINE插桩分析》

参考文档

本文详细参考了如下文档的内容,如需要理解原文意思,可以查看如下链接

http://www.wowotech.net/memory_management/424.html