You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

424 lines
24 KiB

  1. 1. Generic Notes & Some Design
  2. To understand the future direction of PaX, let's summarize what we achieve
  3. currently. The goal is to prevent/detect exploiting of software bugs that
  4. allow arbitrary read/write access to the attacked process. Exploiting such
  5. bugs gives the attacker three different levels of access into the life of
  6. the attacked process:
  7. (1) introduce/execute arbitrary code
  8. (2) execute existing code out of original program order
  9. (3) execute existing code in original program order with arbitrary data
  10. Non-executable pages (NOEXEC) and mmap/mprotect restrictions (MPROTECT)
  11. prevent (1) with one exception: if the attacker is able to create/write
  12. to a file on the target system then mmap() it into the attacked process
  13. then he will have effectively introduced and executed arbitrary code.
  14. Address space layout randomization (ASLR) prevents all of (1), (2) and (3)
  15. in a probabilistic sense for bugs where the attacker needs advance knowledge
  16. of addresses in the attacked process *and* he cannot learn about them (i.e.,
  17. there is no information leaking bug in the target).
  18. It is also worth noting that since all of PaX is implemented in the kernel,
  19. the kernel is considered as the Trusted Computing Base which can be subject
  20. to the same kind of attacks as userland.
  21. Based on the above the future efforts will aim at the following:
  22. (a) try to handle the exception to (1)
  23. (b) implement all possible features for protecting the kernel itself against
  24. exploiting kernel bugs, optionally (long term) create a non-kernel mode
  25. TCB
  26. (c) implement deterministic protection for (2) and maybe (3)
  27. (d) implement probabilistic protection for (2) potentially resisting
  28. information leaking
  29. We do not plan to deal with (a) in general, it is better left for Access
  30. Control and/or Trusted Path Execution systems which are beyond our project
  31. (e.g., grsecurity or some LSM modules).
  32. Note also that detecting/preventing (3) is probably equivalent to the
  33. halting problem (since the protection would have to detect data changes and
  34. their influences on all possible execution flows), so we do not expect
  35. generic solutions such as for (1). Even detecting/preventing (2) looks
  36. suspicious in this regard (given our attack model), but we have yet to see
  37. where and to what extent we can make a compromise to achieve the goal.
  38. 2. More Design & Implementation
  39. In the following we will give some details on (a), (b), (c) and (d) although
  40. it should be noted that all this is just plans, we do not know yet what will
  41. prove to be practical in the end. There are three measures that will help
  42. answer this question though:
  43. - how many/extensive changes are required (complexity),
  44. - how much protection is achieved (efficiency),
  45. - is it fast enough for practical purposes (performance).
  46. In the following we will try to answer these questions as well.
  47. (a.1) for a certain subset of the exception there can be a simple solution,
  48. requiring only some changes in the dynamic linker: if the target
  49. application is dynamically linked but does not load libraries
  50. explicitly (via the dlopen() interface) then the dynamic linker could
  51. be modified to tell the kernel when it is done loading all the shared
  52. libraries and afterwards the kernel could prevent all executable file
  53. mappings (or at least remove the executable status on them).
  54. This solution will require userland changes (vs. kernel only) but
  55. they will be fairly simple. It will also be efficient (achieves the
  56. goal for this set of applications) and fast (basically no measurable
  57. performance impact).
  58. (a.2) actually, the above kernel notification is not strictly needed, the
  59. kernel itself could determine if previous file mappings reference
  60. dlopen() and whether the dynamic linker is done loading libraries.
  61. This solution will be kernel only however it will be more complex
  62. as we will have to parse the ELF headers to determine whether the
  63. dlopen() interface is referenced for dynamic linking (in (a.1) the
  64. dynamic linker already has the necessary parsing code). It will also
  65. be as efficient and fast as (a.1) since it will do the same kind of
  66. processing, just inside the kernel.
  67. (b.1) non-executable kernel pages: using the segmentation logic of IA-32
  68. we can reduce the KERNEL_CS segment to cover only the kernel image,
  69. furthermore we can make this area read-only then using the paging
  70. logic. This step will most likely require the reorganization of the
  71. kernel image (by modifying the kernel linker script and maybe even
  72. source code). How module support fits into this is yet to be determined.
  73. This solution will be complex and may not be kernel-only (depends on
  74. how the module problem can be solved, in particular if the current
  75. modutils support mapping different ET_REL ELF file sections into non-
  76. contiguous memory areas). We expect no performance impact from this
  77. (much like SEGMEXEC) and to be very efficient (i.e., this will prevent
  78. introduction/execution of arbitrary code in kernel land).
  79. (b.2) read-only kernel function pointers: one way of doing (2) is to modify
  80. function pointers and this is what this method would prevent.
  81. This solution will require kernel-only changes, however they will be
  82. quite extensive (although simple) as normally Linux does not use the
  83. 'const' qualifier for structures that contain such pointers (even if
  84. they are really not meant to be modified). Maybe this could also be
  85. brought to the attention of the mainline kernel developers since this
  86. is more of a program correctness issue than that of security per se.
  87. We expect no performance impact from this however we note that this
  88. is not an efficient solution since there are still methods of
  89. achieving (2), in particular there are function pointer like entities
  90. on the kernel stack (saved return EIP values from function calls).
  91. These can however be addressed as well as we will show it later.
  92. (b.3) non-kernel based TCB: IA-32 has two execution modes that are normally
  93. beyond the scope of normal programs (be that OS kernels or userland):
  94. System Management Mode (SMM) and Probe Mode. SMM is normally used to
  95. implement power management schemes (APM/ACPI), while Probe Mode is
  96. used for low-level (hardware) debugging. Since using Probe Mode would
  97. require extra hardware normally not present in consumer systems, we
  98. will discuss SMM only as most current systems have the necessary
  99. hardware support in place already (the PCI chipset) and the rest
  100. requires pure software only.
  101. SMM has a few features that would allow for implementing a new TCB,
  102. beyond the reach and control of traditional OS kernels (i.e., it could
  103. not be compromised even by ring-0 protected mode code).
  104. The first feature is that the PCI chipset (North Bridge or Memory
  105. Controller Hub in particular) allows to carve out some amount of RAM
  106. for SMM use only (i.e., such memory could be accessed only while the
  107. CPU is in SMM). Normally this memory is set up by the BIOS which will
  108. store the power management code there (the System Management Interrupt
  109. (SMI) handler and related data).
  110. The second feature is that SMI can be generated by both software and
  111. hardware means, i.e., it is possible to create an API between the SMI
  112. handler and the rest of the kernel. Also the SMI handler can be
  113. invoked periodically, beyond normal kernel control.
  114. These features would allow for storing various cryptographic
  115. primitives inside the SMI handler and have them validate the state of
  116. the kernel periodically. They would also provide the kernel with some
  117. limited amount of secure data storage (albeit volatile).
  118. This solution is very complex, very hardware specific however very
  119. efficient as well. Depending on the frequency of the SMI, it may or
  120. may not have a noticable performance impact.
  121. (c.0) an attacker can achieve (2) if he is able to change the execution flow
  122. of the target to his liking. Since such changes normally occur at very
  123. specific instructions (jmp, call, retn), our goal is to protect these.
  124. All of these methods will necessarily be userland changes, the kernel
  125. could not perform them fast enough, if at all. Note also that these
  126. methods can be implemented at various levels of abstraction, such as
  127. the source code, the compiler, the static and the dynamic linker.
  128. Complexity is expected to be high in general regardless of what level
  129. they are implemented at. We also expect that we will have to make
  130. tradeoffs between efficiency and performance, and even in the extreme
  131. we do not expect to be very efficient (that is, there will remain
  132. attack methods under specific circumstances and we may not even be
  133. able to precisely determine them for a given target).
  134. With this said, let's look at a few ideas now.
  135. (c.1) To protect execution flow changes via the jmp/call instructions, we
  136. would have to be able to identify all explicit function pointers in
  137. the target and make them read-only. While we can at least identify
  138. them if we have the source code or there are sufficient relocations
  139. present in the given ELF file (this is where using ET_DYN ELF files
  140. becomes important, even for normal executables), making them read-only
  141. is a problem.
  142. First, such function pointers are normally not qualified as 'const'
  143. in the original source code (in whatever language), so in memory
  144. they will end up being mixed with other writable data and hence using
  145. the paging logic to make such pages read-only would introduce a big
  146. performance impact. Second, there are function pointers which need
  147. to be writable by the program logic itself. In any case, reducing the
  148. number of writable function pointers does decrease the success rate
  149. of (2) since there will be less places where the execution flow can be
  150. diverted (and the remaining places may not be enough to successfully
  151. attack the given target).
  152. The most important function pointers are the following:
  153. - GOT entries
  154. - .ctors/.dtors
  155. - atexit() handlers
  156. - malloc() callbacks
  157. - linuxthread callbacks (atfork() handlers)
  158. Of these at least the first two sets can be easily isolated and made
  159. read-only, the others are by their nature writable. To protect the
  160. GOT entries we will have to make changes to both the static and the
  161. dynamic linker. The static linker will have to place the GOT into
  162. its own PT_LOAD segment so that later the dynamic linker can use
  163. mprotect() to make it read-only. Making the GOT read-only is possible
  164. either at application startup or on every GOT entry update. The former
  165. requires the enforcement of the LD_BIND_NOW behaviour and it affects
  166. startup time (for the worse), whereas the latter affects GOT entry
  167. resolution time (it needs two mprotect() calls to make the GOT
  168. temporarily writable then read-only again) and is open to races
  169. (although probably not easy to exploit since by the time an attack
  170. would take place, most needed GOT entries should have been resolved).
  171. Note that being able to change the writability status of the GOT is
  172. not a problem since under PaX abusing it would require function
  173. pointer hijacking (to call mprotect() with proper arguments) which
  174. is either not possible or if it is, then the attacker is already able
  175. to call any function and pass arguments to it, so being able to change
  176. the GOT does not give him anything extra.
  177. The actual implementation does not need to be an ld.so modification,
  178. it can be another shared library loaded through /etc/ld.so.preload.
  179. Protecting .ctors/.dtors is as simple as changing the linker script
  180. to put these sections into a read-only segment.
  181. (c.2) To protect execution flow changes via the retn instruction, we face
  182. the same problem as above with writable (by their nature) function
  183. pointers. What makes the situation different is that we can determine
  184. in advance (at compile/link time) and verify at runtime the valid
  185. values for a given saved EIP on the stack. An efficient and simple
  186. example is shown below:
  187. callee
  188. epilogue: mov register,[esp]
  189. cmp [register+1],MAGIC
  190. jnz .1
  191. retn
  192. .1: jmp esp
  193. caller:
  194. call callee
  195. test eax,MAGIC
  196. What happens here is that we insert an instruction (that does not
  197. otherwise affect the program logic) after the call which encodes in
  198. its body a magic number derived from the callee's prototype and then
  199. before returning from the callee we verify that we are returning to
  200. a proper call place or not (register is anything that can be trashed
  201. inside a function). The jmp esp will simply trigger the non-executable
  202. page enforcement logic and terminate the task.
  203. This method is efficient in that it sharply reduces the number of code
  204. locations where a retn can be forced to return to, and in particular
  205. it cannot return to the normal entry point of functions which makes
  206. argument passing and stack balancing non-trivial. Note also that it
  207. does not matter if the call itself is a direct or indirect one, i.e.,
  208. calls through traditional function pointers are just as well protected
  209. as normal function calls (although it may be worth separating the two
  210. cases because the magic number for directly called functions can be
  211. based on more information, such as the function's name).
  212. The drawback of this method is that reaction on an attack is delegated
  213. to userland. It means that when deploying this method in a system,
  214. every component (executables + libraries) must be recompiled at once,
  215. it cannot be gradually introduced into the system nor can binaries be
  216. moved across systems of different nature nor can it be disabled easily
  217. (that is, it would require another full system recompilation). As we
  218. will see later, there is a probabilistic approach that remedies some
  219. of these problems.
  220. (c.3) The next method works by assuming a weakened attack model where we
  221. still assume arbitrary read/write access but we only aim to prevent
  222. attacks that need to divert execution flow more than once (i.e., the
  223. multiple return-to-libc style attack).
  224. In this case we can modify the function prologue/epilogue code so
  225. that a necessary 'ingredient' for the multiple style of the attack
  226. would no longer be present at all. Let's observe first that on IA-32
  227. function parameters are passed on the stack normally (vs. registers)
  228. and hence the stack needs to be rebalanced after a function call. In
  229. a multiple return-to-libc style attack an attacker has to explicitly
  230. rebalance the stack after every diverted function call (under Linux
  231. the default calling convention is 'caller rebalances' vs. the 'callee
  232. rebalances' convention used in Windows). Rebalancing requires the
  233. modification of ESP by a specific amount and then also transferring
  234. control to execute the next step of the attack. Such an instruction
  235. sequence is normally found in the epilogue code of functions and
  236. therefore this is what has to be modified (and for symmetry reasons,
  237. the prologue as well). Our idea is to modify ESP inside a function so
  238. that it would be way off track while the function executes and have
  239. a prologue and epilogue code similar to this:
  240. without frame ptr with frame ptr
  241. prologue: sub esp,BIG_VALUE+4 push ebp
  242. mov [esp+BIG_VALUE],register mov ebp,esp
  243. ... push register
  244. sub esp,BIG_VALUE
  245. ...
  246. mov [esp+BIG_VALUE+TOP],param mov [esp+BIG_VALUE+TOP],param
  247. add esp,BIG_VALUE-TOP-4 add esp,BIG_VALUE-TOP-4
  248. call func call func
  249. sub esp,BIG_VALUE-TOP sub esp,BIG_VALUE-TOP
  250. ... ...
  251. epilogue: mov register,[esp+BIG_VALUE] mov register,[esp+BIG_VALUE]
  252. add esp,BIG_VALUE+4 mov ebp,[esp+BIG_VALUE+4]
  253. retn add esp,BIG_VALUE+8
  254. retn
  255. It is clear that an appropriately chosen BIG_VALUE will prevent the
  256. stack rebalancing since during an attack only the epilogue is executed
  257. without the corresponding prologue. Note also that ESP has to be
  258. restored across function calls so that the callee can perform the same
  259. ESP modification. This method is expected to be efficient but also to
  260. have a noticable impact on performance, although we cannot tell how
  261. much (and whether it will be acceptable for practical purposes). Also
  262. it will be complex if for no other reason than signal delivery which
  263. wants to use the current stack (ESP) and would result in the kernel
  264. killing the task in our case. There are different ways of addressing
  265. this problem, such as delaying signal delivery (e.g., during system
  266. calls we will have a valid userland ESP so at that time signals can
  267. be safely delivered) or setting up a special signal stack (which is
  268. an already supported POSIX feature, although doing it in multithreaded
  269. applications would not be trivial) or teaching the kernel about the
  270. userland ESP shift value and have it take that into account before
  271. delivering the signal.
  272. (c.4) attack method (3) is probably the hardest problem of all. Since here
  273. the attacker relies on normal program behaviour (as far as execution
  274. flow is concerned), we cannot detect changes there. What is possible
  275. is to try to prevent specific ways of modifying arbitrary memory and
  276. hence reduce the potential arsenal an attacker can use.
  277. The following is a list of the most well-known and widespread bugs
  278. that give some kind of write (and sometimes read) ability:
  279. - stack based array overflow
  280. - heap based array overflow
  281. - user controlled format string
  282. - integer overflow
  283. - incorrect integer signedness change
  284. There are already a few known solutions for detecting/preventing
  285. exploiting such bugs (compiler modifications), where we can extend
  286. such work is to try to do them at the binary level (i.e., without
  287. access to the source code). The best candidates for such modifications
  288. are ET_DYN ELF files (typically libraries), ET_EXEC ELF executables
  289. have the fundamental problem of not having enough relocations (maybe
  290. vendors should be educated to produce either ET_DYN executables in
  291. the future or at least generate sufficient relocations into ET_EXEC
  292. files, GNU ld makes the latter very easy now).
  293. (d.1) if we weaken our attack model by assuming arbitrary write ability but
  294. no reading then we can extend ASLR to introduce randomness into the
  295. saved values of EIP and hence deter attacks such as the one described
  296. in Phrack #59-9. An efficient and simple (fast) implementation could
  297. be the modification of function prologue and epilogue code as shown
  298. below:
  299. without frame ptr with frame ptr
  300. prologue: xor [esp],esp xor ebp,[esp]
  301. sub esp,xxxx xor [esp],esp
  302. ... push ebp
  303. mov ebp,esp
  304. sub esp,xxxx
  305. ...
  306. epilogue: add esp,xxxx add esp,xxxx/mov esp,ebp
  307. xor [esp],esp pop ebp
  308. retn/retn xx xor [esp],esp
  309. xor ebp,[esp]
  310. retn/retn xx
  311. This trick uses the fact that under ASLR ESP has randomness in its
  312. lower 8 bits as well (bits 4-7).
  313. (d.2) The next method is the probabilistic version of saved EIP checking.
  314. Here the kernel will create a random 32 bit value (cookie) on every
  315. system call and place it into a variable provided by userland (it can
  316. be described in the ELF header for example). Then we modify userland
  317. as follows:
  318. callee
  319. epilogue: mov register,[esp]
  320. mov register,[register+1]
  321. sub register,MAGIC
  322. neg register
  323. sbb register,register
  324. lock or [cookie],register
  325. retn
  326. caller:
  327. call callee
  328. test eax,MAGIC
  329. It is clear that during a normal execution flow the value of cookie
  330. does not change after a function returns hence it can be checked for
  331. whenever the task enters the kernel through a system call. Should the
  332. kernel find a discrepancy between the cookie as stored in userland and
  333. its own version, it can react like it does for the non-executable page
  334. violations. To make information leaking useless the kernel can simply
  335. generate a new cookie on each system call (after having verified the
  336. previous one of course), this way an attacker cannot learn the current
  337. value of the cookie since that itself requires communication and hence
  338. a system call which would obsolete the leaked information.
  339. This method is expected to be as efficient as its deterministic
  340. version and it also suffers from one less problem: it can be applied
  341. gradually to a system, no need to recompile everything at once, also
  342. protected executables can be freely moved between different systems
  343. without adverse effects.
  344. Note that this method is free from races (coming from signal delivery
  345. or multiple threads) because cookie is updated in one irreversible
  346. atomic or instruction (the kernel should of course prevent generating
  347. the 0xffffffff value for the cookie, but since its probability is very
  348. low, this is not critical).
  349. Finally, we would like to improve on the existing methods PaX provides.
  350. In particular, we should change the way we get randomness for ASLR from
  351. the kernel since the current method is too exhaustive and is an overkill
  352. since we do not really need such a good quality randomness at all. The
  353. other possible improvment is in the fast path of the PAGEEXEC page fault
  354. handler which could be sped up a bit by using assembly.