首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

内核相关问题:kill init和内核结构的释放

内核相关问题:kill init和内核结构的释放

如果在命令行执行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(&current->sighand->siglock);   

  • for (;;) {   
  • ...   

  • if (current->pid == 1)//由pid判断是否传送信号,执行到此,信号只可能是SIGKILL或STOP之类的巨猛信号了,如果是init进程那么忽略,不向它传送
       

  • continue;   
  • ...   
  •                  do_group_exit(signr);                 
  •          }   
  •          spin_unlock_irq(&current->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;   
  •          }   
  • }
继承事业,薪火相传
返回列表