- UID
- 1029342
- 性别
- 男
|
如果在命令行执行kill -9 1,那么结果是没有反应,连个提示都没有,实际上init进程是杀不死的,到底为何呢?kill指令实际上是发信号,如果一个进程对一个信号没有反应那么 原因可能有以下三点:1.该进程屏蔽了此信号;2.该进程是内核线程,手动屏蔽了此信号;3.内核忽略了此信号.我们看看init进程,它不是内核线程 (实际上在rest_init之初的init是内核线程,只是它马上exec到用户空间了),而且SIGKILL(9)是用户线程所不能忽略和屏蔽的,因 此只有第三种可能,内核忽略了此信号,找找代码,看看下面的函数就得到了确切的答案。
- int get_signal_to_deliver(siginfo_t *info, struct k_sigaction *return_ka, struct pt_regs *regs, void *cookie)
- {
- ...
- spin_lock_irq(¤t->sighand->siglock);
for (;;) { - ...
if (current->pid == 1)//由pid判断是否传送信号,执行到此,信号只可能是SIGKILL或STOP之类的巨猛信号了,如果是init进程那么忽略,不向它传送
continue; - ...
- do_group_exit(signr);
- }
- spin_unlock_irq(¤t->sighand->siglock);
return signr; - }
因此我们知道内核只是通过进程的pid来识别init进程的,如果我们找到init,然后修改它的pid,使之不再为1,是否就可以杀死init进程了呢?理论是这样,难道真的是这么简单吗?于是我写下以下的模块:
- #include <linux/init.h>
#include <linux/module.h>
static __init int test_init(void) - {
- task_t *task=find_task_by_pid(1); //找到1号进程的task_struct结构指针
- task->pid = 3314; //将init的pid改为一个没有使用的pid
- force_sig(SIGKILL,task); //然后杀死它,就是杀死init进程
return 0; - }
static __exit void test_exit(void) - {
return ; - }
- module_init(test_init);
- module_exit(test_exit);
- MODULE_LICENSE("Dual BSD/GPL");
- MODULE_AUTHOR("Zhaoya");
- MODULE_DESCRIPTION("kill init");
- MODULE_VERSION("Ver 0.1");
结果如何呢?当然是系统崩溃,linux临死前coredump,调用堆栈中有choose_new_parent函数,找到choose_new_parent函数看了一下:
- static inline void choose_new_parent(task_t *p, task_t *reaper, task_t *child_reaper)
- {
- BUG_ON(p == reaper || reaper->state >= TASK_ZOMBIE);
- p->real_parent = reaper;
if (p->parent == p->real_parent) //这里出错,p是init的孩子,p->parent当然是init,而reaper也是init,所以if为真,BUG乎!
- BUG();
- }
那里为何出错呢?实际上SIGKILL信号真的发给了init,然后init就do_exit了,当中调用了以下函数:参数的father就是init本身,该函数过继了init的孩子们给一个新的父亲。
- static inline void forget_original_parent(struct task_struct * father, struct list_head *to_release)
- {
struct task_struct *p, *reaper = father;
struct list_head *_p, *_n;
do { - reaper = next_thread(reaper);
if (reaper == father) { - reaper = child_reaper;//child_reaper在初始化时设置为init进程,于是到此为止father和reaper是一样的。
break; - }
- } while (reaper->state >= TASK_ZOMBIE);
- list_for_each_safe(_p, _n, &father->children) {
int ptrace; - p = list_entry(_p,struct task_struct,sibling);
- ptrace = p->ptrace;
- BUG_ON(father != p->real_parent && !ptrace);
if (father == p->real_parent) { - choose_new_parent(p, reaper, child_reaper);//到了此点。
- reparent_thread(p, father, 0);
- } else {
- ...
- }
因此init进程不可杀并不是用户空间的策 略,而是内核的机制,是操作系统的一部分,操作系统用这个机制实现了很多事情,比如僵尸进程管理回收问题,多用户安全问题等等,不要指望杀死init了, 即使通过rootkit做到了,试问有意义吗?就是个儿戏罢了,没有实用性的。
下面谈谈内核野指针问题,这其实是一个争论的话题,争论主题就是要将不用的指针清零还是采用懒惰策略,待下次使用的时候再清零。前者更安全,后者更高效, 两全不得其美,必选其一。如果说发生了内核错误,我的期望是马上出错马上崩溃,不然等到以后出错的时间就是不确定的了,那会很不安全,同样给调试带来困 难,安全性永远比性能重要。
我采用了一个很极端的例子,在一个进程执行的时候释放掉其task_struct,为了使事情简单,我的进程如下:
- int main()
- {
while(1){} - }
编译后运行,ps后它的pid是2732,然后写如下模块:
- #include <linux/init.h>
#include <linux/module.h>
static __init int test_init(void) - {
- task_t *task=find_task_by_pid(2732);
- free_task(task);
return 0; - }
static __exit void test_exit(void) - {
return ; - }
- module_init(test_init);
- module_exit(test_exit);
- MODULE_LICENSE("Dual BSD/GPL");
- MODULE_AUTHOR("Zhaoya");
- MODULE_DESCRIPTION("free task");
- MODULE_VERSION("Ver 0.1");
加 载模块后,只要不要动键盘,一点问题没有,程序依旧完好地运行,内核没有崩溃,但是一旦敲入ps,内核立马崩溃,让我们来看看这是为什么。简单起见,我用 2.6.9内核分析。free_task实际调用kmem_cache_free(task_struct_cachep, (task)),而后者就是:
- static inline void __cache_free (kmem_cache_t *cachep, void* objp)
- {
struct array_cache *ac = ac_data(cachep); - check_irq_off();
- objp = cache_free_debugcheck(cachep, objp, __builtin_return_address(0));
if (likely(ac->avail < ac->limit)) { - STATS_INC_FREEHIT(cachep);
- ac_entry(ac)[ac->avail++] = objp;
return; - } else {
- STATS_INC_FREEMISS(cachep);
- cache_flusharray(cachep, ac);
- ac_entry(ac)[ac->avail++] = objp;
- }
- }
|
|