3 ms·
Does anyone have links that would explain this concept in greater detail? I get the meaning of W^X and I even understand the danger of executable memory, and wh
by SwellJoe 12y ago
Does anyone have links that would explain this concept in greater detail? I get the meaning of W^X and I even understand the danger of executable memory, and why one wouldn't want it to be write-able (or at least not write-able by untrusted writers), but, beyond that I don't really understand the broader implications.
Also, it sounds like it would be a massive undertaking, even for a small(ish) kernel like OpenBSD, if the kernel wasn't always written with this goal in mind. Is that the case? (I don't see a patch referenced, so I'm not able to judge for myself.)
- geofft 12y agoIt's fairly rare that you want to write and modify a page somewhat simultaneously, as a high-level goal. Most of the time you're loading some existing executable from disk, and once it's loaded you're executing it, so you can just have the kernel write to it, switch it to executable and nonwritable, and pass it back to the userspace process. Sometimes you have a JIT or a VM doing binary translation, but even there you typically JIT some code once and then call it one or more times, and if you JIT some other code it's a different page. You don't typically intersperse writing code to a page and executing that page. So you just want userspace to be a little rigorous about separating those two steps, and tell the kernel when it's switching between those two, instead of requesting a page that's simultaneously writable and executable. As another commenter said, one of the reasons this isn't done at the outset is that some architectures (notably x86-32) don't implement W^X in hardware, so there's no pressure to be 100% clean about this. But it's rare that you have code that isn't straightforward, at least conceptually, to rework into W^X compatibility. One of the more annoying things is fixing up relocations: if you have a call to a function in a dynamic library, and you don't know where that library is going to be loaded until it's loaded, the most obvious way to implement this is to map your program code writable and fill in addresses once you know what they are. Each place where an address needs to be filled in is called a "relocation". So when you load a dynamic library, you loop over all relocations and fill in any addresses for symbols contained in that dynamic library. There are lots of reasons this is awful; one is that you have to go update every place in the program code. So you indirect that through a thing called the "procedure linkage table" (PLT), which contains a bunch of tiny functions that just go call your real dynamic functions, and you hard-code references to the PLT. The PLT still has to be writable, though. If you don't want that, you make a separate section of the program called the "global offset table" (GOT) that contains addresses, and you have each stub function in the PLT do an indirect function calls to a matching entry in the GOT. So the PLT is executable and doesn't need to be writable, and the GOT is writable and doesn't need to be executable. If you want to be super paranoid, you resolve all your dynamic libraries at startup and mark the GOT as read-only ("bind now" and read-only relocations aka "relro", respectively), so that nothing is writable. But that's a different discussion from W^X. (This telling is not very historically accurate about how the PLT and GOT came to exist, but hopefully the explanation of what they do is close enough to correct to convey the general ideas.)
- brynet 12y agoOpenBSD's kernel virtual/physical memory system, uvm(9), already had the ability to set permissions on mappings. But the recent work done here is still substantial, API's were cleaned up and the W^X policy was applied in the places where memory was being mapped with both permissions (writable, executable). I'm not overly familiar with all that went into this, but reading the commit logs by both Theo and Mike might help clarify some of it. http://freshbsd.org/search?project=openbsd&q=file.name%3Aamd64&committer=mlarkin http://freshbsd.org/search?project=openbsd&q=file.name%3Aamd... http://freshbsd.org/search?project=openbsd&q=file.name%3Akern&committer=mlarkin http://freshbsd.org/search?project=openbsd&q=file.name%3Aker... http://freshbsd.org/search?project=openbsd&q=file.name%3Aamd64&committer=deraadt http://freshbsd.org/search?project=openbsd&q=file.name%3Aamd... http://freshbsd.org/search?project=openbsd&q=file.name%3Akern&committer=deraadt http://freshbsd.org/search?project=openbsd&q=file.name%3Aker...
- pkaye 12y agoIt comes from the early x86 processors not having an "no-execute" permission bit for page table entries. In the AMD64 bit architecture, an NX bit was added.
- danieldk 12y agoAnd Intel retroactively added it to x86 (requiring PAE). E.g.: http://ark.intel.com/products/27460/Intel-Pentium-4-Processor-520J-supporting-HT-Technology-1M-Cache-2_80-GHz-800-MHz-FSB http://ark.intel.com/products/27460/Intel-Pentium-4-Processo...
- brynet 12y agoMike Larkin made a follow-up post to the mailing lists mentioning some upcoming work. http://marc.info/?l=openbsd-tech&m=142122093110713&w=2 http://marc.info/?l=openbsd-tech&m=142122093110713&w=2 OpenBSD/i386 still has to run on systems without PAE, but it should also take advantage of the capabilities of modern processors.
- koenigdavidmj 12y agoStack smashing attacks are no longer possible, since: 1. ProPolice detects most attempts to clobber the return address. 2. You can't set the return address to memory you control the contents of, such as user input, since that memory is writable and therefore not executable. 3. The remaining way to get code into executable memory is to write a file and have the program mmap(2) it. Address space layout randomisation makes finding this code difficult, even if you have the ability to smash the stack in a way that bypasses ProPolice. The difficult cases (like the trampoline case mentioned) are usually problematic because they are programmatically writing small functions in machine code, then executing them; basically this requires the discipline to write the function and then immediately flip the page from writable to executable. Implementing a JIT compiler like the JVM would encounter similar difficulties.
- yuubi 12y ago> no longer possible More difficult, but https://en.wikipedia.org/wiki/Return-oriented_programming https://en.wikipedia.org/wiki/Return-oriented_programming is a thing.
- brynet 12y agoOpenBSD has switched it's platforms to using PIE (Position-independent Executables) by default. The 5.7 release will also introduce self-relocating static PIE. http://marc.info/?l=openbsd-cvs&m=141922027318727&w=2 http://marc.info/?l=openbsd-cvs&m=141922027318727&w=2
- chongli 12y agoA cursory Googling turned up this: http://flyer.sis.smu.edu.sg/trustcom11.pdf http://flyer.sis.smu.edu.sg/trustcom11.pdf I don't know whether it's practical or not but it's definitely possible.
- comex 12y agoI think you're confusing the kernel and userland, as the post mentions that OpenBSD's kernel ASLR support (i.e. position independence) is currently limited. When it comes to userland, W^X is a much older feature, which OpenBSD pioneered, but which by now is essentially ubiquitous (except when a JIT is in use, e.g. in web browsers), along with ASLR. Such features have helped make exploits more difficult over time, but far from impossible - it all depends on the type of vulnerability, as well as things like how much interactivity exists between the attacker/the attacker's code and the target (potentially allowing em to gather data about ASLR, stack canaries, etc. before sending the final code execution bit). For example, web browsers are a very good case for the attacker, where not only is there a lot of interactivity in the form of JavaScript method calls, but a JIT usually ensures RWX pages exist; on the other side, an inetd server that spawns a new process for every request, with new ASLR offsets and stack canaries, would be pretty bad, since there is little interactivity. When it comes to the kernel, an important attack source is userland programs (already compromised or run by a malicious user in a multiuser system) trying to abuse the system call interface. In this case, not only is there a lot of interactivity (many system calls + complex low-level device drivers, if applicable + weird CPU features + high level of control over multiple cores/threads and timing + sharing the same CPU caches etc. with the kernel), on pre-Haswell x86-64 processors, there is actually no performant way for the kernel to prevent the memory of the currently running user process from being directly accessible from it (not executable as of Ivy Bridge though), making any kind of ASLR much less useful. So while kernels can and do get pretty far by having well-written code that avoids vulnerabilities, they usually only need to give an inch for userland to take a mile. There are, however, other, less favorable attack scenarios, e.g. remote attacks on network stacks, and in any case W^X can't hurt.