OpenBSD system-call-origin verification
Benefits for LWN subscribers The primary benefit from subscribing to LWN is helping to keep us publishing, but, beyond that, subscribers get immediate access to all site content and access to a number of extra site features. Please sign up today! |
A new mechanism to help thwart return-oriented programming (ROP) and similar attacks has recently been added to the OpenBSD kernel. It will block system calls that are not made via the C library (libc) system-call wrappers. Instead of being able to string together some "gadgets" that make a system call directly, an attacker would need to be able to call the wrapper, which is normally at a randomized location.
Theo de Raadt introduced the feature in a late November posting to the OpenBSD tech mailing list. The idea is to restrict where system calls can come from to the parts of a process's address space where they are expected to come from. OpenBSD already disallows system calls (and any other code) executing from writable pages with its W^X implementation. Since OpenBSD is largely in control of its user-space applications, it can enforce restrictions that would be difficult to enact in a more loosely coupled system like Linux, however. Even so, the restrictions that have been implemented at this point are less strict than what De Raadt would like to see.
The eventual goal would be to disallow system calls from anywhere but the region mapped for libc, but the Go language port for OpenBSD currently makes system calls directly. That means the main program text segment needs to be in the list of memory regions where system calls are allowed to come from. Static binaries will also need to have their text segment included, since libc will inhabit part of that address space. In his message, he described the full list of allowed memory regions:
For dynamic binaries, valid regions are ld.so's text segment, the signal trampoline, and libc.so's text segment... AND the main program's text.
Switching Go to use the libc wrappers (as is already done on Solaris and macOS) is something that De Raadt would like to see. It may allow dropping the main program text segment from the list of valid regions in the future:
The kernel can mark most of the regions valid as it starts up a process, but it will not know what address space holds libc.so for a dynamic binary. Marking that region as valid is left to the ld.so dynamic linker. It has been changed to execute a new system call, msyscall(), to mark the region occupied by libc.so as one that is valid for making system calls. msyscall() can only be called once by a process, so changes or additions to the valid regions are not possible once ld.so has done so. Any process that makes a system call from a non-approved region will be killed.
Another OpenBSD security measure plays a role here as well. On boot, libc is re-linked in a random order, so that the locations of the system-call wrappers within libc are different for every running system. This change forces attackers to use those wrappers, which will be difficult to find reliably for a non-local attack.
Things move quickly in the OpenBSD world (as we saw with the kernel address-space randomized link (KARL)
feature in 2017), so it was no surprise that the code for this new feature
was committed to
the OpenBSD tree just two days after it was first posted. It is an ABI
break for the operating system, but that is no big deal for OpenBSD.
De Raadt said: "we here at
OpenBSD are the kings of ABI-instability
". He suggested that relying on the OpenBSD ABI is
fraught with peril:
I have altered the ABI. Pray I do not alter it further.
This is an incremental security improvement; it is a hardening measure that makes it more difficult for attackers to reliably exploit a weakness that they find. There was no real dissent seen in the thread, so it would seem to be a relatively non-controversial change. But, once Go is changed and the main program text is not allowed to make system calls, that might change if there are other applications that need the raw system-call capability. For Linux, though, that kind of ABI change would never get very far.
Index entries for this article | |
---|---|
Security | Hardening |
(Log in to post comments)
OpenBSD system-call-origin verification
Posted Dec 11, 2019 16:51 UTC (Wed) by jhhaller (guest, #56103) [Link]
OpenBSD system-call-origin verification
Posted Dec 11, 2019 17:22 UTC (Wed) by farnz (subscriber, #17727) [Link]
Looking at the Rust std library source, it appears that all of the important bits are implemented as wrappers around FFI to OpenBSD's libc, not as raw syscalls. So, at least for Rust, this should Just Work.
OpenBSD system-call-origin verification
Posted Dec 11, 2019 18:31 UTC (Wed) by jhhaller (guest, #56103) [Link]
While I doubt there is an immediate replacement of libc based version, I wouldn't be surprised if this approach eventually gains momentum.
OpenBSD system-call-origin verification
Posted Dec 11, 2019 18:48 UTC (Wed) by ballombe (subscriber, #9523) [Link]
OpenBSD system-call-origin verification
Posted Dec 12, 2019 8:35 UTC (Thu) by dvdeug (subscriber, #10998) [Link]
And there's a non-toy compiled language implementation that doesn't allow calling C functions? I'm almost tempted to drop the "compiled" from that sentence; there are some interpreted language implementations that have a specific purpose that don't need to call native-code functions, but one of the most powerful thing any language implementation can do is permit calling native-code (C/Fortran) libraries that either (a) interact with hardware, (b) implement something complex and fiddly in tested code or (c) implement something super-fast in tested code.
OpenBSD system-call-origin verification
Posted Dec 12, 2019 8:43 UTC (Thu) by Cyberax (✭ supporter ✭, #52523) [Link]
OpenBSD system-call-origin verification
Posted Dec 12, 2019 9:28 UTC (Thu) by mjg59 (subscriber, #23239) [Link]
OpenBSD system-call-origin verification
Posted Dec 12, 2019 10:25 UTC (Thu) by Cyberax (✭ supporter ✭, #52523) [Link]
OpenBSD system-call-origin verification
Posted Dec 12, 2019 11:47 UTC (Thu) by dvdeug (subscriber, #10998) [Link]
OpenBSD system-call-origin verification
Posted Dec 12, 2019 22:39 UTC (Thu) by Cyberax (✭ supporter ✭, #52523) [Link]
OpenBSD system-call-origin verification
Posted Dec 14, 2019 17:53 UTC (Sat) by luto (subscriber, #39314) [Link]
OpenBSD system-call-origin verification
Posted Dec 11, 2019 19:28 UTC (Wed) by josh (subscriber, #17465) [Link]
OpenBSD system-call-origin verification
Posted Dec 11, 2019 21:45 UTC (Wed) by mathstuf (subscriber, #69389) [Link]
OpenBSD system-call-origin verification
Posted Dec 12, 2019 4:57 UTC (Thu) by wahern (subscriber, #37304) [Link]
OpenBSD system-call-origin verification
Posted Dec 11, 2019 21:13 UTC (Wed) by mm7323 (subscriber, #87386) [Link]
Some smarts with dynamic library handing may enable a lookup table in the kernel per shared object and reduce memory overhead. But anything should be able to make a raw system call... iff that's how the code was compiled to run.
OpenBSD system-call-origin verification
Posted Dec 12, 2019 8:38 UTC (Thu) by NYKevin (subscriber, #129325) [Link]
OpenBSD system-call-origin verification
Posted Dec 12, 2019 19:56 UTC (Thu) by mm7323 (subscriber, #87386) [Link]
Then I'd bet that in most calls to syscall(2) the first argument is a constant, or a propagated constant, and so the compiler could still generate the required ELF section entries to secure the call under such a scheme.
There will still be something out there using syscall() in imaginative ways that can't be determined at compile-time (fuzzers?) so a way to disable the check on a selective basis would still be needed, and that may allow a route to gradually deprecate syscall() for a big security boost.
OpenBSD system-call-origin verification
Posted Dec 12, 2019 21:27 UTC (Thu) by nybble41 (subscriber, #55106) [Link]
OpenBSD system-call-origin verification
Posted Dec 13, 2019 10:41 UTC (Fri) by mm7323 (subscriber, #87386) [Link]
The first argument to syscall() is the system call number, which I surmise can commonly be determined by the compiler. For example, else where in the comments the following code is cited: https://git.kernel.org/pub/scm/linux/kernel/git/dhowells/...
The syscall() use in the above code passes constant syscall numbers from <sys/syscall.h> such as __NR_add_key. Using a gcc builtin we can check that the compiler recognises these values as a constant too and at least with gcc 8.3.1:
__builtin_constant_p(__NR_add_key) === 1
Therefore it should be possible to get at least GCC to generate a section with the true system call number and return address, at least in this common use pattern of syscall().
OpenBSD system-call-origin verification
Posted Dec 14, 2019 1:14 UTC (Sat) by nybble41 (subscriber, #55106) [Link]
OpenBSD system-call-origin verification
Posted Dec 15, 2019 7:29 UTC (Sun) by mm7323 (subscriber, #87386) [Link]
Tail-call optimisation is done by the compiler, so given I'm suggesting the compiler itself can generate the syscall number and return addresses, it should still be possible to get this right. At worst tail-call optimisation could be disabled if the return address (or set of return addresses) cannot be determined.
All such a scheme does is to implement Control Flow Integrity (CFI) at the system call level, and prevent a process from making new or different system calls from the time it was compiled, akin to pledge(), but automatic, complete, and dealing with libraries.
Anyway, someone else posted a link to some research which is essentially this, but parsing binaries rather than modifying the compiler stage. It goes far further than I could explain in comments, please take a look if you are interested:
https://lwn.net/Articles/807246/
OpenBSD system-call-origin verification
Posted Dec 16, 2019 16:20 UTC (Mon) by nybble41 (subscriber, #55106) [Link]
No, the argument is exactly the same. Only your understanding of it has changed.
> Tail-call optimisation is done by the compiler, so given I'm suggesting the compiler itself can generate the syscall number and return addresses, it should still be possible to get this right.
For the trivial cases yes, you could special-case calls to syscall() in the compiler and avoid tail-call optimization for these cases. That won't help with indirect calls, of course, unless you disable TCO globally. And the program could always define an alias for syscall() which the compiler won't recognize. But that isn't the real problem. The real problem is that malicious code can simulate a call to syscall() from the _expected_ return address. All it has to do is put the right return address on the stack or in the link register before branching to syscall(), a standard requirement for any ROP attack.
If all you want is an automatic equivalent of pledge() then simply recording the system call numbers, without return addresses, would make much more sense. The kernel can't enforce anything about the return address anyway, since that is easily controlled by malicious code. There would need to be a way to override it, since some programs (mainly interpreters, e.g. the syscall function in Perl) need to be able to make arbitrary system calls depending on the inputs. Though having any program that links with libc enable all system calls invoked by any function available in libc wouldn't restrict the execution environment very much. To really be effective you'd need to attach the system call numbers to specific library entry points, or link statically and get the syscalls from the individual object files within the archive.
> Anyway, someone else posted a link to some research which is essentially this, but parsing binaries rather than modifying the compiler stage.
That could work since the entire program is available, and presumably their binary parser can simply reject any program for which it can't determine the syscall bounds. However, it's incompatible with dynamic linking where the whole program isn't known until it's actually executed, and possibly not even then if the program uses dlopen() to load plugins.
OpenBSD system-call-origin verification
Posted Dec 16, 2019 20:22 UTC (Mon) by mm7323 (subscriber, #87386) [Link]
Say goodbye to exec
Posted Dec 12, 2019 0:46 UTC (Thu) by jreiser (subscriber, #11027) [Link]
According to the description, this has changed the API (Application Programming Interface) for the shell. exec of a compiled binary program no longer works, because with randomized layout of shared libraries then the target's instantiation of libc is nearly always at a different address than the shell's libc.
Say goodbye to exec
Posted Dec 12, 2019 2:21 UTC (Thu) by mathstuf (subscriber, #69389) [Link]
OpenBSD system-call-origin verification
Posted Dec 12, 2019 18:58 UTC (Thu) by rweikusat2 (subscriber, #117920) [Link]
OpenBSD system-call-origin verification
Posted Dec 12, 2019 22:32 UTC (Thu) by Cyberax (✭ supporter ✭, #52523) [Link]
You can call libc from assembly just fine.
OpenBSD system-call-origin verification
Posted Dec 13, 2019 16:21 UTC (Fri) by rweikusat2 (subscriber, #117920) [Link]
The underlying message is nevertheless: From now on, it's strengstens verboten to call into the OpenBSD kernel from machine code. This is a necessary security measure because only criminals would do that, anyway.
NB: This is *not* a statement in favour of the great usefulness of machine code programming. But that's a hobby of some people who enjoy building stuff, as opposed to "make money by coming up with ever more abstruse ways of tearing it down".
OpenBSD system-call-origin verification
Posted Dec 13, 2019 18:26 UTC (Fri) by atai (subscriber, #10977) [Link]
That can be a sport with regular races. Seriously.
OpenBSD system-call-origin verification
Posted Dec 13, 2019 18:28 UTC (Fri) by atai (subscriber, #10977) [Link]
OpenBSD system-call-origin verification
Posted Dec 13, 2019 18:32 UTC (Fri) by Cyberax (✭ supporter ✭, #52523) [Link]
Dude, you have some really strange concepts of pretty much everything CS-related.
The overhead of libc functions is pretty much negligible, since you're doing syscalls.
OpenBSD system-call-origin verification
Posted Dec 13, 2019 19:42 UTC (Fri) by rweikusat2 (subscriber, #117920) [Link]
I wasn't referring to the additional runtime cost of invoking the system call through a C wrapper, although that's not necessary negligible and someone writing machine code would very likely care about it nevertheless, but about the "infrastructure overhead" of having to link with this library (including the size of the thing) and the absurdity of writing code following the C calling conventions of a particularly system so that one can interface with (compiled) C code supposed to enable other C code to use an interface defined in machine code.
For a loosely related example, people implement alternative C libraries for Linux solely because they're horrified by "glibc bloat". That's not much of a real practical concern, either, because today's "embedded systems" vastly more powerful than "high-end UNIX workstations of the mid-1990s", but some people do care about it very much nevertheless.
OpenBSD system-call-origin verification
Posted Dec 19, 2019 6:31 UTC (Thu) by marcH (subscriber, #57642) [Link]
> That's not much of a real practical concern, either, because today's "embedded systems" vastly more powerful than "high-end UNIX workstations of the mid-1990s", but some people do care about it very much nevertheless.
Yes of course: some hobbyists have fun implementing libraries with insanely small footprints, only to be used in systems several orders of magnitude bigger. It's like a sport.
You should actually look at some of these libraries and corresponding operating systems, they tend to give examples of the sort of real hardware they target.
OpenBSD system-call-origin verification
Posted Dec 19, 2019 10:29 UTC (Thu) by Cyberax (✭ supporter ✭, #52523) [Link]
OpenBSD system-call-origin verification
Posted Dec 19, 2019 15:46 UTC (Thu) by mathstuf (subscriber, #69389) [Link]
OpenBSD system-call-origin verification
Posted Dec 19, 2019 20:15 UTC (Thu) by Cyberax (✭ supporter ✭, #52523) [Link]
OpenBSD system-call-origin verification
Posted Dec 20, 2019 17:06 UTC (Fri) by cortana (subscriber, #24596) [Link]
OpenBSD system-call-origin verification
Posted Dec 20, 2019 20:31 UTC (Fri) by Cyberax (✭ supporter ✭, #52523) [Link]
OpenBSD system-call-origin verification
Posted Dec 21, 2019 13:26 UTC (Sat) by cortana (subscriber, #24596) [Link]
OpenBSD system-call-origin verification
Posted Dec 21, 2019 16:44 UTC (Sat) by dezgeg (subscriber, #92243) [Link]
OpenBSD system-call-origin verification
Posted Dec 24, 2019 1:01 UTC (Tue) by mathstuf (subscriber, #69389) [Link]
OpenBSD system-call-origin verification
Posted Dec 24, 2019 12:31 UTC (Tue) by cortana (subscriber, #24596) [Link]
And that NSS will tell you a host's addresses but not which interface they are reachable via, or whether the query was secured by DNSSEC, dns-over-tls, etc, which resolved does provide via its D-BUS API.
(As well as being able to do nifty things like LLMNR, or split-DNS. OTOH resolved does have its own bugs and limitations that drive me up the wall a bit...)
OpenBSD system-call-origin verification
Posted Jan 8, 2020 11:20 UTC (Wed) by nix (subscriber, #2304) [Link]
This isn't to avoid libraries getting loaded behind your back into a running process so much as it is to allow removal of the incredibly convoluted and invasive statically-linked dlopen() feature, which is only really there so that statically linked programs can do name lookups. (But moving all the NSS stuff out of every process's address space into one more controllable domain is definitely a side benefit!)
(I am only an egg, but this is my understanding, anyway. My apologies if I'm mischaracterizing anything or accidentally mixing it up with my own ideas of obviously right implementations etc: human memory is a fallible thing...)
OpenBSD system-call-origin verification
Posted Jan 9, 2020 1:34 UTC (Thu) by anselm (subscriber, #2796) [Link]
Probably not, given that the uses of NSS and those of systemd-resolved only overlap slightly.
OpenBSD system-call-origin verification
Posted Dec 14, 2019 8:50 UTC (Sat) by dvdeug (subscriber, #10998) [Link]
A hobby of many people is low-level computer hacking, wherein any Unix-type system is going to get in the way with all this "filesystem" and "virtual memory" crap. Guess what they do? They use a system that supports their needs.
OpenBSD system-call-origin verification
Posted Dec 16, 2019 20:12 UTC (Mon) by rweikusat2 (subscriber, #117920) [Link]
OpenBSD system-call-origin verification
Posted Dec 17, 2019 5:44 UTC (Tue) by dvdeug (subscriber, #10998) [Link]
Most code ever written won't compile or run on modern Unix systems, short of emulation, and the dialects they're written in fundamentally won't work well on Unix. You can't really use certain Forth dialects, as they implement hard drives as an array of sectors that the program has arbitrary access to. MS-DOS C dialects assume segmented pointers. VMS C has its own system calls that makes porting any non-trivial program challenging. Same thing with Windows C. Many BASICs assume you can PEEK and POKE certain memory locations for certain effects. You can't run most assembly languages or machine language; 68000 assembly written for OpenBSD won't run on any modern version, and never ran on any non-68K OpenBSD. You can run any language or program on any sufficiently large system with enough layers of indirection, but virtually all non-trivial code not written for Unix systems is going to make enough assumptions about how things work that Unix disagrees with to require serious emulation or virtualization to work without code changes.
The boundary of "your programming language of choice must not be one we disapprove of" was first willfully crossed when memory protection and filesystems were added.
And you say you're not "complaining", but you've chosen to characterize the "eternal OpenBSD quest" in a biased way that sounds a lot like a complaint; the goal is to "increase resilience against bugs", by many tactics, not just those which some rare programmers might find complicating things.
OpenBSD system-call-origin verification
Posted Jan 8, 2020 17:36 UTC (Wed) by marcH (subscriber, #57642) [Link]
Other systems often get these mitigations "for free" once OpenBSD paid the initial development price for them.
I really can't see anything to be unhappy about.
OpenBSD system-call-origin verification
Posted Dec 14, 2019 20:59 UTC (Sat) by flussence (subscriber, #85566) [Link]
OpenBSD system-call-origin verification
Posted Dec 13, 2019 11:11 UTC (Fri) by mneug (guest, #135662) [Link]
That would also work for statically linked binaries.
OpenBSD system-call-origin verification
Posted Dec 18, 2019 1:42 UTC (Wed) by vomlehn (guest, #45588) [Link]
OpenBSD system-call-origin verification
Posted Dec 18, 2019 2:08 UTC (Wed) by pizza (subscriber, #46) [Link]
OpenBSD system-call-origin verification
Posted Dec 19, 2019 10:17 UTC (Thu) by topimiettinen (guest, #133428) [Link]
Also alignment of the return address could be checked. Random ROP gadgets for system calls might be found at various non-aligned locations, but if compilers could be instructed to align the system call location strictly, even at specific offset to page boundary, it would decrease the chances of finding a gadget by 1/alignment (1/PAGE_SIZE for fixed page offset version). The check itself would be doable today in BPF and it would be very easy to add this to for example systemd or Firejail as a new sandboxing option.