|
|
Subscribe / Log in / New account

OpenBSD system-call-origin verification

OpenBSD system-call-origin verification

Posted Dec 12, 2019 19:56 UTC (Thu) by mm7323 (subscriber, #87386)
In reply to: OpenBSD system-call-origin verification by NYKevin
Parent article: OpenBSD system-call-origin verification

Firstly syscall(2) use seems generally rare, especially since glibc added a gettid() wrapper :-)

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.


to post comments

OpenBSD system-call-origin verification

Posted Dec 12, 2019 21:27 UTC (Thu) by nybble41 (subscriber, #55106) [Link] (5 responses)

You'd need to actually remove syscall(2) from libc to get much benefit from all this, and that would break compatibility with existing programs. As long as syscall(2) exists the kernel would only see that the system call came from the syscall(2) function in libc, which isn't enough to verify the origin of the system call number.

OpenBSD system-call-origin verification

Posted Dec 13, 2019 10:41 UTC (Fri) by mm7323 (subscriber, #87386) [Link] (4 responses)

I don't think you fully understood my comment above.

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] (3 responses)

No, I understood that. I just don't see what it accomplishes. In particular, you can't trust that syscall()'s return address bears any relationship to the origin of the system call number or other arguments. One relatively innocuous case is tail-call optimization: a() calls b() which calls syscall(), but the return address is in a(), not b(). Or syscall(), or something tail-calling syscall(), could be invoked through a function pointer such that the return address is not knowable at compile-time. Of course, a malicious actor could arrange for any return address they wish, including a perfectly legitimate call with the right system call number, and most likely also arrange to have that function return to their own code later on.

OpenBSD system-call-origin verification

Posted Dec 15, 2019 7:29 UTC (Sun) by mm7323 (subscriber, #87386) [Link] (2 responses)

So you've switched argument now, but that's still not a problem.

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] (1 responses)

> So you've switched argument now ...

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]

I really suggest you go and read the research papers - they written by people far clever than me and answer a lot of your questions.


Copyright © 2025, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds