Blame view

Documentation/x86/exception-tables.txt 11.7 KB
3697cd9aa   Amerigo Wang   Doc: update Docum...
1
       Kernel level exception handling in Linux
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
2
    Commentary by Joerg Pommnitz <joerg@raleigh.ibm.com>
3697cd9aa   Amerigo Wang   Doc: update Docum...
3
4
  When a process runs in kernel mode, it often has to access user
  mode memory whose address has been passed by an untrusted program.
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
5
  To protect itself the kernel has to verify this address.
3697cd9aa   Amerigo Wang   Doc: update Docum...
6
7
  In older versions of Linux this was done with the
  int verify_area(int type, const void * addr, unsigned long size)
720a84591   Jesper Juhl   [PATCH] remove ve...
8
  function (which has since been replaced by access_ok()).
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
9

3697cd9aa   Amerigo Wang   Doc: update Docum...
10
  This function verified that the memory area starting at address
670e9f34e   Paolo Ornati   Documentation: re...
11
  'addr' and of size 'size' was accessible for the operation specified
3697cd9aa   Amerigo Wang   Doc: update Docum...
12
13
14
  in type (read or write). To do this, verify_read had to look up the
  virtual memory area (vma) that contained the address addr. In the
  normal case (correctly working program), this test was successful.
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
15
16
17
  It only failed for a few buggy programs. In some kernel profiling
  tests, this normally unneeded verification used up a considerable
  amount of time.
3697cd9aa   Amerigo Wang   Doc: update Docum...
18
  To overcome this situation, Linus decided to let the virtual memory
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
19
20
21
  hardware present in every Linux-capable CPU handle this test.
  
  How does this work?
3697cd9aa   Amerigo Wang   Doc: update Docum...
22
23
24
  Whenever the kernel tries to access an address that is currently not
  accessible, the CPU generates a page fault exception and calls the
  page fault handler
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
25
26
  
  void do_page_fault(struct pt_regs *regs, unsigned long error_code)
3697cd9aa   Amerigo Wang   Doc: update Docum...
27
28
29
  in arch/x86/mm/fault.c. The parameters on the stack are set up by
  the low level assembly glue in arch/x86/kernel/entry_32.S. The parameter
  regs is a pointer to the saved registers on the stack, error_code
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
30
  contains a reason code for the exception.
3697cd9aa   Amerigo Wang   Doc: update Docum...
31
32
33
34
35
36
37
38
39
40
41
42
  do_page_fault first obtains the unaccessible address from the CPU
  control register CR2. If the address is within the virtual address
  space of the process, the fault probably occurred, because the page
  was not swapped in, write protected or something similar. However,
  we are interested in the other case: the address is not valid, there
  is no vma that contains this address. In this case, the kernel jumps
  to the bad_area label.
  
  There it uses the address of the instruction that caused the exception
  (i.e. regs->eip) to find an address where the execution can continue
  (fixup). If this search is successful, the fault handler modifies the
  return address (again regs->eip) and returns. The execution will
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
43
44
45
  continue at the address in fixup.
  
  Where does fixup point to?
3697cd9aa   Amerigo Wang   Doc: update Docum...
46
47
48
49
  Since we jump to the contents of fixup, fixup obviously points
  to executable code. This code is hidden inside the user access macros.
  I have picked the get_user macro defined in arch/x86/include/asm/uaccess.h
  as an example. The definition is somewhat hard to follow, so let's peek at
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
50
  the code generated by the preprocessor and the compiler. I selected
3697cd9aa   Amerigo Wang   Doc: update Docum...
51
  the get_user call in drivers/char/sysrq.c for a detailed examination.
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
52

