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

目录

initail buddy
alloc_pages
free_pages
总结

在《KASAN(7)-基于slab的实现》中已经分析了slab的情况了,但是还有一部分情况是基于buddy系统的,kasan在buddy上的实现是比较特殊的,本文分析伙伴系统下的kasan实现。

initail buddy

初始化伙伴系统的时候系统中未占用的内存全部调用buddy系统的free接口初始化,初始化代码流程如下

start_kernel mm_init mem_init memblock_free_all free_low_memory_core_early __free_memory_core __free_pages_memory memblock_free_pages __free_pages_core __free_pages_ok free_pages_prepare kernel_poison_pages

如下代码可以看到kernel_poison_pages的代码实现依赖于CONFIG_PAGE_POISONING,此配置用于代表是否对page下毒,如下

#ifdef CONFIG_PAGE_POISONING static inline bool page_poisoning_enabled_static(void) { return static_branch_unlikely(&_page_poisoning_enabled); } static inline void kernel_poison_pages(struct page *page, int numpages) { if (page_poisoning_enabled_static()) __kernel_poison_pages(page, numpages); } #else static inline bool page_poisoning_enabled_static(void) { return false; } static inline void kernel_poison_pages(struct page *page, int numpages) { } static inline void kernel_unpoison_pages(struct page *page, int numpages) { } #endif

比较遗憾的是,我的设备并没有打开CONFIG_PAGE_POISONING,所以默认情况下buddy系统的影子区域并不会下毒,而且根据之前的分析,我们知道默认影子的值是0(KASAN_SHADOW_INIT=0),所以可以得到结论,对于buddy系统初始化过程中没有下毒。

关于这点分析,其实从测试用例中已经得到解释了,如下

static void pagealloc_oob_right(struct kunit *test) { char *ptr; struct page *pages; size_t order = 4; size_t size = (1UL << (PAGE_SHIFT + order)); /* * With generic KASAN page allocations have no redzones, thus * out-of-bounds detection is not guaranteed. * See https://bugzilla.kernel.org/show_bug.cgi?id=210503. */ KASAN_TEST_NEEDS_CONFIG_OFF(test, CONFIG_KASAN_GENERIC); pages = alloc_pages(GFP_KERNEL, order); ptr = page_address(pages); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ptr); KUNIT_EXPECT_KASAN_FAIL(test, ptr[size] = 0); free_pages((unsigned long)ptr, order); }

alloc_pages

根据分析,CONFIG_KASAN_GENERIC下的kasan是不能检测buddy的oob问题的,那么是不是sw-tag/hw-tag能够检测呢。我们还是继续分析,alloc_page代码流程

alloc_pages alloc_pages_current __alloc_pages_nodemask get_page_from_freelist prep_new_page post_alloc_hook kasan_alloc_pages

我们查看kasan_alloc_pages的实现,如下

#ifdef CONFIG_KASAN_HW_TAGS void kasan_alloc_pages(struct page *page, unsigned int order, gfp_t flags); void kasan_free_pages(struct page *page, unsigned int order); #else /* CONFIG_KASAN_HW_TAGS */ static __always_inline void kasan_alloc_pages(struct page *page, unsigned int order, gfp_t flags) { /* Only available for integrated init. */ BUILD_BUG(); } static __always_inline void kasan_free_pages(struct page *page, unsigned int order) { /* Only available for integrated init. */ BUILD_BUG(); } #endif /* CONFIG_KASAN_HW_TAGS */

也就是如果是HW_TAGS下的kasan是支持在伙伴系统内下毒的,由于本机器不支持hw tags,接下来就不做分析了。

那么总结一下就是,默认内核提供了 CONFIG_PAGE_POISONING 调试接口给伙伴系统的所有页面进行下毒,但此配置默认不开,所以伙伴系统初始化之后其影子页面是值为0的页面,再者通过alloc_pages调用下的页面,只有是 CONFIG_KASAN_HW_TAGS 才提供标记值来下毒,而我的设备是 CONFIG_KASAN_GENERIC 的配置,所以alloc_pages下的内存,kasan相当于什么也没做。也就意味着 CONFIG_KASAN_GENERIC 下的伙伴系统,kasan无法检测其oob问题

free_pages

除了oob问题,还有uaf的问题,所以关于free_pages还是需要分析,首先基于代码流程分析,如下

free_pages __free_pages free_the_page __free_pages_ok free_pages_prepare

这里有段逻辑,如下

static inline bool kasan_has_integrated_init(void) { return kasan_hw_tags_enabled(); } --------------------------------------------------- if (kasan_has_integrated_init()) { if (!skip_kasan_poison) kasan_free_pages(page, order); } else { bool init = want_init_on_free(); if (init) kernel_init_free_pages(page, 1 << order, false); if (!skip_kasan_poison) kasan_poison_pages(page, order, init); }

其逻辑解析如下

  1. 如果是hw tags,则调用kasan_free_pages
  2. 如果不是hw tags,内核配置了init_on_free,那么free时会清空页面
  3. 默认调用kasan_poison_pages

我们查看kasan_poison_pages的代码如下

static __always_inline void kasan_poison_pages(struct page *page, unsigned int order, bool init) { if (kasan_enabled()) __kasan_poison_pages(page, order, init); } void __kasan_poison_pages(struct page *page, unsigned int order, bool init) { if (likely(!PageHighMem(page))) kasan_poison(page_address(page), PAGE_SIZE << order, KASAN_FREE_PAGE, init); }

可以看到,虽然kasan在伙伴系统alloc的时候什么也没有做,但是在free的时候还是下毒成了KASAN_FREE_PAGE,也就是0xff, 这也就意味着,在 CONFIG_KASAN_GENERIC 下kasan可以检测 伙伴系统的uaf问题。

我们可以根据《KASAN(3)-测试分析》的pagealloc api test uaf章节的日志分析,如下

[ 5510.618665] BUG: KASAN: use-after-free in pagealloc_uaf+0xb4/0xcc [test_kasan_kylin] [ 5510.618679] Write of size 1 at addr ffffff80a4080000 by task insmod/3253 [ 5510.619044] page:000000009a7015d6 refcount:0 mapcount:-128 mapping:0000000000000000 index:0x0 pfn:0xa4080 [ 5510.619058] flags: 0x0() [ 5510.619133] ffffff80a407ff00: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [ 5510.619146] ffffff80a407ff80: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [ 5510.619159] >ffffff80a4080000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [ 5510.619170] ^ [ 5510.619183] ffffff80a4080080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [ 5510.619196] ffffff80a4080100: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

可以看到,伙伴系统下的页面,如果被free后,会设置值为0xff,其目的是检测伙伴系统下的uaf问题

总结

本文介绍了伙伴系统下的kasan实现,由于本文主要讨论是 CONFIG_KASAN_GENERIC 下的kasan,所以伙伴系统初始化的情况下,并没有下毒,并且在调用alloc_pages时,也没有下毒,所以 CONFIG_KASAN_GENERIC 下的kasan无法检测伙伴系统的oob问题,但是kasan会在free_pages时下毒,所以 CONFIG_KASAN_GENERIC 下的kasan还是能够检测uaf的问题的。