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

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个回答
likang1202 回答时间:2020-6-4 08:58:31
谢谢分享

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

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