3697cd9aa   Amerigo Wang   Doc: update Docum...
53
  The original code in sysrq.c line 587:
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
54
55
56
57
58
          get_user(c, buf);
  
  The preprocessor output (edited to become somewhat readable):
  
  (
3697cd9aa   Amerigo Wang   Doc: update Docum...
59
60
61
62
63
64
    {
      long __gu_err = - 14 , __gu_val = 0;
      const __typeof__(*( (  buf ) )) *__gu_addr = ((buf));
      if (((((0 + current_set[0])->tss.segment) == 0x18 )  ||
         (((sizeof(*(buf))) <= 0xC0000000UL) &&
         ((unsigned long)(__gu_addr ) <= 0xC0000000UL - (sizeof(*(buf)))))))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
65
        do {
3697cd9aa   Amerigo Wang   Doc: update Docum...
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
          __gu_err  = 0;
          switch ((sizeof(*(buf)))) {
            case 1:
              __asm__ __volatile__(
                "1:      mov" "b" " %2,%" "b" "1
  "
                "2:
  "
                ".section .fixup,\"ax\"
  "
                "3:      movl %3,%0
  "
                "        xor" "b" " %" "b" "1,%" "b" "1
  "
                "        jmp 2b
  "
                ".section __ex_table,\"a\"
  "
                "        .align 4
  "
                "        .long 1b,3b
  "
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
88
                ".text"        : "=r"(__gu_err), "=q" (__gu_val): "m"((*(struct __large_struct *)
3697cd9aa   Amerigo Wang   Doc: update Docum...
89
90
91
                              (   __gu_addr   )) ), "i"(- 14 ), "0"(  __gu_err  )) ;
                break;
            case 2:
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
92
              __asm__ __volatile__(
3697cd9aa   Amerigo Wang   Doc: update Docum...
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
                "1:      mov" "w" " %2,%" "w" "1
  "
                "2:
  "
                ".section .fixup,\"ax\"
  "
                "3:      movl %3,%0
  "
                "        xor" "w" " %" "w" "1,%" "w" "1
  "
                "        jmp 2b
  "
                ".section __ex_table,\"a\"
  "
                "        .align 4
  "
                "        .long 1b,3b
  "
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
111
                ".text"        : "=r"(__gu_err), "=r" (__gu_val) : "m"((*(struct __large_struct *)
3697cd9aa   Amerigo Wang   Doc: update Docum...
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
                              (   __gu_addr   )) ), "i"(- 14 ), "0"(  __gu_err  ));
                break;
            case 4:
              __asm__ __volatile__(
                "1:      mov" "l" " %2,%" "" "1
  "
                "2:
  "
                ".section .fixup,\"ax\"
  "
                "3:      movl %3,%0
  "
                "        xor" "l" " %" "" "1,%" "" "1
  "
                "        jmp 2b
  "
                ".section __ex_table,\"a\"
  "
                "        .align 4
  "        "        .long 1b,3b
  "
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
133
                ".text"        : "=r"(__gu_err), "=r" (__gu_val) : "m"((*(struct __large_struct *)
3697cd9aa   Amerigo Wang   Doc: update Docum...
134
135
136
137
138
139
140
                              (   __gu_addr   )) ), "i"(- 14 ), "0"(__gu_err));
                break;
            default:
              (__gu_val) = __get_user_bad();
          }
        } while (0) ;
      ((c)) = (__typeof__(*((buf))))__gu_val;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
141
142
143
144
145
146
147
148
149
      __gu_err;
    }
  );
  
  WOW! Black GCC/assembly magic. This is impossible to follow, so let's
  see what code gcc generates:
  
   >         xorl %edx,%edx
   >         movl current_set,%eax
3697cd9aa   Amerigo Wang   Doc: update Docum...
150
151
   >         cmpl $24,788(%eax)
   >         je .L1424
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
152
   >         cmpl $-1073741825,64(%esp)
3697cd9aa   Amerigo Wang   Doc: update Docum...
153
   >         ja .L1423
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
154
   > .L1424:
3697cd9aa   Amerigo Wang   Doc: update Docum...
155
   >         movl %edx,%eax
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
   >         movl 64(%esp),%ebx
   > #APP
   > 1:      movb (%ebx),%dl                /* this is the actual user access */
   > 2:
   > .section .fixup,"ax"
   > 3:      movl $-14,%eax
   >         xorb %dl,%dl
   >         jmp 2b
   > .section __ex_table,"a"
   >         .align 4
   >         .long 1b,3b
   > .text
   > #NO_APP
   > .L1423:
   >         movzbl %dl,%esi
