CVE-2016-4470内核补丁技术问询:变量修复逻辑与竞态条件分析
先快速回顾漏洞背景
原漏洞描述:Linux内核4.6.3及更早版本中,
security/keys/key.c文件内的key_reject_and_link函数未确保某数据结构完成初始化,本地用户可通过构造恶意keyctl request2命令触发拒绝服务(系统崩溃)。
作为C语言新手,咱们从变量修复、竞态条件、完整机制三个角度一步步拆解,尽量不用晦涩术语:
1. 补丁里的变量是怎么修复漏洞的?
这个漏洞的根源其实就是C语言里最常见的“局部变量未初始化”问题。
在漏洞版本的代码中,当key_reject_and_link函数处理失败的密钥请求时,会创建一个叫struct keyctl_reply的结构体——这个结构体是用来给用户空间返回错误结果的。原代码只初始化了结构体里的type(标记这是错误回复)和data(存储具体错误码)两个字段,却完全忘了初始化len字段(用来告诉内核要给用户空间拷贝多少字节的数据)。
给你看个简化的原代码片段,一眼就能发现问题:
struct keyctl_reply reply; // 初始化错误类型和错误码 reply.type = KEYCTL_REPLY_TYPE_ERROR; reply.data = error_code; // 这里!没给reply.len赋值! keyctl_reply_to_user(&reply, user_space_buffer);
C语言里,栈上的局部变量如果不手动初始化,会保留栈里之前剩下的随机“垃圾值”。当后续的keyctl_reply_to_user函数用这个reply.len的值时,就会出大问题:
- 如果垃圾值是个超大的数,函数会尝试往用户空间拷贝远超预期的数据,直接触发内存越界,内核当场崩溃(也就是所谓的DoS);
- 就算垃圾值不大,也可能把内核里的敏感数据泄露给用户(不过这个漏洞主要表现是崩溃)。
补丁的修复超级直接:给reply.len明确赋上正确的值。比如补丁里会加一行:
reply.len = sizeof(error_code);
因为data里存的是一个错误码(通常是int类型,4字节左右),所以长度就是错误码的字节数。这样keyctl_reply_to_user就能准确知道要拷贝多少数据,既不会越界崩溃,也不会泄露垃圾数据。
2. 这个漏洞涉及竞态条件吗?
完全不涉及。
竞态条件的核心是“多个执行路径(比如多线程、中断)同时操作共享资源,结果依赖于执行顺序”。但这个漏洞的根源是单执行路径下的局部变量未初始化——纯粹是代码写的时候的疏忽,和并发、共享资源一点关系都没有。不管是单进程还是多进程触发,只要走到这个错误分支,未初始化的字段就会引发问题,和竞态半毛钱关系都没有。
3. 补丁的完整修复机制拆解
咱们把整个逻辑链理得清清楚楚:
- 触发路径:本地用户构造恶意的
keyctl request2请求,让内核处理这个请求时走到key_reject_and_link的错误处理分支; - 原代码的坑:创建
keyctl_reply结构体时,漏了初始化len字段,导致这个字段是栈上的随机垃圾值; - 补丁的动作:在构造回复结构体时,明确设置
reply.len = sizeof(error_code),把长度固定为错误码的字节数; - 修复后的效果:
keyctl_reply_to_user函数用正确的长度拷贝数据到用户空间,不会再出现内存越界,自然也就不会触发内核崩溃了。
说白了,这个补丁就是补上了一个C语言新手也容易犯的“忘记初始化局部变量”的小错误,但在内核里,这种小错误就会变成严重的DoS漏洞。
内容的提问来源于stack exchange,提问作者anon_user




