你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

Linux内核态缺页会发生什么?

[复制链接]
gaosmile 发布时间:2020-6-3 21:27
近日,我在写内核模块的时候犯了一个低级错误:

  • 直接access用户态的内存而没有使用copy_to_user/copy_from_user!


在内核看来,用户态提供的虚拟地址是不可信的,所以在一旦在内核态访问用户态内存发生缺页中断,处理起来是非常棘手的。

Linux内核的做法是提供了一张 异常处理表 ,使用专有的函数来访问用户态内存。类似 try-catch块一般。具体详情可参见copy_to_user/copy_from_user的实现以及内核文档Documentation/x86/exception-tables.txt的描述。

本来简单看下这个异常处理表能怎么玩。

首先,我们可以写一片代码,将内核的异常处理表dump下来:

  1. // show_extable.c
  2. #include <linux/module.h>
  3. #include <linux/kallsyms.h>

  4. int (*_lookup_symbol_name)(unsigned long, char *);
  5. unsigned long (*_get_symbol_pos)(unsigned long, void *, void *);
  6. unsigned long start_ex, end_ex;

  7. int init_module(void)
  8. {
  9.   unsigned long i;
  10.   unsigned long orig, fixup, originsn, fixinsn, offset, size;
  11.   char name[128], fixname[128];

  12.   _lookup_symbol_name = (void *)kallsyms_lookup_name("lookup_symbol_name");
  13.   _get_symbol_pos = (void *)kallsyms_lookup_name("get_symbol_pos");
  14.   start_ex = (unsigned long)kallsyms_lookup_name("__start___ex_table");
  15.   end_ex = (unsigned long)kallsyms_lookup_name("__stop___ex_table");

  16.   // 按照exception_table_entry的sizeof从start遍历到end。
  17.   for(i = start_ex; i < end_ex; i += 2*sizeof(unsigned long)) {
  18.     orig = i; // 取出exception_table_entry的insn字段地址。
  19.     fixup = i + sizeof(unsigned int); // 取出fixup字段地址。

  20.     originsn = orig + *(unsigned int *)orig; // 根据相对偏移字段求出绝对地址
  21.     originsn |= 0xffffffff00000000;
  22.     fixinsn = fixup + *(unsigned int *)fixup;
  23.     fixinsn |= 0xffffffff00000000;
  24.     _get_symbol_pos(originsn, &size, &offset);
  25.     _lookup_symbol_name(originsn, name);
  26.     _lookup_symbol_name(fixinsn, fixname);
  27.     printk("[%lx]%s+0x%lx/0x%lx [%lx]%s\n",
  28.         originsn,
  29.         name,
  30.         offset,
  31.         size,
  32.         fixinsn,
  33.         fixname);
  34.   }

  35.   return -1;
  36. }
  37. MODULE_LICENSE("GPL");
复制代码

我们看下输出:

  1. # ___sys_recvmsg+0x253位置发生异常,跳转到ffffffff81649396处理异常。
  2. [ 7655.267616] [ffffffff8150d7a3]___sys_recvmsg+0x253/0x2b0 [ffffffff81649396]bad_to_user
  3. ...
  4. # create_elf_tables+0x3cf位置处如果发生异常,跳转到ffffffff81648a07地址执行异常处理。
  5. [ 7655.267727] [ffffffff8163250e]create_elf_tables+0x3cf/0x509 [ffffffff81648a1b]bad_gs
复制代码


一般而言,类似bad_to_user,bad_from_user之类的异常处理函数都是直接返回用户一个错误码,比如Bad address之类,并不是直接用户程序直接段错误,这一点和用户态访问非法地址直接发送SIGSEGV有所不同。比如:
  1. <font color="#001000"><font style="background-color:rgb(255, 255, 255)"><font face="Tahoma">
  2. #include <fcntl.h>
  3. int main(int argc, char **argv)
  4. {
  5.   int fd;
  6.   int ret;
  7.   char *buf = (char *)0x56; // 显然是一个非法地址。

  8.   fd = open("/proc/sys/net/nf_conntrack_max", O_RDWR | O_CREAT, S_IRWXU);
  9.   perror("open");
  10.   ret = read(fd, buf, 100);
  11.   perror("read");
  12. }
  13. </font></font></font>
复制代码

执行之:


  1. [root@localhost test]# ./a.out
  2. open: Success
  3. read: Bad address # 没有段错误,只是一个普通错误。
