OpenBSD's unveil()
lwn.net
lwn.net
Well GNOME's evince can read remote files over at least http/https and ftp, eg: evince ftp://remote/foo.pdf. I guess it's just a short step to adding ssh/sftp support. The remote support is implemented using glib's https://developer.gnome.org/gio//2.36/GFile.html#g-file-new-... which has a generic plugin mechanism for URL schemas, although I didn't look into whether ssh is already supported.
:-)
From a security point of view sshfs is certainly a better approach. However for qemu we were concerned mainly about performance, and some testing that I did at the time indicated that the library approach (libssh2/libssh) would perform better which was why we went that way.
I guess the lesson here is that retrofitting security to existing applications is difficult, or perhaps the lesson is that Linux applications should be split into cooperating processes using lightweight messaging, like QNX or L4.
Even ignoring ssh, the same problem applies to evince + https currently (reading client certificates), and that is certainly not implemented using a subprocess, nor would it be very easy to change that considering how difficult it is to correctly run a subprocess from a multithreaded library using Linux / POSIX APIs.
OTOH, doing it as a system dialog that runs safely in its own process feels a lot better to me than building the functionality into every app in a piecemeal way.
You could even create a wrapper that'd mount the remote endpoint to a temporary location, runs your application with that path, then unmounts once your application exits.
> Calling unveil() for the first time will "drop a veil" across the entire filesystem, rendering the whole thing invisible to the process, with one exception...
Sorry, but the semantics of calling it once should be similar to the semantics of calling it twice. The first call veils and the second call unveils.
Also, on Linux at least, efficiently implementing “you can only see this subtree” is quite tricky due to races against moving the directories in question around. I wonder how OpenBSD gets around those races.
Or: every call does the following two things. (1) It ensures that a veil is present. (2) It makes a hole in the veil at the location provided.
noveil(...) to mark pathes we plan to use (fails on veiled pathes), and a subsequent veil() that irreversibly removes access to everything that was not protected by a noveil(...) call between this and the last veil() call.
If a veil() call never comes, well, then we did not restrict access at all and only spammed some kernel structure.
If all you care is protection in case someone gets codeexec in your process, then this would be simpler and give additional features (we can incrementally drop access, instead of only once).
I see an advantage of the unveil(...) in that: Suppose my process cannot irrevocably drop access, because e.g. it anticipates to need to write to some pathes we don't know already. But still, the unveil can help to mitigate stuff like directory traversals: I can't lock (unveil(0,0)), so codeexec is still game over; if my code for late unveiling has bugs it's game over; but if the late unveiling code has no bugs, but the actual file access code is buggy, then this construction will protect me.
Subsequent versions could be veiled by default, requiring calls to `unveil` for everything, but obviously that would break every existing program.
ACK and NAK are used quite a lot in communication protocols, and the usage drift from there.
Entirely the opposite of SELinux.
For example, process 1 could only r/w files in "/some/path/1" and process 2 could access "/some/path/2". But the number of processes is dynamic. I might have two or I might have 100. So I need to be able to specify new directories at runtime.
I've been looking at the linux hardening mechanisms but I couldn't find a way to do this with, for example, selinux. With selinux it seems you are mostly able to create system level policies that applied to whole programs. I haven't found a way to provide something like a parameter to change the policy for a given process.
But it looks like this would be very easy to accomplish with unveil(2). This is very interesting work!
If I trust a binary to read a directory, what's the point of constraining specific processes further?
The only use case I could think of is where the binary itself runs untrusted code from a third party. (e.g. plugins).
Also, if you didn’t write the OS, your untrusted code will call third party code, if it is worth running it at all (it has to communicate results back to the caller. That requires some OS calls, even if only to trigger some semaphore)
That's why pledge(2) and unveil(2) are useful despite literally being part of the software it restricts.
https://www.freedesktop.org/software/systemd/man/systemd.exe...
That's the real beauty of how the OpenBSD approach works, it lets you incrementally ratchet up the restrictions as various stages of the startup/run loop of a program progress, so you don't have to specify everything a program might ever try to do ahead of time, and have that huge pool of permissions persist throughout the entire life of the program.
As I understand it, seccomp can do what unveil and pledge can do, just with a much more complicated API. I wonder if anyone is working on a pledge/unveil compatible wrapper API for seccomp...
unveil(2) would be very challenging, if not impossible. Both would require proper Linux integration and kernel implementation.
For example your process scheduler could fork, create a new mount namespace with unshare() , bind-mount the temporary workdir to a known location, drop privileges and the exec the target program.
If you aren't executing third party code, don't worry about it? And if you are; provide an api third parties must use that validates paths before r/w files? Disallow '..' './' '~/' etc?
https://www.youtube.com/watch?v=wdeFQJXd_II&list=PLbx1j8Pb6u...
For the chrome case, you can currently only access ~/Downloads. This involves changing our habits as /users/. If you need to access files from outside this directory, you need to open a terminal and copy/move files there yourself.
That said, for now.. the chromium package does actually have a external unveil policy in /etc/chromium/unveil.* for each process type (main, gpu, renderer, etc). But that won't necessarily always be there.
In other words: We have to do what the software requires of us, not the other way round.
That is where Apple's approach is probably better: have an entirely separate process bridging the software and specific tasks. The bridge process should be easier to audit (and can have its own restrictions and sanboxing e.g. the only things it needs are to communicate over a socket and see the filesystem), and the browser never gets to see the filesystem.
For example, the GPU and plugin processes can only act on file descriptors received this way:
https://github.com/openbsd/ports/blob/master/www/chromium/pa...
But this alone doesn't protect this "bridge" process itself from accessing the filesystem, this includes your SSH keys, which is where unveil() comes in.
Of course not, but the point is that the bridge process only comes in contact with the user and a software invoking it, and its sole purpose is to pick a file on the filesystem.
Including your ssh pubkey if that's what you need to select/upload.
Is there any way to make use of unveil() at the command line to accomplish the above? To clarify, I suppose unveil() is not intended to be used that way, but will there be some sort of wrapper that allows us to restrict a third-party already-compiled program to certain directories only?
I really hope a similar util lands on GNU/Linux one day...
To restrict it before the program even starts would require a huge amount of what amounts to spurious unveils once the program starts its main operating loop, and really misses the point of the abstraction its trying to create.
This is exactly where approaches like selinux fail, they rely on static distro/user defined policy that can only ever be set from before the program starting, and thus must encapsulate everything the program might ever try to do. With unveil and pledge, the program itself indicates what each branch needs to do, and can much much more tightly restrict itself.
Using the unveil() is a completely different approach that should yield less false positive and involve less hassle. There is just one caveat: you need to make sure the binary hasn't been tampered with. If someone replaced/modified it, SELinux will block unauthorized behavior, whereas counting on unveil() still working in this case might let you down.
Might is an understatement. If somebody changed what you run, there’s no guarantee at all that it even still calls unveil.
However, I would think chances that your system is set up such that third parties can tamper with your binaries are about equal to those that the system setup allows them to ‘tweak’ your SELinux configurations.