如何给 Linux 内核提交补丁:一次真实的踩坑记录
skbuff_fclone_cache 的 usercopy 问题,过程中踩了不少坑。这篇文章记录一下整个流程,希望能帮到想给内核提补丁的朋友。
背景
事情是这样的,n132和我发现了一个内核 panic,在启用 CONFIG_HARDENED_USERCOPY 的时候会触发。
问题出在 skbuff_fclone_cache 这个 slab 缓存创建的时候没有定义 usercopy 区域,但是 skbuff_head_cache 是有的。这就导致内核在尝试把 sk_buff.cb 的数据拷贝到用户空间的时候会 BUG()。
崩溃的调用链大概是这样:
- TCP 用
alloc_skb_fclone()分配 skb skb_clone()克隆这个 skb- 克隆的 skb 被放到
sk_error_queue - 用户空间调用
recvmsg(MSG_ERRQUEUE)读错误队列 sock_recv_errqueue()调用put_cmsg()拷贝数据- 然后就炸了,
__check_heap_object()检查失败
崩溃日志长这样:
1 | [ 5.379589] usercopy: Kernel memory exposure attempt detected from SLUB object 'skbuff_fclone_cache' (offset 296, size 16)! |
这个 bug 后来被分配了 CVE-2026-22977。
分析一下崩溃
崩溃信息里说 offset 296, size 16,我们来算一下:
1 | sizeof(struct sk_buff) = 232 |
这就确认了问题确实出在克隆 skb 的 cb 字段。
第一步:环境准备
克隆正确的仓库
这里点,我们不要直接克隆 Linus 的主仓库!要根据你的补丁类型选择对应的子系统仓库:
1 | # 网络相关的 bug 修复,用这个: |
| 仓库 | 用途 |
|---|---|
net.git |
当前版本的 bug 修复 |
net-next.git |
下一版本的新功能 |
linux.git |
通用开发 |
我这个是 net模块里的代码,所以应该用 net.git。
配置 Git 邮件
编辑 ~/.gitconfig:
1 | [user] |
用 Gmail 的话需要去 Google 账户安全设置里生成一个应用专用密码。
为什么用 Mutt?
这里要特别说一下,千万不要用 Gmail 网页版发补丁!
Gmail 网页版会:
- 把纯文本转成 HTML
- 自动换行,直接把补丁搞坏
- 把 Tab 换成空格
- 各种编码问题
内核要求纯文本邮件,补丁要内联在邮件里(不是附件)。推荐的组合是:
git send-email发补丁mutt看维护者回复
Mutt 配置 ~/.muttrc:
1 | set realname = "Weiming Shi" |
第二步:写修复代码
创建工作分支:
1 | git checkout -b fix-skbuff-fclone-usercopy |
修复其实很简单,把 kmem_cache_create() 换成 kmem_cache_create_usercopy():
1 | - net_hotdata.skbuff_fclone_cache = kmem_cache_create("skbuff_fclone_cache", |
第三步:测试
编译:
1 | make menuconfig # 启用 CONFIG_HARDENED_USERCOPY=y |
测试的话推荐用 virtme-ng,比手动搞 initramfs 方便太多了:
1 | # 安装 |
第四步:自测清单
发补丁之前一定要过一遍这个清单,我就是因为跳过了编译测试,结果收到了 kernel test robot 的构建失败邮件,很尴尬。
代码风格
1 | ./scripts/checkpatch.pl --strict 0001-your-patch.patch |
常见问题:行太长、缩进用了空格(应该用 Tab)、行尾有空白。
多配置编译
1 | make defconfig && make -j$(nproc) |
启动测试
1 | vng --build --run |
验证修复
1 | ./poc_put_cmsg |
打补丁前会 panic,打完应该没事了。
跑 selftests
1 | make -C tools/testing/selftests/net run_tests |
拼写检查
1 | codespell your-patch.patch |
第五步:写提交信息
这是我踩坑最多的地方。
一个合格的提交信息长这样:
1 | net: skbuff: add usercopy region to skbuff_fclone_cache |
几个要点
- 标题格式:
子系统: 简短描述,不超过 72 字符 - 正文每行不超过 75 字符,这个很重要!可以用 vim 的
:set cc=75画条线 - Fixes 标签:指向引入 bug 的那个提交
- Signed-off-by:你的名字和邮箱
调用栈怎么写
Eric Dumazet 给我的反馈:
“下次发调用栈之前,先跑一下 scripts/decode_stacktrace.sh 拿到有意义的符号。”
所以要先处理一下:
1 | ./scripts/decode_stacktrace.sh vmlinux < raw_stack_trace.txt |
然后精简,不要把整个 dmesg 贴上去。删掉时间戳、寄存器 dump、模块列表,只保留错误信息和关键调用链:
1 | usercopy: Kernel memory exposure attempt detected from SLUB object |
第六步:生成和发送补丁
生成补丁:
1 | git format-patch -1 -v4 --subject-prefix="PATCH net" |
-v4 表示第 4 版。
检查一下:
1 | ./scripts/checkpatch.pl *.patch |
找维护者:
1 | ./scripts/get_maintainer.pl net/core/skbuff.c |
发送:
1 | git send-email \ |
一个坑:security@kernel.org
Eric Dumazet 还跟我说:
“如果你已经在公开邮件列表上发了,就不要再抄送 security@kernel.org 了,没用。”
规则:
- 私下披露 → 抄送
security@kernel.org - 已经公开 → 不要抄送
第七步:处理反馈
发完补丁会收到各种反馈:
- Kernel Test Robot:自动构建测试,会告诉你编译有没有问题
- 维护者:代码审查
- 其他开发者:评论
我的 v2 就收到了 robot 的构建失败报告,因为函数参数传错了。教训就是本地一定要先编译测试。
收到反馈后:
- 不要在邮件里直接回复修复代码,发新版本
- 用
-v3、-v4标记版本 - 在
---下面加更新日志说明改了什么
第八步:申请 CVE
等补丁合并之后再说。
根据内核 CVE 文档,未修复的问题不会分配 CVE,要等补丁进了 stable 树才行。
大多数情况下 CVE 会自动分配,内核 CVE 团队(Greg KH、Sasha Levin、Lee Jones)会审查每个进 stable 的补丁。如果他们觉得是安全问题就会自动分配 CVE,公告在 linux-cve-announce。
如果漏了,可以发邮件到 cve@kernel.org。注意这个地址只用于已合并的修复,未修复的安全问题发 security@kernel.org。
我这个补丁合并后被分配了 CVE-2026-22977。
和维护者的邮件沟通
这里要特别说一下和 Eric Dumazet(Google 的 netdev 维护者)的沟通过程。
我最开始的方案(v4)是直接给 skbuff_fclone_cache 加 usercopy region:
1 | // v4 方案:修改 net/core/skbuff.c |
但 Eric 在 邮件 里提了另一种思路:
“use a bounce buffer for copying skb->mark”
他的意思是,与其放宽 skbuff_fclone_cache 的安全限制,不如在 sock_recv_errqueue() 里用一个栈上的临时变量做中转:
1 | // v5 方案:修改 net/core/sock.c |
这样就不用动 slab cache 的配置,安全性更好。
所以我发了 v5 用 bounce buffer 的方案,最终合并的就是这个版本。这个过程让我学到了:维护者的反馈很有价值,他们比你更了解代码的设计意图。
我踩过的坑
- v1:提交信息格式全错,标题写成了
Signed-off-by:,离谱 - v2:漏了 From 头,
.gitconfig没配好 - v2:忘了抄送邮件列表,一定要跑
get_maintainer.pl - v3:代码写错了,偏移量算错,本地没测就发了
- v4:amend 了十几次,说明提交前应该检查更仔细
- 调用栈没解码,被 Eric 提醒了
- **不该抄送 security@**,已经公开了还抄送没意义
时间线
| 日期 | 版本 | 改动 |
|---|---|---|
| 12月15日 | v1 | 初次提交,格式全错 |
| 12月16日 | v2 | 修了提交信息 |
| 12月16日 | v3 | 加了 From 头,修了 CC/TO |
| 12月16日 | v4 | 修了代码 |
| 12月24日 | v5 | 试了另一种方案 |
小结
回顾一下整个流程:
- 下载内核源码,注意选对仓库(bug 修复用
net.git,新功能用net-next.git) - 改代码,修 bug
- 编译,确保没 warning
- 用
vng或 QEMU 启动测试,跑 PoC 确认修复了 - 跑对应模块的测试,不同子系统有不同的测试工具:
- 网络:
make -C tools/testing/selftests/net run_tests - BPF:
make -C tools/testing/selftests/bpf run_tests - 内存管理:
make -C tools/testing/selftests/mm run_tests - 文件系统:
make -C tools/testing/selftests/filesystems run_tests - 通用:
make kselftest TARGETS=<子系统>
- 网络:
- 跑
./scripts/checkpatch.pl --strict检查代码风格 - 写提交信息,每行 75 字符,用
:set cc=75画线对齐 - 调用栈要用
./scripts/decode_stacktrace.sh解码,然后精简 - 加上
Fixes:、Reported-by:、Signed-off-by:这些标签 git format-patch生成补丁./scripts/get_maintainer.pl找维护者git send-email发送,不要用 Gmail 网页版- 如果已经公开了,不要抄送 security@kernel.org
- 收到反馈就发新版本(
-v2、-v3),不要在邮件里直接回复代码 - 等合并后 CVE 会自动分配,或者发邮件给
cve@kernel.org
最后,Eric Dumazet 在邮件里说了一句,当时看到时候还蛮开心的(
“Congratulations on your first linux contribution!”
从分析 bug 到补丁被合并(commit f9ac7befe5f1),这个过程n132可谓是手把手教我了,这里得再次感谢一下他。
参考资料
- 1.提交补丁文档 ↩
- 2.邮件客户端配置 ↩
- 3.内核 CVE 流程 ↩
- 4.Elixir Bootlin - 带超链接的内核源码浏览 ↩
- 5.linux-cve-announce - CVE 公告 ↩
- 6.我的 v2 被 robot 打回的邮件 ↩
- 7.Eric Dumazet 建议 bounce buffer 方案的邮件 ↩
- 8.https://n132.github.io/2025/07/10/How-to-patch-a-linux-kernel-bug.html ↩
Comments