3 ms·
This seems like a pretty major limitation of the interface: QUOTE It is important to consider that directory results are remembered at the time of a call to u
by avar 8y ago
This seems like a pretty major limitation of the interface:
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.
- vesak 8y agoWhy would the user do that? You don't move away a program's data dir while it's running and expect everything to just work, right?
- avar 8y agoThere are programs that rely on this working, which would continue to work with unveil(2) if it wasn't for this artificial limitation. 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.
- vesak 8y agoEven though the function's name is unveil, I'm thinking that this feature is for denying access rather than allowing. In that sense, I think it's a useful protection measure that the new directory doesn't suddenly become visible in your example -- smells like an attack vector. Also, I would guess that doing these checks via pathname every time files are accessed would have to be pretty bad for performance. If such a feature is wanted, I would think that the daemon can just call unveil again when it's doing an automatic recovery.
- avar 8y agoPerhaps it's for performance reasons. I don't see how it would be an attack vector to permit /some/path/dir to be read after the "dir" is re-created or replaced. You'd guard against that with standard nix ACLs. If someone can replace the directory they can also modify it. > [...]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.
- Ohjohng8 8y agoI think in linux you could already do this if you wanted to. 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.
- unilynx 8y agoin linux, systemd can do that when starting a service. the config is in a unit file, not a library 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
- blattimwind 8y ago> or just an inode? But then how is inode re-use handled? inodes have generations. Each reuse increments generation. (ino, generation) tuple uniquely identifies an object within the file system.
- avar 8y agoThanks. I was wrong about that, I was confusing this with PID re-use.
- dchest 8y agoThis is correct, however generation is mostly needed (and was created) for NFS, and rarely used for anything else. Unveal lives in kernel, it just keeps a reference to vnode (https://github.com/openbsd/src/blob/03249988e9bbffb48a56883955bda316855561b9/sys/sys/proc.h#L428-L433 https://github.com/openbsd/src/blob/03249988e9bbffb48a568839...)
- Ohjohng8 8y agoI assume that is intended as a security feature to prevent TOCTTOU bugs, especially when a process does not use the *at family of syscalls. I.e. a malicious process can't swap directories under your nose.
- TheDong 8y agoNote, this is the same as bindmounts in linux. $ 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.