slab:编译器辅助的类型化 slab 缓存分区 — 按类型隔离对象减少内存碎片与跨对象污染
💡 一句话总结
内核的通用 kmalloc 缓存把大小相近但类型不同的对象(网络协议控制块、文件系统结构、原始字节缓冲区)混放在同一片 slab 里,一旦攻击者获得某个原始缓冲区的越界写,就能直接污染相邻对象的指针或引用计数这类关键元数据;本补丁让编译器(Clang 22+ 的 __builtin_infer_alloc_token())在编译期推断每次 kmalloc 的对象类型,得到一个类型派生 token,再用这个 token 作为 16 个分区 slab 缓存(kmalloc-part-01..15)的确定性索引,把"含指针对象"与"无指针/原始缓冲区"隔离到不同分区——越界写难以跨分区污染指针对象。作者自报该加固"以相对较低的性能代价实现"(未提供吞吐/延迟基准),并用 /proc/slabinfo 直方图佐证对象分布合理(6673 个无指针/未识别对象落入 00-07 区,12015 个含指针对象落入 08-15 区,均为作者自报、未独立验证)。
📋 补丁基本信息
| 项目 | 内容 |
|---|---|
| 补丁类型 | 新特性(安全加固 / 分配器基础设施)——主导类型为安全加固;兼有重构(把 RANDOM_KMALLOC_CACHES 重构为 KMALLOC_PARTITION_CACHES 通用分区框架) |
| 状态 | 状态(Merged)· 合入版本:Linux 7.2(v7.2-rc1 起;2026-06-16 经 slab-for-7.2 合并提交 f8115f0e8a05 并入主线;commit 2026-05-14 合入 slab 树) |
| 当前版本 | v4([PATCH v4 1/3],即合入版)· v4 lore 链接 |
| 版本演进 |
RFC(2025-08-25)→ v2(2026-04-16)→ v3 → v4(2026-05-11,合入) (本环境 lore 索引仅收录 v2 与 v4 线程;v1/v3 未命中,见"方案演进"数据源说明) |
| 作者机构 | Marco Elver(Google)<elver@google.com> |
| 提交日期 | authored 2026-05-11 · committed 2026-05-14 |
| 改动范围 | include/linux/slab.h、mm/slub.c、mm/slab_common.c、mm/slab.h、mm/Kconfig、init/Kconfig、Makefile、kernel/configs/hardening.config、include/linux/percpu.h、mm/kfence/kfence_test.c,+220/-115 行,10 文件 |
| 核心函数 | __builtin_infer_alloc_token() / __kmalloc_token() / kmalloc_type() / kmalloc_slab() / __kmalloc_noprof() / kmalloc_token_t |
| 原始链接 | git.kernel.org(规范 commit 链接) |
| Review / 合入 | Acked-by: GONG Ruiqi(华为)· Reviewed-by & Co-developed-by: Harry Yoo(Oracle)· 由 slab 维护者 Vlastimil Babka(SUSE)合入 |
📊 速览卡片
特性等级依据:这是安全加固特性而非直接性能优化,无吞吐/延迟基准(作者只自报"以相对较低性能代价实现"),故性能提升幅度无法给高分;但落地相对容易(Kconfig 开关、仅需 Clang 22+ 工具链、无特殊硬件)、向后兼容(RANDOM 模式保留为过渡)、场景覆盖广(全部 kmalloc 分配受益)——综合 ★★★。
🎯 解决什么问题
[PATCH v4 1/3] 系列中的主体补丁。该系列整体要做的事:把 Linux 的 kmalloc 通用缓存从"按大小分桶 + 可随机化"升级为"可配置的确定性分区"。系列补丁 2/3、3/3 的内容未能在本环境 lore 索引中确认(仅 1/3 被收录);但本补丁机制所依赖的类型感知分配 API——kmalloc_obj() / kmalloc_flex()(Kees Cook,2026-01-14,commit 2932ba8d9c99 / e4c8b46b924e)——已随 Linux 7.0 合入主线,为编译器识别分配类型提供了统一、不变的模式(见"交叉引用")。只看本补丁容易只见"加了一个 Kconfig 选项",而看不到它吃的是 7.0 就已就位的类型化 API 红利。
RANDOM_KMALLOC_CACHES(随机化 kmalloc cache,按代码地址 + 每启动随机种子选 cache,用于防堆喷射)。commit message 明确指出其局限:随机化是概率性的——"While attackers don't know which kmalloc cache an object will be allocated from, they might circumvent the randomization by retrying attacks across multiple machines until the target objects are co-located"(攻击者可以跨机器重试直到目标对象被放到一起)。本补丁引入的 KMALLOC_PARTITION_TYPED 模式改为确定性分配:"this mode deterministically assigns a slab cache to an allocation of type T, regardless of allocation site",并引用 iOS 18 的利用研究(dfsec 博客,blasting past iOS 18)作为"按指针/无指针隔离可缓解某类内存破坏利用"的论据。以上均为 commit message 原文事实。
kmem_cache(SLAB_MERGE)。结果是:一个原始字节缓冲区和一个存着指针/引用计数的关键对象,物理上可能就在同一个 slab 的相邻 object 里。攻击者只要先拿到一个无指针原始缓冲区的越界写,就能把相邻的指针字段改成任意地址,把一次"越界读/写"放大成任意地址读写。这正是 commit message 所述要缓解的:"attackers who gains a buffer overflow on a primitive buffer cannot use it to directly corrupt pointers or other critical metadata in an object residing in a different, isolated heap region"。
🧩 核心机制
一句话机制:在 kmalloc 调用点用编译器内置函数算出一个"类型 token",让这个 token 顺着 kmalloc 函数参数一路传到 cache 选择处(kmalloc_type()),作为 16 个分区 slab cache 的确定性下标。
CONFIG_RANDOM_KMALLOC_CACHES 重命名为 CONFIG_KMALLOC_PARTITION_CACHES,并把原"随机化"降格为其中的一个模式 KMALLOC_PARTITION_RANDOM;新增 KMALLOC_PARTITION_TYPED 模式(mm/Kconfig 用 Kconfig choice 让用户在两种模式间二选一)。旧符号保留为 transitional 过渡配置,已有用户的 RANDOM_KMALLOC_CACHES=y 不破坏。② 类型 token 机制:新增
kmalloc_token_t 与 __kmalloc_token(...) 宏。在 KMALLOC_PARTITION_TYPED 下它展开为 __builtin_infer_alloc_token(__VA_ARGS__)——把 kmalloc 的参数(大小/类型)喂给编译器内置函数,编译器尽力推断对象类型并返回一个类型派生 token。关键设计是:这个 token 不是像 RANDOM 那样在分配器内部用调用地址现算,而是在每个调用点由编译器算好,再作为参数传入(PASS_TOKEN_PARAMS / DECL_TOKEN_PARAMS 宏贯穿 kmalloc/kzalloc/kmalloc_array/krealloc/kvmalloc 全族)。这样"同一类型无论在哪调用都进同一 cache",这是确定性分区成立的前提。③ 指针/无指针分区:Clang 的默认 token 模式
typehashpointersplit 把 token ID 设为类型名的哈希,且高半区(8-15)保留给含指针的类型、低半区(0-7)给不含指针的类型。内核侧 kmalloc_type() 直接返回 KMALLOC_PARTITION_START + token.v,于是含指针对象永远进 kmalloc-part-08..15,无指针/未识别对象进 kmalloc-part-01..07(token 0 落入普通 kmalloc)。从根上把"可被越界写的原始缓冲区"与"指针/元数据"物理隔开。
来源:基于本地内核 git(commit feb662d9168b)真实 diff 绘制
| 步骤 | 操作 | 目的 |
|---|---|---|
| 1 | 调用点写 kmalloc(sizeof(struct foo), GFP) 等类型可推断形式 | 让编译器能从参数识别对象类型 T(kmalloc_obj/kmalloc_flex 固定了这一模式) |
| 2 | __kmalloc_token(...) 展开为 __builtin_infer_alloc_token(...) | 编译期算出类型派生 token(Clang 22+;失败回退 0) |
| 3 | token 经 PASS_TOKEN_PARAMS 传入 kmalloc 族,到 kmalloc_type(flags, token) | 返回 KMALLOC_PARTITION_START + token.v,确定性选 cache |
| 4 | kmalloc_slab() 依此选 kmalloc-part-NN(SLAB_NO_MERGE,禁止合并) | 含指针(token 8-15)与无指针(token 0-7)物理分区 |
+#ifdef CONFIG_KMALLOC_PARTITION_CACHES
+typedef struct { unsigned long v; } kmalloc_token_t;
+#ifdef CONFIG_KMALLOC_PARTITION_RANDOM
+extern unsigned long random_kmalloc_seed;
+#define __kmalloc_token(...) ((kmalloc_token_t){ .v = _RET_IP_ })
+#elif defined(CONFIG_KMALLOC_PARTITION_TYPED)
+#define __kmalloc_token(...) ((kmalloc_token_t){ .v = __builtin_infer_alloc_token(__VA_ARGS__) })
+#endif
...
-extern unsigned long random_kmalloc_seed;
-
-static __always_inline enum kmalloc_cache_type kmalloc_type(gfp_t flags, unsigned long caller)
+static __always_inline enum kmalloc_cache_type kmalloc_type(gfp_t flags, kmalloc_token_t token)
{
/*
* The most common case is KMALLOC_NORMAL, so test for it
* with a single branch for all the relevant flags.
*/
if (likely((flags & KMALLOC_NOT_NORMAL_BITS) == 0))
-#ifdef CONFIG_RANDOM_KMALLOC_CACHES
- /* RANDOM_KMALLOC_CACHES_NR (=15) copies + the KMALLOC_NORMAL */
- return KMALLOC_RANDOM_START + hash_64(caller ^ random_kmalloc_seed,
- ilog2(RANDOM_KMALLOC_CACHES_NR + 1));
+#ifdef CONFIG_KMALLOC_PARTITION_RANDOM
+ /* KMALLOC_PARTITION_CACHES_NR (=15) copies + the KMALLOC_NORMAL */
+ return KMALLOC_PARTITION_START + hash_64(token.v ^ random_kmalloc_seed,
+ ilog2(KMALLOC_PARTITION_CACHES_NR + 1));
+#elif defined(CONFIG_KMALLOC_PARTITION_TYPED)
+ return KMALLOC_PARTITION_START + token.v;
#else
return KMALLOC_NORMAL;
#endif▲ 这段是机制成立的关键:__kmalloc_token() 把"按调用地址随机"(_RET_IP_)与"按类型确定性"(__builtin_infer_alloc_token)两种模式做成同一接口;kmalloc_type() 的入参从 unsigned long caller 换成 kmalloc_token_t token 后,TYPED 模式直接返回 KMALLOC_PARTITION_START + token.v——同一类型无论在哪调用,token 相同,必进同一 cache,这就是"确定性"的来源;而 RANDOM 模式保留哈希 + 随机种子,行为与旧版一致,保证向后兼容。
📈 性能影响
次:on-CPU 分配路径开销——token 是编译期常量(对常量大小分配)或一次简单计算,
kmalloc_type() 只是把 token.v 当作数组下标,热路径上多出的成本几乎为零;RANDOM 模式的哈希计算本就存在。按 Brendan Gregg 的 on-CPU vs off-CPU 视角:本补丁影响的是 on-CPU 分配路径(选 cache 的索引计算),无 off-CPU(锁/IO)成分。
SLAB_NO_MERGE,同类对象不再跨类型合并,内存碎片与每个 cache 的空闲对象浪费会略增(作者自述为"limited degree of memory and CPU overhead that relates to hardware and system workload")。
/proc/slabinfo 直方图(作者自报、未独立验证):16 个分区 cache 中,kmalloc-part-01..07(无指针/未识别)合计 6673 个对象,kmalloc-part-08..15(含指针)合计 12015 个对象;此外用 -Rpass=alloc-token 诊断出 186 个类型推断失败点(RFC 阶段为 966),多为可变长缓冲与带柔性数组的结构。这些数字说明的是"分区效果与推断覆盖率",不是性能基准。本报告不编造任何提升百分比。
🔄 方案演进
v2(2026-04-16):Kees Cook review 提出 Kconfig 结构调整与命名建议;作者回应讨论(详见下文)。
v3:本环境 lore 索引未命中,内容未能独立确认(推测为 v2→v4 之间的中间修订,依据是 v4 已是系列 1/3 且有华为 Acked-by)。
v4(2026-05-11):即合入版,类型推断失败点降至 186;带
Acked-by: GONG Ruiqi 与 Reviewed-by / Co-developed-by: Harry Yoo (Oracle);2026-05-14 合入 slab 树,2026-06-16 随 slab-for-7.2 进入主线。
202604210954.84C57E5E0@keescook):"16 个桶"是 RANDOM_KMALLOC_CACHES 因哈希而任意定的上限;他此前 2024 年的 call-site-partitioning 系列(链接)追求的是每类型 1 个独立 cache 的完全隔离。作者以 16 分区折中,并保留了扩展空间(Kconfig choice 模式化后,未来可加新模式)。Kees Cook 的命名建议(同一条 +
D3E316B3-9909-408E-85BF-647383810CFF@kernel.org):建议配置名用"名词-特性"顺序(KMALLOC_PARTITION_*,类比 CONFIG_SLAB_FREELIST_RANDOM),并利用 Kconfig 新关键字 transitional 平滑迁移旧配置。作者采纳:最终合入版正是 KMALLOC_PARTITION_CACHES / KMALLOC_PARTITION_RANDOM / KMALLOC_PARTITION_TYPED,且 RANDOM_KMALLOC_CACHES 保留为 transitional。数据源说明:系列 v1/v3 与部分讨论帖未在本环境 lore 索引中命中;上述演进与 review 信息来自 lore 已收录的 v2/v4 线程 + commit message(Link: 标签 + 署名链),是合入主线的权威记录。
⚠️ 风险与局限
__builtin_infer_alloc_token() 与 -falloc-token-max 是 Clang 22+ 特性(Kconfig 用 cc-option 探测 CC_HAS_ALLOC_TOKEN);GCC 或不支持的编译器回退到 KMALLOC_PARTITION_RANDOM 或无分区,TYPED 模式不可用。类型推断是尽力而为:186 个失败点(可变长缓冲、带柔性数组的结构)落入 token 0 普通 cache,仍与其他未识别对象共处,防御在这些点上是"退化"的。且 heap 隔离是 best-effort——不防跨 cache 攻击,需按作者建议搭配 SHUFFLE_PAGE_ALLOCATOR 与 init_on_free=1 缓解页重用类攻击。
KMALLOC_PARTITION_TYPED(若编译器支持)否则 RANDOM;旧 RANDOM_KMALLOC_CACHES 用户迁移平滑(transitional)。内存开销:16 个分区 cache 全部 SLAB_NO_MERGE,缓存合并被禁、对象不再跨类型聚合,碎片与每 cache 空闲对象浪费会略增(作者自述有限)。运维可观测:/proc/slabinfo 出现 kmalloc-part-01..15 系列 cache 名,可用于确认分区生效。Makefile 在启用 TYPED 时追加 -falloc-token-max=16(= KMALLOC_PARTITION_CACHES_NR + 1),要求编译环境接受该 flag。
kmalloc_token_t 参数通过宏透明贯穿 kmalloc 族,外部调用方源码级不变。模块/驱动用 kmalloc() 自动获得 token 传参(宏展开),无需改动。与 kmalloc_obj() / kmalloc_flex()(Linux 7.0 起)是天然配合:后者固定分配模式,前者按类型分区,二者合起来推断覆盖率才高。未来 SLAB_VIRTUAL(LWN 944647)可提供物理页级隔离,是此特性的下一步演化方向。
202604210954.84C57E5E0@keescook)与 Kconfig 命名建议(D3E316B3-9909-408E-85BF-647383810CFF@kernel.org)——作者采纳命名并保留 RANDOM 兼容,完全隔离留待未来新模式,已在 v4 解决。未发现针对本补丁的公开 NACK。lore 完整讨论串仅 v2/v4 可查,v1/v3 未收录。
🔗 交叉引用
slab: Introduce kmalloc_flex() and family(Kees Cook,Linux 7.0)——柔性数组成员分配 API,同样为本补丁的类型推断提供稳定模式
Clang allocation tokens 文档 — 本补丁核心机制
__builtin_infer_alloc_token() 的权威说明Blasting Past iOS 18(dfsec 博客) — commit message [2] 引用的"按指针/无指针隔离缓解内存破坏利用"论据
Kees Cook 的 call-site-partitioning 系列(2024-08) — 追求"每类型 1 个 cache 完全隔离"的前作,本补丁的折中背景
LLVM RFC:allocator partitioning hints — 上游编译器侧的讨论(commit message Link)
SLAB_VIRTUAL(LWN 944647) — 未来物理页级隔离,commit message [3] 指出的后续方向
本站内容均由 AI 基于公开知识辅助生成,仅供学习参考,请勿直接引用作为依据。作者不对信息的准确性、完整性及适用性作保证,亦不对因使用本站内容产生的任何损失承担责任。