这是2002年FreeBSD系统的一个安全漏洞。

当时一些开发人员意识到getpeername库的某个实现可能会隐藏致命的安全漏洞。

下面的代码简单的还原了漏洞的发生过程。

1 /*
2 * Illustration of code vulnerability similar to that found in
3 * FreeBSD’s implementation of getpeername()
4 */
5
6 /* Declaration of library function memcpy */
7 void *memcpy(void *dest, void *src, size_t n);
8
9 /* Kernel memory region holding user-accessible data */
10 #define KSIZE 1024
11 char kbuf[KSIZE];
12
13 /* Copy at most maxlen bytes from kernel region to user buffer */
14 int copy_from_kernel(void *user_dest, int maxlen) {
15    /* Byte count len is minimum of buffer size and maxlen */
16    int len = KSIZE < maxlen ? KSIZE : maxlen;
17    memcpy(user_dest, kbuf, len);
18    return len;
19 }

int copy_from_kernel(void *user_dest, int maxlen)方法的作用是将系统内核中的大小为maxlen的内存拷贝到用户内存里。在系统运行时,用户进程是没有权限访问系统内核的内存的,该方法等于是为用户进程开了个小后门,使得用户进程可以访问最大KSIZE大小的系统内核内存。由于系统内核内存中有很多运行时的敏感信息,所以该方法做了最大KSIZE大小内存的的限制,这是有道理的。但如果用户访问了任意大小的内核内存,那么系统敏感信息将会暴露,这将为不怀好意的攻击者提供足够多的入侵信息。

我们可以看到,第7行的memcpy方法会将n大小的内存从src拷贝到dest中,这里要注意的是n的类型是size_t,而size_t实际上就是unsigned,在stdio.h中有相应的typedef定义。

问题14行的maxlen参数,该参数的类型是int。在16行里,逻辑约定如果拷贝的内存大小过大,则最多拷贝KSIZE大小的内存,否则拷贝maxlen大小的内存。这个内存的最终大小是存放在len变量里的,len是int型,也就是说len是有符号的。

好了,下面是见证安全漏洞的时刻。

假设攻击者调用了copy_from_kernel方法,其中maxlen设为负数,那么根据第16行的逻辑,len变量应该也是负数,也就是有符号的负整数。

在17行的memcpy方法调用中,len是负数,然而根据函数原型,第3个参数应该是unsigned,无符号整数,根据整数的二进制表示法,负数的二进制表示正好就是unsigned的大整数表示方式,这个大家可以去看看操作系统的原理补充一下相关的上下文知识。于是结果就是,攻击者通过传入负数调用copy_from_kernel方法,可以访问巨大长度的系统内核内存,从而获取系统运行时的敏感信息,达到入侵系统的目的。

从测试的角度上总结一下,这是因为没有做等价类的测试用例导致的。这里maxlen既然是int,那么根据等价类,我们可以测试任意正整数,负整数和0,这3个等价类。

从开发的角度上看,这是因为参数类型不匹配导致的,解决方式就是将代码里所有的int类型改成unsigned就可以了,类型匹配之后,就可能消除由于类型转换而带来的各种缺陷隐患。

这就是一个深藏不露的安全性问题,不知道大家是不是可以想明白这个产生这个问题的原理呢?如果让你对这段代码进行测试,你可以发现这个安全漏洞么?