3697cd9aa   Amerigo Wang   Doc: update Docum...
171
172
173
  The optimizer does a good job and gives us something we can actually
  understand. Can we? The actual user access is quite obvious. Thanks
  to the unified address space we can just access the address in user
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
174
175
176
177
178
  memory. But what does the .section stuff do?????
  
  To understand this we have to look at the final kernel:
  
   > objdump --section-headers vmlinux
3697cd9aa   Amerigo Wang   Doc: update Docum...
179
   >
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
180
   > vmlinux:     file format elf32-i386
3697cd9aa   Amerigo Wang   Doc: update Docum...
181
   >
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
   > Sections:
   > Idx Name          Size      VMA       LMA       File off  Algn
   >   0 .text         00098f40  c0100000  c0100000  00001000  2**4
   >                   CONTENTS, ALLOC, LOAD, READONLY, CODE
   >   1 .fixup        000016bc  c0198f40  c0198f40  00099f40  2**0
   >                   CONTENTS, ALLOC, LOAD, READONLY, CODE
   >   2 .rodata       0000f127  c019a5fc  c019a5fc  0009b5fc  2**2
   >                   CONTENTS, ALLOC, LOAD, READONLY, DATA
   >   3 __ex_table    000015c0  c01a9724  c01a9724  000aa724  2**2
   >                   CONTENTS, ALLOC, LOAD, READONLY, DATA
   >   4 .data         0000ea58  c01abcf0  c01abcf0  000abcf0  2**4
   >                   CONTENTS, ALLOC, LOAD, DATA
   >   5 .bss          00018e21  c01ba748  c01ba748  000ba748  2**2
   >                   ALLOC
   >   6 .comment      00000ec4  00000000  00000000  000ba748  2**0
   >                   CONTENTS, READONLY
   >   7 .note         00001068  00000ec4  00000ec4  000bb60c  2**0
   >                   CONTENTS, READONLY
  
  There are obviously 2 non standard ELF sections in the generated object
  file. But first we want to find out what happened to our code in the
  final kernel executable:
  
   > objdump --disassemble --section=.text vmlinux
   >
   > c017e785 <do_con_write+c1> xorl   %edx,%edx
   > c017e787 <do_con_write+c3> movl   0xc01c7bec,%eax
   > c017e78c <do_con_write+c8> cmpl   $0x18,0x314(%eax)
   > c017e793 <do_con_write+cf> je     c017e79f <do_con_write+db>
   > c017e795 <do_con_write+d1> cmpl   $0xbfffffff,0x40(%esp,1)
   > c017e79d <do_con_write+d9> ja     c017e7a7 <do_con_write+e3>
   > c017e79f <do_con_write+db> movl   %edx,%eax
   > c017e7a1 <do_con_write+dd> movl   0x40(%esp,1),%ebx
   > c017e7a5 <do_con_write+e1> movb   (%ebx),%dl
   > c017e7a7 <do_con_write+e3> movzbl %dl,%esi
  
  The whole user memory access is reduced to 10 x86 machine instructions.
  The instructions bracketed in the .section directives are no longer
3697cd9aa   Amerigo Wang   Doc: update Docum...
220
  in the normal execution path. They are located in a different section
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
221
222
223
  of the executable file:
  
   > objdump --disassemble --section=.fixup vmlinux
3697cd9aa   Amerigo Wang   Doc: update Docum...
224
   >
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
225
226
227
228
229
230
   > c0199ff5 <.fixup+10b5> movl   $0xfffffff2,%eax
   > c0199ffa <.fixup+10ba> xorb   %dl,%dl
   > c0199ffc <.fixup+10bc> jmp    c017e7a7 <do_con_write+e3>
  
  And finally:
   > objdump --full-contents --section=__ex_table vmlinux
