明尼苏达大学因插入实验性漏洞,被禁止贡献 Linux 内核代码!
因为这事涉及到中国导师和中国学生国内外相关社区都讨论得比较火爆,但是实际上Kangjie Lu和Qiushi Wu提交的漏洞本身属于研究的一部分,在完成实验后也都撤回了相应的提交或是修复了提交的漏洞。他们之前对苹果也进行过相关实验,并且苹果也根据结论对系统进行了加固。这个实验简单说就是借着修复一个bug的机会引入可攻击的漏洞。
实际引爆这个事件的是Kangjie Liu的一个印度学生Aditya Pakki提交的代码。这段代码最初几个评审者都没提出问题,后来被Google首席工程师Eric Dumazet评审时发现了问题,社区大佬Greg K-H也注意到了这个提交并提出了批评,结果这哥们死鸭子嘴硬,然后事情就爆了...
那么这个提交到底存在什么问题?做为曾经搓过C++的老码农一定要围观一下。先说结论,这个问题非常非常低级,但是也容易被忽视。如果是面试遇到的话,没有下一轮了。不懂内核没关系,只要大学C语言好好学了就能分析出哪里有问题。
提交具体内容点这里:https://github.com/torvalds/linux/commit/0c85a7e87465f2d4cbc768e245f4f45b2f299b05
第一个修改。被注释部分是这三哥添加的代码(rm = NULL)。
void rds_message_put(struct rds_message *rm)
{
rdsdebug("put rm %p ref %d\n", rm, refcount_read(&rm->m_refcount));
WARN(!refcount_read(&rm->m_refcount), "danger refcount zero on %p\n", rm);
if (refcount_dec_and_test(&rm->m_refcount)) {
BUG_ON(!list_empty(&rm->m_sock_item));
BUG_ON(!list_empty(&rm->m_conn_item));
rds_message_purge(rm);
kfree(rm);
// + rm = NULL;
}
}
第二个修改,其它内容不重要,只保留关键部分,被注释部分是这三哥修改的代码(if (was_on_sock && rm))。
static void rds_send_remove_from_sock(struct list_head* messages, int status)
{
rds_message_put(rm);
if (was_on_sock)
// + if (was_on_sock && rm)
rds_message_put(rm);
}
据三哥说是通过一个代码扫描工具发现了这个问题并修复,如果是真的只能说这个工具写得比较烂。太基础的东西就不在解释了,这个问题最初几个评审者都没发现,所以大家也不要把底层开发者想象的多牛B,上班还有老板发工资,社区纯粹是用爱发电。即使当年在微软,牛B的首席工程师犯初级错误也不少见。
先来揣测一下将rm置为null的意图,其实也不算揣测本来意图就应该是这样。当调用kfree之后,rm就成了一个野指针,指向那里完全不确定,为了保证安全,将rm置为NULL,没毛病,有点经验的码农都这么干。这样在函数rds_send_remove_from_sock里,在调用rds_message_put后保证rm被释放并清空。在判断was_on_lock的时候如果rm是空指针就没必要再调用rds_message_put了。那么问题就来了:
- rm = NULL这句话完全无效!!!
- 如果rm真的被置为NULL,那么下面的if判断不用做了,rm为NULL,永远不会再次调用rds_message_put。
为什么kfree(rm)之后需要再调用rds_message_put我不懂,但是上面两点足以让社区大佬非常不爽了。
下面来解释一下为什么rm = NULL无效,这里就要复习一下指针了。C语言没有C++引用的概念,如果要在函数里修改一个变量的值,参数就必须传一个指针。如果这句话你看懂了,那么如果要要在函数里修改一个指针指向的地址,那么参数必须传指针的指针!!!此文面向学过C语言的读者,直接上例子代码。
#include <stdio.h>
#include <stdlib.h>
void free_pointer_1(void* p) {
if (p != NULL) {
free(p);
}
p = NULL;
}
void free_pointer_2(void** pp) {
if (*pp != NULL) {
free(*pp);
}
*pp = NULL;
}
void test_1(void) {
void *p = malloc(1024);
printf("test_01: before p = 0x%x\n", (unsigned int)p);
free_pointer_1(p);
printf("test_01: after p = 0x%0x\n", (unsigned int)p);
}
void test_2(void) {
void *p = malloc(1024);
printf("test_01: before p = 0x%x\n", (unsigned int)p);
free_pointer_2(&p);
printf("test_01: after p = 0x%0x\n", (unsigned int)p);
}
int main(void) {
test_1();
printf("\n----------\n\n");
test_2();
}
只能说三哥代码训练量不够,这个问题上大意了,态度又不端正。其它评审者也不够仔细,毕竟这种问题不会天天遇到,XX斗阵这跟弦没有做到时刻紧绷。
最后随便说说对整个事件的看法,没去看Reddit上的讨论,从B乎来看很多认为这属于钓鱼执法,浪费社区资源为自己论文积累素材,而且造成了社区的信任危机。同时还指责明大为这个项目豁免属于管理不严,感觉只要不是玩活人就剖都不算伦理问题。我个人认为这个研究是有意义的,但是操作细节上值得商榷,毕竟社区用爱发电,这么神出鬼没的搞很容易草木皆兵。说伦理问题的也是扯淡,真实世界的攻击者不会跟你讲伦理,这次实验也算是为社区敲响了警钟。虽说开源大家都能看到代码,但是大多数人就看个寂寞。