Unveil(2) – Unveil parts of a restricted filesystem view
man.openbsd.org
man.openbsd.org
fork();
unveil("/home/jakob/", "w");
unveil("/etc/some_config_file", "r");
unveil(0,0);
exec();I think that's the really weird bit, I guess they didn't want multiple functions but it would make more sense to veil() (hide everything), unveil(path, mode) (show that path) and lockveil(), something along those lines. Or maybe use some sort of mode constants e.g. veil(VEIL_INIT), veil(VEIL_REVEAL, path, mode), veil(VEIL_LOCK).
That said, “unveil” is good enough.
Also the usage of strings for flags and not int bits just let me cry out. This is pure nonsense.
There's nothing wrong with the use of strings here. It's very readable, easily understandable to anyone who knows C, can be checked by automated tools (or at runtime) for invalid values, and is easily extensible in the future. It's also not on a hot path (I mean, you shouldn't have to do this at any point other than process creation).
Makes the calls shorter and easier to read. Consider:
if (pledge("stdio rpath tmppath proc exec", NULL) == -1)
err(1, "pledge");
vs. if (pledge(PLEDGE_STDIO | PLEDGE_RPATH | PLEDGE_TMPPATH | PLEDGE_PROC | PLEDGE_EXEC, NULL) == -1)
err(1, "pledge");
Makes it easier to add a pledge interface to other languages with having to keep chasing bit flag changes, ie. there's been several new promises add since 2015 but the OpenBSD::Pledge perl module hasn't needed to change.And really, unless you're using pledge wrong, ie. by using it outside of the startup path and/or not checking the return value, what does compile-time checking get you?
Now, at a language level, would I like the C committee to start looking at some of these things? Yes, but why bother hoping for miracles.
1) https://www.engadget.com/2018/07/13/aliens-colonial-marines-...
1) easily readable, string-like input; 2) built-in-your-code verification and 3) compile-time check; 4) zero-overhead execution.
Just reading Ted‘s blog post gave me an idea how lang-specific libraries / (thin-layer) abstractions (or simply additional magic helper functionality) can improve upon legacy API designs (e.g. the GLX example) by using lang-specific functionality I never knew I‘d need or even cared about. Sometimes one can learn when you‘d never expect it. :D
Unless I‘m mistaken and misunderstood the whole thing, please correct me in that case.
QUOTE
It is important to consider that directory results are remembered at the time of a call to unveil(). This means that a directory that is removed and recreated after a call to unveil() will appear to not exist. Non directories are remembered by name within their containing directory, and so may be created, removed, or re-created after a call to unveil() and still appear to exist.
ENDQUOTE
So it's not really path-based, but rather translates a path at the the time of the call to some UUID (or just an inode? But then how is inode re-use handled?) for that particular directory.
So if you unveil("~/myapp", ...) and the user does something like `mv ~/myapp ~/myapp.back && mkdir ~/myapp` the program won't see anything under ~/myapp anymore, since it's not the same directory, even thought it's at the same path.
Think e.g. a daemon that processes data in /var/run/something and uploads it somewhere else, and gracefully handles failure if the directory disappears (e.g. an admin had to remove all existing data). Due to the unveil(2) interface such an action would require a full restart of the daemon, but that's not a normal limitation on *nix systems.
I can't think of any reason for it to work this way except that it's exposing the underlying limits of the implementation, or if they're making the trade-off of tying it to the inode so `mv ~/myapp ~/myapp.back` will continue to allow access to files under ~/myapp.back, but they don't document that.
If such a feature is wanted, I would think that the daemon can just call unveil again when it's doing an automatic recovery.
> [...]the daemon can just call unveil again[...]
Not if it previously called unveil(NULL, NULL), which is a significant part of the security selling point of this feature. If so it'll need a full restart.You also won't be able to tell if the directory can't be accessed or if it truly doesn't exist, since syscalls will return ENOENT instead of EACCES in this case. So the state machine to recover from this becomes complex. You might do a full restart just to find that no, the directory really doesn't exist anymore, rather than getting replaced.
On the other hand this is consistent with how chroot(8) works, but in that case you're cd'd to the directory in question, which you'll loose access to if it gets replaced (standard nix semantics, nothing to do with chroot per-se).
Overall I really like these simple security restriction mechanisms OpenBSD is adding, and wish these sorts of APIs were available on more popular OSs, but if say Linux cloned this I hope they don't carry over this caveat, since it's not consistent with how path access works in general.
unprivileged user namespace + mount namespace -> mount some tmpfs, bind-mount (optionally read-only or noexec), pivot_root and you got an isolated view of the filesystem. You could write a wrapper around that which provides a streamlined API in the fashion of unveil.
It's what sandboxes and containers runtimes do. Instead it could also be made into a security library which processes could use as part of their startup sequence.
Basically, the linux building-blocks (seccomp, namespaces, privileges) are more low-level and you need to assemble pledge/unveil-like abstractions from them. But I am not aware of any established library that does that in a convenient fashion.
Intuitively you would think that makes them less secure. But inheritance means that it becomes impossible to use system utilities. On OpenBSD /bin/sh is pledge'd; you could never do the same thing using seccomp because it would make the shell useless. At best you'd dynamically seccomp a single shell session according to a particular task, and you'd have to do it from the invoking process. Unsurprisingly, nobody bothers doing this.
So it's impossible to emulate pledge, and impractical to emulate unveil, on Linux. At this point, almost every program on OpenBSD uses pledge. No configuration. No option switches. It's done. Everything works like before, except now the security risk of bugs in all that code are substantially mitigated.
All the criticisms of pledge and unveil miss the basic point of these interfaces--to be easily useable by developers and easily used in all programs, not as low-level primitives to be used to write libraries to be used to write tools to be used by sysadmins for sandboxing services.
probably better, as such code is likely to be very unportable. even if you standardise the calls in a library, on linux the right paths will probably vary between distributions and architectures
That's not possible with an external container/sandbox launcher, you need to interleave it with the control flow.
Seccomp&co are too low-level. Jail launchers are too coarse.
This way bugs/exploits of the server can't suddenly go read/write to i.e. /etc/passwd.
I.e. a malicious process can't swap directories under your nose.
inodes have generations. Each reuse increments generation. (ino, generation) tuple uniquely identifies an object within the file system.
$ mkdir a b
$ sudo mount --bind a b
$ rmdir a && mkdir a
$ touch a/foo
$ ls b # no foo
This can be seen in docker containers if you bindmount things around as well.The solution for both is the same: simply bindmount a level higher. If you need to mutate "data", instead make the directory "myapp/data" and mutate the sub directory only while mounting/unveiling the parent.
I wish OS X and windows had this.
It's done there, such that "accidental" dependencies are not followed, e.g. if your a.cpp source included b.h it better be that the rule compiling a.cpp explicitly stated that is dependent on a rule "exporting" b.h
And possibly for other uses too :)
I haven't used OpenBSD in around fifteen years.
Is the dynamic linker somehow exempt from unveil? Or does it somehow know what's going on?
Sooner or later you run out of clever English verbs for the plethora of fixed-function ones.
How do you constrain filesystem access to be under a list of allowed directories using BPF?
I briefly looked at it, and there doesn't seem to be a good way to manipulate strings. Everyone seems to be using chroot / bind mounts for that on Linux, which adds crap lines to `mount` output.
This is nice and I bet it will be preferable to blocking further unveil calls via pledge. Doing it with pledge would depend on the code path. Just as an example, are you connecting to an IP address or a hostname (requiring a "dns" pledge)? Are you reading from stdin or opening a file directly (requiring an "rpath" pledge)? The current pledge state wouldn't matter when removing unveil access via unveil itself, much simpler.
Pledge is already outstanding but combined with unveil I feel like a kid in a candy store.
unveil(0, UNV_LOCK)