Pledge() and unveil() in SerenityOS (2020)
awesomekling.github.io
awesomekling.github.io
Pledge() and Unveil() in SerenityOS - https://news.ycombinator.com/item?id=22116914 - Jan 2020 (28 comments)
Related from yesterday:
Show HN: Porting OpenBSD Pledge() to Linux - https://news.ycombinator.com/item?id=32096801 - July 2022 (114 comments)
[1] https://awesomekling.github.io/I-quit-my-job-to-focus-on-Ser...
Seriously please can all of you other programmers stop pointlessly abbreviating things that are already quite short?! "read_path" is perfectly fine.
I think, the main point is to make it as easy as possible to use.
The core issue is that userspace programs and libraries can be compiled using structs with the old size (in theory the libc can abstract this using symbol versioning but then the same issue lies within the libc) causing out-of-bounds memory accesses when the kernel tries to access the struct fields. There's also forwards-compatibility issues but bad memory accesses are marginally worse.
I (obviously) prefer the extensible struct solution but there are downsides (and other solutions weren't an option for Linux anyway).
>This mechanism works by marshaling parameters to a system call into a single C structure; a pointer to that structure and the size of the structure are passed as the parameters to the system call. That size parameter acts as a sort of version number.
Oh hey, that’s exactly how every WIN32 call works. I see an article from 2003 talking about why Windows is strict about the struct size parameter: https://devblogs.microsoft.com/oldnewthing/20031212-00/?p=41...
> You can't trivially extend structs in a kernel ABI
IMO the article you linked is evidence that this is trivial. Especially for syscalls that will only be called a few times in a process’s lifetime. I dunno why there’s so much bike shedding about this on the mailing list.
Edit: Ahh I didn’t realize you were the Aleksa mentioned in the article. I wish you good luck.
But yes, it is relatively trivial -- my point was more that you can't just use a struct in the way the first comment suggested, you need to come up with some scheme (even if it seems trivial in retrospect).
As for the bike-shedding, that's LKML for you (though in fairness it is a bit of a tall order to try to come up with some enforceable API design rules in Linux -- syscalls with half-baked designs being added is less rare than one would hope, so clearly there's not an overarching design principle being applied already, though thankfully it's becoming pretty rare to see a completely borked syscall that clearly has no users being merged).
> Although using strings subverts C’s already weak type checking, that’s probably not a major concern. One can screw up bit masks by using || in place of |. Or, as above, one can incorrectly pack the magic array. It’s usually much easier to visually audit a string than the C code used to plaster a dozen option together.
It's pretty easy to design an interface that is way way less error-prone than strings (especially ones full of single-letter differences!) and the visual auditing argument falls apart as soon as you have to `snprintf()` some string together from parts.
This code is way more readable, way less error prone, more discoverable, faster and more easily extendable than strings:
auto config = make_pledge_config();
config.read_path = true;
config.stdio = true;
pledge(&config);
You'd think security focused people would care about static type checking.After the process has told this to the kernel the process can then only do these things for its life time. You can pledge() again later, but you can only restrict your pledge never expand it.
This is a nice feature because it limits the number of processes that can potentially be security liabilities even if they have bugs.
unveil() is a similar feature but for file system paths.
It’s a feature of SerenityOS (inspired/borrowed from OpenBSD), and not a feature of C/C++.
Try reading the article, it’s pretty easy to follow :)
Pledge is not some external security feature but something that every program itself manages.
FWICT, only sort of. It's like giving up permissions that you might already have (presumably to reduce potential security problems). And it's a bit more specific to the kernel.
Pledge: "I will at most use these kernel facilities" (don't let me do otherwise)
Unveil: "I will at most access these fs paths" (hide all other paths)
The idea is that every process should call each early when it starts running and, after that, if the process is ever compromised, it will not be able to do much harm since the files and syscalls it can interact with are limited.
For example, a browser should never access /etc/passwd or call the exec syscall. So, a browser, when run, should as early as possible call pledge and unveil to prevent itself from accessing /etc/passwd or calling exec if it ever becomes compromised.
With application permissions or external constraints, this is not really helpful, because the application needs to do its setup.
However if the application can pledge not to do the setup things between the setup and the steady state, and it gets corrupted or owned during the steady state (e.g. because it’s a network daemon and there’s a bug), it becomes a lot harder to exploit since there should be very little the would-be exploiter can do or explore before the OS kills the program.
So this is not really about protecting the system against the application, it’s about the application participating in the system’s protection by dynamically reducing its own permissions while running.
pledge() allows programs to declare up front what they’ll be doing. Functionality is divided into a reasonably small number of “promises” that can be combined. Each promise is basically a subset of the kernel’s syscalls.
Once you’ve pledged a set of promises, you can’t add more promises, only remove ones you’ve already made.
If a program then attempts to do something that it said it wouldn’t be doing, the kernel immediately terminates the program.
https://github.com/SerenityOS/serenity/commits/master/Kernel... and it used to be in a different file previously.