slab:编译器辅助的类型化 slab 缓存分区 — 按类型隔离对象减少内存碎片与跨对象污染

内存分配器(SLUB)· kmalloc 分区缓存 · 编译器 allocation token · 堆安全加固

💡 一句话总结

内核的通用 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)合入

📊 速览卡片

核心机制
token 分区
优化目标
指针隔离
适用场景
堆安全加固
特性等级
★★★
实测提升
未提供基准

特性等级依据:这是安全加固特性而非直接性能优化,无吞吐/延迟基准(作者只自报"以相对较低性能代价实现"),故性能提升幅度无法给高分;但落地相对容易(Kconfig 开关、仅需 Clang 22+ 工具链、无特殊硬件)、向后兼容(RANDOM 模式保留为过渡)、场景覆盖广(全部 kmalloc 分配受益)——综合 ★★★。

🎯 解决什么问题

系列整体定位(本补丁所属 v4 补丁系列)
本补丁是 Marco Elver 的 [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 原文事实。
系统层面:kmalloc 通用缓存"按大小不分类型"
通用 kmalloc 缓存按大小分桶(kmalloc-8、kmalloc-16 … kmalloc-4096),同一大小的不同类型对象共用同一个 cache;SLUB 还会在结构大小/对齐相同的情况下合并不同 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"。
场景层面:处理不可信输入的内核路径
内核里大量路径在"解析不可信数据"时同时持有原始字节缓冲区与关键对象:网络报文解析(skb、协议头、控制块)、文件系统解包(目录项、元数据缓冲)、消息/请求解析(IPC、设备 ioctl)、虚拟化/虚拟机监控(客户机输入)。这些路径上,攻击者控制的一段输入被拷进一个大小正好的 kmalloc 缓冲区,若解析代码有一个 off-by-N 或未检查的长度,越界写就会落在同 cache 相邻对象上。此特性的价值在于:即使发生了这类越界写,它也无法跨分区到达含指针的对象——原始缓冲区分在 token 0-7 区,指针对象分在 token 8-15 区,二者物理上不再相邻。
受影响负载:处理不可信输入的内核路径(网络 / 文件系统 / 消息解析)+ 安全敏感部署(云、多租户、沙箱)· 为什么此特性解决此场景:按"是否含指针"把对象分区后,原始缓冲区的越界写无法直接污染关键指针,阻断"越界写 → 任意地址读写"的放大链

🧩 核心机制

一句话机制:在 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)。从根上把"可被越界写的原始缓冲区"与"指针/元数据"物理隔开。
类型化 slab 分区机制:分配调用点 → 编译器 __builtin_infer_alloc_token() 类型推断 → token ID → kmalloc_type() 确定性选 cache → 指针/无指针分区
图 1:类型化 slab 缓存分区的 token 流与指针/无指针分区
来源:基于本地内核 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)
3token 经 PASS_TOKEN_PARAMS 传入 kmalloc 族,到 kmalloc_type(flags, token)返回 KMALLOC_PARTITION_START + token.v,确定性选 cache
4kmalloc_slab() 依此选 kmalloc-part-NN(SLAB_NO_MERGE,禁止合并)含指针(token 8-15)与无指针(token 0-7)物理分区
关键代码片段:token 宏 + kmalloc_type() 确定性选 cache
+#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 模式保留哈希 + 随机种子,行为与旧版一致,保证向后兼容。

📈 性能影响

