Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Sure, assuming your programs don't execute other programs. I don't know much about OpenBSD specifically, but spawning all over the place is the "norm" in terms of "Unix philosophy" program design.

(I agree with the point in the adjacent thread: it's hard to know what to make of security mitigations that aren't accompanied by a threat model and attacker profile!)



*Agent Smith voice*

But what use is a fork() if you're unable to exec()?


It is now less normal for programs to run programs on openbsd.


> assuming your programs don't execute other programs.

What about language runtimes? They don't execute other programs in the sense of ELF executables (although the programs they interpret might), but they have to support every syscall that's included in the language. So, for example, the Python interpreter would have to include the appropriate code for every syscall that Python byte code could call (in addition to whatever internal syscalls are used by the interpreter itself). That would be a pretty complete set of syscalls.


Yep, language runtimes are an (inevitably?) large attack surface. My understanding is that OpenBSD userspace processes can voluntarily limit their own syscall behavior with pledge[1], so a Python program (or the interpreter itself) could limit the scope of a particular process. But I have no idea how common that is.

[1]: https://man.openbsd.org/pledge


A langage runtime would dispatch to the libc, which is always whitelisted.

This is only an issue for the weirdo langage runtimes who’d also refuse to use libc.


cough go cough

Although it is periodically useful to be able to copy a binary to some random Linux server and know it will work.


Even for go it should actually work as-is: the syscalls should exist statically in the binary, so the loader can enumerate and whitelist them.

What gets blocked is the system constructing the entire thing at runtime, or at least setting the syscall number dynamically.


Isn’t that how all syscalls work? The syscall number typically goes in a register.


The syscall goes in a register but it does not have to appear literally right next to the `syscall` instruction in the binary. As TFA explains in the introduction, a syscall stub generally looks like

    mov eax,0x5
    syscall
However it doesn’t have to, `syscall` will work as long as `eax` is set no matter where it’s set, or where it’s set from. You could load it from an array or a computation for all `syscall` cares.

So as an attacker if you can get eax to a value you control (and probably a few other registries) then jump to the `syscall` instruction directly you have arbitrary syscall capabilities.

The point of this change is that the loader now records exact syscall stubs as “address X performs syscall S”, then on context switch the kernel validates if the syscall being performed matches what was recorded by the loader, and if not it aborts (I assume I didn’t actually check).

This means as long as your go binary uses a normal syscall stub it’ll be recognised by the loader and whitelisted, but if say a JIT constructs syscalls dynamically (instead of bouncing through libc or whatever) that will be rejected because the loader won’t have that (address, number) recorded.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: