当超群发来的问题,一个低端内存耗尽导致系统panic的问题,通过写一个死循环循环调用同一个脚本,然后盯着/proc/slabinfo看,就会发现size-32类型的slab火速增加。通过简单直接有效古老的排除法(感谢每日持续集成编译),确定是内核的问题,老的内核没问题,新内核有问题,本来直接上大神器,kmemcheck,奈何看了一眼内核,版本太低,不支持。

只能用土法炼钢了,挨着排查新内核增加的补丁,直接奔着有内存分配的修改去了,终于发现了一个可疑的补丁,对应社区补丁exec-do-not-leave-bprm-interp-on-stack.patch,中有分配内存:

kstrdup通过__kmalloc分配了内存,调用者有load_misc_binary和load_script,两个调用者一个是执行二进制进行加载的函数,另外一个是脚本的处理方式(内核对普通二进制和脚本进行了区分),后面的代码均以load_misc_binary为例。

首先还是要先看一下这个补丁是干啥用的,未打补丁前是

load_misc_binary给了bprm下的interp一个数组作为申请空间,但实际上写补丁的人忽略了一点,iname数组随着函数的结束就是将内存空间释放了,而bprm还在,它的interp指向了一个已释放的空间。这就存在的安全隐患,任何执行程序都可能访问内核隐私信息(已释放后的重新被其他进程分配的内存信息),所以使用了bprm_change_interp重新为interp分配内存。

下面就要看一下它在哪里释放了这块申请的内存,先顺一下程序执行的过程。

do_execve调用do_execve_common,do_execve_common在此处初始化了bprm

最后发现bprm也是通过free_bprm(bprm)释放的

根本没有对bprm下的指针动态申请的空间做处理,翻看补丁,也是一样没有提供(我司专有补丁和社区链接不一致,由SUSE专门提供给低版本内核,前面链接的补丁是有处理的)。原因就基本清楚了,bprm结构体下申请空间的指针没有释放处理,程序每执行一次就会申请一块内存不释放,所以重现的时候死循环调用空脚本,就会使明显复现。

中间的一些过程也贴一下,do_execve_common查找程序处理对象search_binary_handler:

search_binary_handler通过linux_binfmt的load_binary处理不同程序,fmt初始化在

load_binary被定义成load_misc_binary,如此整个过程就串起来了,load_script的处理过程是基本相同的,可以自行看代码。


从一次内存泄露看程序在内核中的执行过程来自于OenHan

链接为:http://oenhan.com/kernel-program-exec

发表评论