复制代码


我们能不能将其行为修改成和用户态访问非法地址一致呢?简单,替换掉bad_to_user即可,代码如下:


  1. // fix_ex.c
  2. #include <linux/module.h>
  3. #include <linux/sched.h>
  4. #include <linux/kallsyms.h>

  5. int (*_lookup_symbol_name)(unsigned long, char *);
  6. unsigned long (*_get_symbol_pos)(unsigned long, void *, void *);
  7. unsigned long start_ex, end_ex;
  8. void *_bad_from_user, *_bad_to_user;

  9. void kill_user_from(void)
  10. {
  11.   printk("经理!rush tighten beat electric discourse!\n");
  12.   force_sig(SIGSEGV, current);
  13. }

  14. void kill_user_to(void)
  15. {
  16.   printk("经理!rush tighten beat electric discourse! SB 皮鞋\n");
  17.   force_sig(SIGSEGV, current);
  18. }

  19. unsigned int old, new;

  20. int (*_lookup_symbol_name)(unsigned long, char *);
  21. unsigned long (*_get_symbol_pos)(unsigned long, void *, void *);

  22. int hook_fixup(void *origfunc1, void *origfunc2, void *newfunc1, void *newfunc2)
  23. {
  24.   unsigned long i;
  25.   unsigned long fixup, fixinsn;
  26.   char fixname[128];


  27.   for(i = start_ex; i < end_ex; i += 2*sizeof(unsigned long)) {
  28.     fixup = i + sizeof(unsigned int);
  29.     fixinsn = fixup + *(unsigned int *)fixup;
  30.     fixinsn |= 0xffffffff00000000;
  31.     _lookup_symbol_name(fixinsn, fixname);
  32.     if (!strcmp(fixname, origfunc1) ||
  33.       !strcmp(fixname, origfunc2)) {
  34.       unsigned long new;
  35.       unsigned int newfix;

  36.       if (!strcmp(fixname, origfunc1)) {
  37.         new = (unsigned long)newfunc1;
  38.       } else {
  39.         new = (unsigned long)newfunc2;
  40.       }
  41.       new -= fixup;
  42.       newfix = (unsigned int)new;
  43.       *(unsigned int *)fixup = newfix;
  44.     }
  45.   }

  46.   return 0;
  47. }

  48. int init_module(void)
  49. {
  50.   _lookup_symbol_name = (void *)kallsyms_lookup_name("lookup_symbol_name");
  51.   _get_symbol_pos = (void *)kallsyms_lookup_name("get_symbol_pos");
  52.   _bad_from_user = (void *)kallsyms_lookup_name("bad_from_user");
  53.   _bad_to_user = (void *)kallsyms_lookup_name("bad_to_user");
  54.   start_ex = (unsigned long)kallsyms_lookup_name("__start___ex_table");
  55.   end_ex = (unsigned long)kallsyms_lookup_name("__stop___ex_table");

  56.   hook_fixup("bad_from_user", "bad_to_user", kill_user_from, kill_user_to);
  57.   return 0;
  58. }
  59. void cleanup_module(void)
  60. {
  61.   hook_fixup("kill_user_from", "kill_user_to", _bad_from_user, _bad_to_user);
  62. }

  63. MODULE_LICENSE("GPL");
复制代码

编译,加载,重新执行我们的a.out:


  1. [root@localhost test]# insmod ./fix_ex.ko
  2. [root@localhost test]# ./a.out
  3. open: Success
  4. 段错误
  5. [root@localhost test]# dmesg
  6. [ 8686.091738] 经理!rush tighten beat electric discourse! SB 皮鞋
  7. [root@localhost test]#
复制代码



发生了段错误,并且打印出了让经理赶紧打电话的句子。

其实,我的目的并不是这样的,我真正的意思是,Linux的异常处理链表,又是一个藏污纳垢的好地方,我们可以在上面的hook函数中藏一些代码,比如说inline hook之类的,然后呢?然后静悄悄地等待用户态进程的bug导致异常处理被执行。将代码注入的时间线拉长,从而更难让运维和经理注意到。

让代码注入的时间点和模块插入的时间点分开,让事情更加混乱。不过,注意好隐藏模块或者oneshot哦。

浙江温州皮鞋湿,下雨进水不会胖。

收藏 评论1 发布时间:2020-6-3 21:27

举报

1个回答
李康1202 回答时间:2020-6-4 08:58:31
谢谢分享

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版