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

Reply via email to