内存管理-77-内存检测工具-1-KMSAN-1-简介

注: Linux-6.1.114 默认不使能。

一、KMSAN 简介

CONFIG_KMSAN(Kernel Memory Sanitizer)检测内核中对未初始化内存的使用——即读取了从未被写入过的变量/内存区域。

1. 检测什么问题

/* 问题 1:栈变量未初始化 */
int do_something(void) {
    int result;          //未赋值!
    if (result > 0)      //KMSAN 报错:使用了未初始化的 result
        do_work();
    return result;       //KMSAN 报错
}

/* 问题 2:kmalloc 后未初始化就使用 */
void *buf = kmalloc(128, GFP_KERNEL);
//没有 memset 或赋值
if (buf[0] == 0x5A)     //KMSAN 报错:读取未初始化的堆内存
    ...

/* 问题 3:向用户态泄漏未初始化数据(信息泄露漏洞) */
struct my_info info;
info.field_a = 1;
//info.field_b 未赋值(可能含有上一次的内核栈数据)
copy_to_user(user_buf, &info, sizeof(info));  //KMSAN 报错:field_b 未初始化

第 3 种是最严重的安全问题——内核栈上的残留数据可能包含地址、密钥等敏感信息,未初始化就传给用户态就是信息泄漏漏洞(多个 CVE 由此产生)。


2. 工作原理

KMSAN 通过 编译器插桩 + shadow memory 追踪每个字节的初始化状态:

                 实际内存                  Shadow Memory(KMSAN 维护)
              ┌─────--───────┐          ┌─────────-----------───┐
              │ 0x41 (已写)   │          │ 0x00 (已初始化)        │
              │ 0x?? (未写)   │          │ 0xFF (未初始化)        │  <-- 1:1 对应
              │ 0x00 (已写)   │          │ 0x00 (已初始化)        │
              └──────--──────┘          └─────────-----------───┘

每个内存字节都有对应的 shadow byte0x00 = 已初始化(安全)
  非零 = 未初始化(使用时报错)


3. 编译器做了什么

Clang 的 -fsanitize=kernel-memory 标志让编译器在每次:

(1) 内存写入时 → 将对应 shadow 标记为"已初始化"
(2) 内存读取时 → 检查 shadow,如果"未初始化"且值被用于分支/输出 → 报错


4. 内核运行时做了什么

(1) 页面分配时(alloc_pages)→ shadow 全部标记为"未初始化"
(2) 页面释放时(kmsan_free_page)→ shadow 标记为"未初始化"
(3) kmalloc 时 → shadow 标记为"未初始化"
(4) memset/memcpy 时 → 更新 shadow 状态
(5) copy_to_user 时 → 检查源数据的 shadow,有未初始化字节则报告


5. 与 KASAN 的区别

------------------------------------------------------------------------------------------------
特性            KASAN                            KMSAN
------------------------------------------------------------------------------------------------
检测目标        越界访问、use-after-free          使用未初始化内存
检测方式        shadow memory 标记可访问性         shadow memory 标记初始化状态
典型错误        buffer overflow、double-free     信息泄漏、未初始化变量
性能开销        ~2-3x                            ~3-5x
内存开销        +12.5%(1/8 shadow)               +300%(shadow + origin tracking)
编译器支持      GCC / Clang                      仅 Clang
内核版本        4.0+                             6.1+(较新)
架构支持        x86_64, arm64                    仅 x86_64(6.1 中)
------------------------------------------------------------------------------------------------


6. 报错示例

=====================================================
BUG: KMSAN: uninit-value in do_ioctl+0x234/0x560
Uninit was created at:
  kmalloc+0x12c/0x1e0
  my_driver_open+0x48/0x120

CPU: 2 PID: 1234 Comm: test_app
Call trace:
  do_ioctl+0x234/0x560
  vfs_ioctl+0x44/0x60
  __arm64_sys_ioctl+0xac/0xe0
  ...

Bytes 8-11 of 64 are uninitialized
Memory access of size 4 at ffff888012345678
=====================================================

报告中会告诉你:
(1) 哪里使用了未初始化内存(do_ioctl+0x234)
(2) 这块内存是在哪里分配的(kmalloc in my_driver_open)
(3) 具体哪几个字节未初始化(bytes 8-11)

kmsan_free_page 在 free_pages_prepare 中的作用:

//free_pages_prepare 中:
trace_mm_page_free(page, order);
kmsan_free_page(page, order); //这里

void kmsan_free_page(struct page *page, unsigned int order)
{
    /*
     * 将这个页面对应的 shadow memory 全部标记为"未初始化", 这样如果有人 use-after-free 读这个页面,KMSAN 会捕获到
     *(即使物理内容还在,shadow 已经标记为 uninit)
     */
    kmsan_internal_poison_memory(page_address(page), PAGE_SIZE << order, GFP_KERNEL, KMSAN_POISON_FREE);
}


7. 开启方式

CONFIG_KMSAN=y

# 需要
# - Clang 14+ 编译器
# - x86_64 架构(6.1 中仅支持 x86_64,后续版本可能支持 arm64)
# - 不能与 KASAN 同时开启


8. 局限性

-------------------------------------------------------------------------------------------
局限                说明
-------------------------------------------------------------------------------------------
仅 Clang            GCC 不支持 -fsanitize=kernel-memory
仅 x86_64(6.1)      ARM64 支持在后续内核版本中逐步添加
性能开销大            3-5 倍减速 + 大量额外内存(shadow + origin)
不能与 KASAN 共存    两者都用 shadow memory,实现有冲突 ####
误报                某些故意使用未初始化内存的场景(如随机数种子收集)需要手动标注
-------------------------------------------------------------------------------------------

ARM64 设备上不能用, Linux-6.1 的 KMSAN 仅支持 x86_64。如果需要检测未初始化内存问题,替代方案:

(1) CONFIG_INIT_STACK_ALL_ZERO=y:强制所有栈变量初始化为 0(消除问题而非检测), 实测默认6.1内核使能。
(2) CONFIG_INIT_ON_ALLOC_DEFAULT_ON=y:所有页面/slab 分配时自动清零, 实测默认6.1内核使能。


9. 说Arm64目前不能用的依据

//Kconfig.kmsan
config KMSAN
    bool "KMSAN: detector of uninitialized values use"
    depends on HAVE_ARCH_KMSAN && HAVE_KMSAN_COMPILER
    ...

arch/arm64/Kconfig 中没有 select HAVE_ARCH_KMSAN 或任何与 HAVE_ARCH_KMSAN 相关的内容,意味着 ARM64 在这个内核版本中不满足 KMSAN 的依赖条件,无法开启。

结合 mainline Linux 6.1 的公开信息(kernelnewbies.org 的 6.1 Release Notes 以及 Google KMSAN 项目的文档),KMSAN 在 6.1 合入主线时仅对 x86_64 实现了架构支持代码(shadow memory 映射、汇编桩等)。ARM64 的 KMSAN 支持在更后续的版本中才开始开发。

 

posted on 2026-08-21 14:57  Hello-World3  阅读(2)  评论(0)    收藏  举报

导航