提升角度(方法论分类)
主:内存层次 / 安全加固成本优化——本补丁不是"提升吞吐"型优化,而是"以相对较低的性能代价换取安全隔离"。commit message 明确说 "heap isolation strategies offer a best-effort approach … albeit achievable at relatively low performance cost"。
次:on-CPU 分配路径开销——token 是编译期常量(对常量大小分配)或一次简单计算,kmalloc_type() 只是把 token.v 当作数组下标,热路径上多出的成本几乎为零;RANDOM 模式的哈希计算本就存在。
按 Brendan Gregg 的 on-CPU vs off-CPU 视角:本补丁影响的是 on-CPU 分配路径(选 cache 的索引计算),无 off-CPU(锁/IO)成分。
受益场景(解读(AI 分析))
从改动路径反推:受益最大的是安全敏感部署(云多租户、沙箱、处理不可信输入的服务),它们在乎的是"即使发生内存破坏 bug 也不被放大成 RCE",本质是可用性/安全性带来的间接性能价值(减少安全事件导致的停机、补丁、回归测试)。对普通负载,本补丁是"花少量内存换一层纵深防御"。代价侧:16 个分区 cache 全部 SLAB_NO_MERGE,同类对象不再跨类型合并,内存碎片与每个 cache 的空闲对象浪费会略增(作者自述为"limited degree of memory and CPU overhead that relates to hardware and system workload")。
实测数据
补丁未提供吞吐/延迟基准。commit message 提供的唯一量化数据是作者在 x86 defconfig 内核启动后的 /proc/slabinfo 直方图(作者自报、未独立验证):16 个分区 cache 中,kmalloc-part-01..07(无指针/未识别)合计 6673 个对象,kmalloc-part-08..15(含指针)合计 12015 个对象;此外用 -Rpass=alloc-token 诊断出 186 个类型推断失败点(RFC 阶段为 966),多为可变长缓冲与带柔性数组的结构。这些数字说明的是"分区效果与推断覆盖率",不是性能基准。本报告不编造任何提升百分比。

🔄 方案演进

RFC → v2 → v4 演进脉络
RFC(2025-08-25):首次提出用 Clang allocation tokens 做类型化 kmalloc 分区;当时类型推断失败点高达 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 进入主线。
设计权衡 / Review 推进
Kees Cook 的第一条质疑(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: 标签 + 署名链),是合入主线的权威记录。

⚠️ 风险与局限

收益成立的前提
依赖 Clang 22+ 编译工具链:__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 缓解页重用类攻击。
生产落地影响
Kconfig 开关与迁移:默认 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。
生态 / 兼容性
对 GCC 用户无 TYPED 收益(回退 RANDOM/无分区);kmalloc_token_t 参数通过宏透明贯穿 kmalloc 族,外部调用方源码级不变。模块/驱动用 kmalloc() 自动获得 token 传参(宏展开),无需改动。与 kmalloc_obj() / kmalloc_flex()(Linux 7.0 起)是天然配合:后者固定分配模式,前者按类型分区,二者合起来推断覆盖率才高。未来 SLAB_VIRTUAL(LWN 944647)可提供物理页级隔离,是此特性的下一步演化方向。
review 质疑(可追溯的已解决项)
Kees Cook 的"完全隔离 vs 16 分区"质疑(202604210954.84C57E5E0@keescook)与 Kconfig 命名建议(D3E316B3-9909-408E-85BF-647383810CFF@kernel.org)——作者采纳命名并保留 RANDOM 兼容,完全隔离留待未来新模式,已在 v4 解决。未发现针对本补丁的公开 NACK。lore 完整讨论串仅 v2/v4 可查,v1/v3 未收录。
严重度:MINOR · 落地场景:风险集中在"工具链门槛(Clang 22+)+ 分区 cache 的内存开销",以及类型推断失败点(186 个)上的防御退化;落地是低风险的安全加固,不改变现有 API 语义。

🔗 交叉引用

📌 关联工作 / 系列相关补丁
slab: Introduce kmalloc_obj() and family(Kees Cook,Linux 7.0)——类型感知对象分配 API,固定"类型以可推断形式表达"的模式,本补丁依赖它
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 基于公开知识辅助生成,仅供学习参考,请勿直接引用作为依据。作者不对信息的准确性、完整性及适用性作保证,亦不对因使用本站内容产生的任何损失承担责任。