3697cd9aa   Amerigo Wang   Doc: update Docum...
231
   >
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
   >  c01aa7c4 93c017c0 e09f19c0 97c017c0 99c017c0  ................
   >  c01aa7d4 f6c217c0 e99f19c0 a5e717c0 f59f19c0  ................
   >  c01aa7e4 080a18c0 01a019c0 0a0a18c0 04a019c0  ................
  
  or in human readable byte order:
  
   >  c01aa7c4 c017c093 c0199fe0 c017c097 c017c099  ................
   >  c01aa7d4 c017c2f6 c0199fe9 c017e7a5 c0199ff5  ................
                                 ^^^^^^^^^^^^^^^^^
                                 this is the interesting part!
   >  c01aa7e4 c0180a08 c019a001 c0180a0a c019a004  ................
  
  What happened? The assembly directives
  
  .section .fixup,"ax"
  .section __ex_table,"a"
  
  told the assembler to move the following code to the specified
  sections in the ELF object file. So the instructions
  3:      movl $-14,%eax
          xorb %dl,%dl
          jmp 2b
  ended up in the .fixup section of the object file and the addresses
          .long 1b,3b
  ended up in the __ex_table section of the object file. 1b and 3b
3697cd9aa   Amerigo Wang   Doc: update Docum...
257
258
  are local labels. The local label 1b (1b stands for next label 1
  backward) is the address of the instruction that might fault, i.e.
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
  in our case the address of the label 1 is c017e7a5:
  the original assembly code: > 1:      movb (%ebx),%dl
  and linked in vmlinux     : > c017e7a5 <do_con_write+e1> movb   (%ebx),%dl
  
  The local label 3 (backwards again) is the address of the code to handle
  the fault, in our case the actual value is c0199ff5:
  the original assembly code: > 3:      movl $-14,%eax
  and linked in vmlinux     : > c0199ff5 <.fixup+10b5> movl   $0xfffffff2,%eax
  
  The assembly code
   > .section __ex_table,"a"
   >         .align 4
   >         .long 1b,3b
  
  becomes the value pair
   >  c01aa7d4 c017c2f6 c0199fe9 c017e7a5 c0199ff5  ................
                                 ^this is ^this is
3697cd9aa   Amerigo Wang   Doc: update Docum...
276
                                 1b       3b
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
277
278
279
280
281
282
283
284
285
286
287
  c017e7a5,c0199ff5 in the exception table of the kernel.
  
  So, what actually happens if a fault from kernel mode with no suitable
  vma occurs?
  
  1.) access to invalid address:
   > c017e7a5 <do_con_write+e1> movb   (%ebx),%dl
  2.) MMU generates exception
  3.) CPU calls do_page_fault
  4.) do page fault calls search_exception_table (regs->eip == c017e7a5);
  5.) search_exception_table looks up the address c017e7a5 in the
3697cd9aa   Amerigo Wang   Doc: update Docum...
288
      exception table (i.e. the contents of the ELF section __ex_table)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
289
      and returns the address of the associated fault handle code c0199ff5.
3697cd9aa   Amerigo Wang   Doc: update Docum...
290
  6.) do_page_fault modifies its own return address to point to the fault
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
      handle code and returns.
  7.) execution continues in the fault handling code.
  8.) 8a) EAX becomes -EFAULT (== -14)
      8b) DL  becomes zero (the value we "read" from user space)
      8c) execution continues at local label 2 (address of the
          instruction immediately after the faulting user access).
  
  The steps 8a to 8c in a certain way emulate the faulting instruction.
  
  That's it, mostly. If you look at our example, you might ask why
  we set EAX to -EFAULT in the exception handler code. Well, the
  get_user macro actually returns a value: 0, if the user access was
  successful, -EFAULT on failure. Our original code did not test this
  return value, however the inline assembly code in get_user tries to
  return -EFAULT. GCC selected EAX to return this value.
  
  NOTE:
  Due to the way that the exception table is built and needs to be ordered,
  only use exceptions for code in the .text section.  Any other section
  will cause the exception table to not be sorted correctly, and the
  exceptions will fail.