On 2020-09-22 10:10, Theo de Raadt wrote: > I gotta comment.. Thank you for your feedback.
>>> The tool makes essential use of the execpromises argument >>> to pledge(2), so that it can sandbox the program it executes. >> >> This appears to conflict with the basic idea of pledge(2), which >> is for the *programmer* to first do simple preparatory work that >> requires full syscall access, then pledge(2) according to a precise >> understanding of what the program is supposed to do during normal >> operation. Usually, it is not possible to properly pledge(2) a >> program without designing it for pledge(2), sometimes called >> "hoisting". > > In the simplest cases, pledge removes some support operations, in > particular relating options hiding under ioctl. More complicated > pledge use cases follow a "successive drop" feature as program > initialization starts. > > With this kind of pledge-from-parent, the sophisticated cases are > impossible. I actually agree with this. Designing a program with pledge in mind is always better. However, that requires that the program be trusted, and there still may be some corner cases in which I can tighten down the pledge more than the program itself can. For example, ftp(1) must consider all possible places the sysadmin might put their CA certificates. It also might need to execute commands as part of interactive mode. However, when using ftp(1) as part of my modified sysupgrade(8), I know that the CA certificates will be in the standard location, and that ftp(1) will never need to exec(2) another program. So I can sandbox ftp(1) more than ftp(1) can reasonably sandbox itself. To be fair, one could argue that ftp(1) should have a command-line option that enables these lockdown options and disables the features unsupported under this mode. However, that would require that every single program grow additional options, whereas pledge(1) is very simple. > Sadly, almost all programs bigger than "cat" or "more" require a > huge pledge for initalization, especially if they do accept environment > variables (themselves or in libc), or use the network. So the pledge > will always be huge. > >> As a corollary, pledging a program from the outside, >> without changing the code that is compiled, usually does not >> provide significant benefit. > > I agree. I wrote a command like this myself, when I developed the > execpromises featureset. I am ready to delete exec promises because > I consider it a failed experiment. > > I found that useful application was extremely rare. I could not even > use execpromises properly in programs like "disklabel -E" to control > the behaviour of the $EDITOR. That isn’t actually not the main use case for pledge(1), at least for me. The main use-case is to sandbox untrusted, potentially malicious executables. I do not know any way to implement something like the Rust Playground securely on OpenBSD without using execpromises, at least without requiring at least part of the application to run as root. With execpromises, sandboxing untrusted executables is trivial. That’s what pledge(1) is for. Much of the benefit of execpromises actually comes from being able to preserve unveil(2) across execve(2). I am perfectly okay giving untrusted code "stdio rpath wpath cpath unix proc_exec", for example, if unveil(2) has prevented it from accessing anything important on the filesystem. The other benefit of execpromises is that it reduces the attack surface against the kernel, which makes sandbox escapes significantly less likely. > I've been down this road before, and it doesn't work. You can come > to this conclusion by finding a program, and trying *absolutely everything* > it does, including environment variables and file openings and such that > the libraries do. You'll begin with optimism, but eventually add more and > more pledges. In my main use-case, I know what pledges I am willing to allow untrusted code to have. If it tries to do something that the sandbox doesn’t allow, it *should* fail. I expect that some functionality *will* break. That’s okay in my application. > There's one more thing I want to mention: pledge("shitload of options") > intentionally is a non-POSIX compliant environment. Command line users > won't understand the edge conditions. I agree. pledge(1) is meant for advanced users and for those implementing sandboxes. Sincerely, Demi M. Obenour