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

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

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

: X% M7 b5 }0 G: E
  • 直接access用户态的内存而没有使用copy_to_user/copy_from_user!
    6 A4 [* [  f5 R& W, ?- }

( D9 @8 h! W( `' x2 |9 `
在内核看来,用户态提供的虚拟地址是不可信的,所以在一旦在内核态访问用户态内存发生缺页中断,处理起来是非常棘手的。
7 N( e; Q3 |+ h" U& w6 R# [9 B
Linux内核的做法是提供了一张 异常处理表 ,使用专有的函数来访问用户态内存。类似 try-catch块一般。具体详情可参见copy_to_user/copy_from_user的实现以及内核文档Documentation/x86/exception-tables.txt的描述。

( B$ [( b2 E( S
本来简单看下这个异常处理表能怎么玩。

8 T; e$ K0 k8 h! c2 }% Y+ R
首先,我们可以写一片代码,将内核的异常处理表dump下来:

  1. % Y$ f% v8 A- [* O4 J* L
  2. // show_extable.c4 ]- Q0 o4 K, }  m/ N; {
  3. #include <linux/module.h>0 X( |; @% m) h, V
  4. #include <linux/kallsyms.h>/ Z$ k8 T5 L+ z* {% K
  5. ) s  O; n  R: w1 ?1 F  e; Z6 [  V3 {
  6. int (*_lookup_symbol_name)(unsigned long, char *);( c: x# z$ }& n. ?, X* w$ Z
  7. unsigned long (*_get_symbol_pos)(unsigned long, void *, void *);7 W, A2 S2 t6 A+ ^6 s
  8. unsigned long start_ex, end_ex;- g2 O' h0 z( W9 p7 k

  9. ! P& K9 K, L- x" V# A
  10. int init_module(void)4 z; Y" c; W6 _; C
  11. {
    3 i$ G/ Q# m, F+ z. U- E' c+ u
  12.   unsigned long i;
    + q& j) M: u7 g$ N6 j
  13.   unsigned long orig, fixup, originsn, fixinsn, offset, size;
    : B# `/ I! p7 r6 m, a5 P
  14.   char name[128], fixname[128];
    1 h8 L) W) q8 \! j% o' O
  15. 1 t0 I& Q! V: j; E/ M+ I& K
  16.   _lookup_symbol_name = (void *)kallsyms_lookup_name("lookup_symbol_name");
    # w3 e" p, c- C( L$ O. I! R
  17.   _get_symbol_pos = (void *)kallsyms_lookup_name("get_symbol_pos");
    6 I$ i" v1 H: X5 F
  18.   start_ex = (unsigned long)kallsyms_lookup_name("__start___ex_table");' }) v" y4 Q2 z8 ~
  19.   end_ex = (unsigned long)kallsyms_lookup_name("__stop___ex_table");
    : l1 l, b* {- C. q
  20. # M, }6 ~) k3 S( e9 g% ~' _
  21.   // 按照exception_table_entry的sizeof从start遍历到end。
    4 t+ u8 x$ d- k* n0 \1 `
  22.   for(i = start_ex; i < end_ex; i += 2*sizeof(unsigned long)) {
    , x, X, G$ @- y$ z1 @: U9 u
  23.     orig = i; // 取出exception_table_entry的insn字段地址。
    5 Z# X" f. Y  ]$ B
  24.     fixup = i + sizeof(unsigned int); // 取出fixup字段地址。# j7 q) I. s  Z2 S% u, h2 l- s/ R3 |
  25. % @7 q: W2 B" k& |3 c
  26.     originsn = orig + *(unsigned int *)orig; // 根据相对偏移字段求出绝对地址
    8 V0 V8 u/ L& U
  27.     originsn |= 0xffffffff00000000;2 P' ^  H) N% }- n) Z  x
  28.     fixinsn = fixup + *(unsigned int *)fixup;
    ) ~1 ]1 c8 N' D$ w! _
  29.     fixinsn |= 0xffffffff00000000;
    6 D( S: M0 J! [) s+ {0 R
  30.     _get_symbol_pos(originsn, &size, &offset);/ Z! Z4 U3 p- U5 o: C- T
  31.     _lookup_symbol_name(originsn, name);/ @1 m8 ^. w4 {% ?. V
  32.     _lookup_symbol_name(fixinsn, fixname);, f5 ?) G5 a9 z& ~
  33.     printk("[%lx]%s+0x%lx/0x%lx [%lx]%s\n",
    ( p; s7 i9 U8 R' B
  34.         originsn,
    ) x' u% s( ]$ |
  35.         name,9 g) e; e  r. R" Y+ {
  36.         offset,7 T* f9 q& t2 ^) a' T2 ^4 a
  37.         size,
    " O7 r/ ^* l% Q1 y1 i! l' ~, J/ g1 B$ `
  38.         fixinsn,& L! |. E: |: q  @0 N6 U
  39.         fixname);- M7 L- I5 ]8 H" Y  H
  40.   }  m  l' N' a6 E5 }" y# d5 v

  41. ! h, O4 z9 R1 }' c3 J. m( ?4 t
  42.   return -1;* Z" E6 @: i: E+ p- j. C5 H
  43. }
    , Y& |7 c$ E* y: `" L" T& M" n
  44. MODULE_LICENSE("GPL");
    ! @' M* q( p1 j5 B' X$ U; R
复制代码

+ A0 x$ \* u: S# ?; |# t
我们看下输出:

  1. 6 o& D$ J* V& d' |' }. Z6 r
  2. # ___sys_recvmsg+0x253位置发生异常,跳转到ffffffff81649396处理异常。, @+ Q% L9 y8 a" P% a, N' E
  3. [ 7655.267616] [ffffffff8150d7a3]___sys_recvmsg+0x253/0x2b0 [ffffffff81649396]bad_to_user' A+ t) Z0 ^) B
  4. ...
    % _0 f1 u  T7 I; r: N3 c
  5. # create_elf_tables+0x3cf位置处如果发生异常,跳转到ffffffff81648a07地址执行异常处理。; b5 q/ q8 U+ Y# j/ u5 E, e
  6. [ 7655.267727] [ffffffff8163250e]create_elf_tables+0x3cf/0x509 [ffffffff81648a1b]bad_gs
    6 X9 _- `: K, R& _6 G- }6 l
复制代码
0 g6 N) L' c+ Y% |
" m; K* s1 B5 o' |4 p! M* ~
一般而言,类似bad_to_user,bad_from_user之类的异常处理函数都是直接返回用户一个错误码,比如Bad address之类,并不是直接用户程序直接段错误,这一点和用户态访问非法地址直接发送SIGSEGV有所不同。比如:
  1. <font color="#001000"><font style="background-color:rgb(255, 255, 255)"><font face="Tahoma">
    ! e! V8 A4 N# T0 t( m
  2. #include <fcntl.h>" u* u' W* B2 J. {
  3. int main(int argc, char **argv)
    + T$ ]8 d+ W( ~' L: o' o
  4. {
    4 @0 _/ b) ]7 W) S8 V4 _
  5.   int fd;+ ~7 R0 I6 B  r5 D: u
  6.   int ret;& o- w" J! g, e$ e/ c6 i* A/ L
  7.   char *buf = (char *)0x56; // 显然是一个非法地址。
    ' g) N8 |- J7 u) R/ L0 W

  8. * `) Q+ \, q+ c8 D/ i6 j- i3 `" V
  9.   fd = open("/proc/sys/net/nf_conntrack_max", O_RDWR | O_CREAT, S_IRWXU);
    6 t4 ]3 `$ ]3 s" R( D
  10.   perror("open");
    4 u3 F5 Q8 ~5 H+ R' @9 d
  11.   ret = read(fd, buf, 100);
    5 i1 O4 X9 T' H6 Q& a/ R' v$ o
  12.   perror("read");+ s" J/ j1 D- U* y9 n$ u6 u$ ?
  13. }
    : b% M* V7 J; v' R) s
  14. </font></font></font>5 [6 B% S$ v$ H, v3 J
复制代码

5 ]" y5 o; ~7 f1 c' y5 O2 Z

执行之:


  1. + h6 j% Q) x( @  g2 i
  2. [root@localhost test]# ./a.out5 U6 S, O( h8 `. t) s6 Z. u
  3. open: Success' [6 l. ~0 p' f
  4. read: Bad address # 没有段错误,只是一个普通错误。
    $ ~( j; `! {5 \# d+ y
复制代码

! H7 r2 X6 u* d# q

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

  1. 2 e9 d, I/ {4 Z
  2. // fix_ex.c" |3 G6 g1 Q: q! w' a
  3. #include <linux/module.h>: M$ D# U- D' w, r) d8 W/ l
  4. #include <linux/sched.h>& Q: w6 t0 F0 N
  5. #include <linux/kallsyms.h>; {$ O7 R6 v6 P
  6.   p! [( T+ V& V3 ?
  7. int (*_lookup_symbol_name)(unsigned long, char *);
    9 F% s" L5 x$ _* D
  8. unsigned long (*_get_symbol_pos)(unsigned long, void *, void *);
    ) v3 U2 B4 G( Q  o: G4 ]! t
  9. unsigned long start_ex, end_ex;
    2 {6 C, V: }( I( q( F) }- `* }/ G
  10. void *_bad_from_user, *_bad_to_user;
    0 J: S4 P, a9 t* `% ?
  11. 8 Y$ G6 i$ }+ U- z! G
  12. void kill_user_from(void)1 k) E3 R+ G6 Q; g6 Z& n
  13. {
    " A; t  C9 Q" B8 q1 s
  14.   printk("经理!rush tighten beat electric discourse!\n");
    2 j( y" \5 A3 m/ h; ^
  15.   force_sig(SIGSEGV, current);) X4 R9 v+ d* c9 r7 i5 n
  16. }$ C9 ^' Z' T, \8 \* N5 p8 \+ @- N3 q0 R

  17. 8 D' s- @# j: v% _0 u' y1 v3 W
  18. void kill_user_to(void)
    " M( b* P8 j8 C1 W+ B, Y- S
  19. {8 R" W* g* j8 Z0 x* T. J
  20.   printk("经理!rush tighten beat electric discourse! SB 皮鞋\n");) E, i- o) k$ U
  21.   force_sig(SIGSEGV, current);
    ) [& c* D1 n! N) I( v& k, B: O
  22. }
    ; G( V1 o, W$ p* l9 P5 z0 A
  23. ( Q/ {0 }# c4 I# L  @, f8 L
  24. unsigned int old, new;. `, t  B, D& P/ T9 n

  25. ( o. y) `7 Z) m3 z
  26. int (*_lookup_symbol_name)(unsigned long, char *);
    - V5 R2 P3 W- R# A, m  e7 b
  27. unsigned long (*_get_symbol_pos)(unsigned long, void *, void *);
    5 ^6 L. ]/ Q; M9 L. R9 U

  28. 3 n, f3 R2 M/ r: O' h  J
  29. int hook_fixup(void *origfunc1, void *origfunc2, void *newfunc1, void *newfunc2)
      R: |: W% \$ L5 y1 S
  30. {
      I' C! F' g) W( Z
  31.   unsigned long i;% S8 l( ^: [, M" ~- r1 M0 L# D: H
  32.   unsigned long fixup, fixinsn;# k- F# U" {2 T) _" @1 ~
  33.   char fixname[128];8 d7 x& ~( q3 z% ^$ I+ u" \9 ]

  34. + `' ~8 h& U7 j( Z5 k6 Y* D
  35. ; J* v% J  V; q6 Y
  36.   for(i = start_ex; i < end_ex; i += 2*sizeof(unsigned long)) {
    7 z% I2 j5 D+ \/ g6 |4 R+ e
  37.     fixup = i + sizeof(unsigned int);
    7 p. H' X- V* o: {, |7 d" G  s  y5 x
  38.     fixinsn = fixup + *(unsigned int *)fixup;
    6 F  p' S7 q* A# y9 h& ?  o
  39.     fixinsn |= 0xffffffff00000000;
    & o3 ?' a8 l7 U
  40.     _lookup_symbol_name(fixinsn, fixname);3 P5 t  |' a1 s& ~
  41.     if (!strcmp(fixname, origfunc1) ||$ B7 }8 t( D" l$ R1 R0 f
  42.       !strcmp(fixname, origfunc2)) {5 u4 p: E2 p/ r" f- _" k  I
  43.       unsigned long new;
    2 R" w9 O! j" u. R
  44.       unsigned int newfix;* G; E, ^4 P) |+ S. u

  45. / L4 I: f8 Y& `1 P- ]
  46.       if (!strcmp(fixname, origfunc1)) {: U8 J; w& X% o% f7 h0 U5 u( |( W
  47.         new = (unsigned long)newfunc1;1 _* I9 z6 m$ d$ a
  48.       } else {
    7 V- u* \. o% s' |6 z
  49.         new = (unsigned long)newfunc2;3 R  b) s  O, C1 _. q4 |0 C
  50.       }. t( ?$ d# m+ z0 }- W
  51.       new -= fixup;
    4 d6 F: }4 `% t2 }0 y+ z  M
  52.       newfix = (unsigned int)new;5 ]# [1 t+ N4 b& H
  53.       *(unsigned int *)fixup = newfix;# M" F! \( Y* \" m1 n4 G% q. Y" @+ `
  54.     }# M" u7 q+ L! n& @2 C9 [* D6 g" C! l
  55.   }8 X) h" i( v6 q' j; E  S$ Z
  56. 5 Q. z1 U- l* K3 @" W5 f" U+ ~
  57.   return 0;
    : ], A+ g5 Y( ^' _6 d% _3 G* @  t+ {
  58. }
    4 L  A3 @6 O* U* U7 t: B2 h
  59. 1 T: m0 N! O( h4 _6 H, y+ i! ^: \4 i
  60. int init_module(void)
    - {. R' f  Y# C# N) J* X
  61. {. Z8 C! Z. n- O% ?  Z* r
  62.   _lookup_symbol_name = (void *)kallsyms_lookup_name("lookup_symbol_name");
    5 y, i/ [( q0 X2 R. J  F
  63.   _get_symbol_pos = (void *)kallsyms_lookup_name("get_symbol_pos");$ c4 U& B2 V3 |; a
  64.   _bad_from_user = (void *)kallsyms_lookup_name("bad_from_user");
    ' ]( w; z. \7 v' z: {- E5 j  Q
  65.   _bad_to_user = (void *)kallsyms_lookup_name("bad_to_user");
    , V9 V7 x: }5 y
  66.   start_ex = (unsigned long)kallsyms_lookup_name("__start___ex_table");
    . X% x6 L! p) A) P9 e, m9 v0 R4 Z
  67.   end_ex = (unsigned long)kallsyms_lookup_name("__stop___ex_table");& O) I9 g2 d. y; S- v* O
  68. $ H6 S, j" a$ r2 H5 {! V+ x* N
  69.   hook_fixup("bad_from_user", "bad_to_user", kill_user_from, kill_user_to);
    ; Y4 K* u% L2 a# D5 |1 U
  70.   return 0;
    1 O# m& F/ f% j+ z7 O) j
  71. }. K  ^: P' O1 @$ K1 E" b2 j
  72. void cleanup_module(void)
    " g" ~6 y1 `% D9 f$ a, f
  73. {
    + i% r+ ^& U7 D3 Z( F+ [7 c. i: c
  74.   hook_fixup("kill_user_from", "kill_user_to", _bad_from_user, _bad_to_user);! f- d/ {) R+ y5 j
  75. }2 h$ n% p9 B( z( I1 O; Q5 q& ^( n& n: w
  76. ; f2 U  i# P% k- v4 h
  77. MODULE_LICENSE("GPL");# L9 Z0 j. _" ?$ Y
复制代码
, i  Q5 o* X) t0 t0 y: f2 X

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

  1. ) |8 D. P' W1 {: n( C
  2. [root@localhost test]# insmod ./fix_ex.ko
    3 }% ?0 D7 j1 }. P
  3. [root@localhost test]# ./a.out. [: h4 [! `. x/ \0 s7 _; }
  4. open: Success
    1 x: o8 N; ~; Q2 }% w7 F3 O6 Z: q4 U
  5. 段错误- p) u0 {" N  N- {2 \
  6. [root@localhost test]# dmesg
    1 N+ y7 a9 W8 l; F
  7. [ 8686.091738] 经理!rush tighten beat electric discourse! SB 皮鞋6 S+ M- {- r) }8 _* [- }4 C
  8. [root@localhost test]#
    - [( O+ |# s4 H. [& G+ d
复制代码


9 j( Y1 x) j; B. |2 U


' G0 c" `$ I! N8 K% h  ^: O
发生了段错误,并且打印出了让经理赶紧打电话的句子。

% s' ^: [' t: G  R
其实,我的目的并不是这样的,我真正的意思是,Linux的异常处理链表,又是一个藏污纳垢的好地方,我们可以在上面的hook函数中藏一些代码,比如说inline hook之类的,然后呢?然后静悄悄地等待用户态进程的bug导致异常处理被执行。将代码注入的时间线拉长,从而更难让运维和经理注意到。
9 U8 T) [6 t+ e+ R. c1 E
让代码注入的时间点和模块插入的时间点分开,让事情更加混乱。不过,注意好隐藏模块或者oneshot哦。

( d; |% ~) g$ C
浙江温州皮鞋湿,下雨进水不会胖。

) G# a5 K7 L9 k# J. @
赞 收藏 评论1 发布时间:2020-6-3 21:27

举报

1个回答
李康1202 回答时间:2020-6-4 08:58:31
谢谢分享
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版