Yesterday, I wrote an unprivileged sandboxing tool for OpenBSD, based
on pledge(2) and unveil(2).  I have included the complete C source
code below, and also attached it in case this makes it easier to use.

I called it pledge(1), but am open to suggestions for a better
name.  The tool makes essential use of the execpromises argument
to pledge(2), so that it can sandbox the program it executes.
While I understand that this argument is slated for removal,
pledge(1) would not be possible without it.

pledge(1) tries to be smart: it will always unveil the target program
for execution, and will always unveil the dynamic linker and system
library directory for reading.  This avoids confusing errors from
execve(2), as well as spurious crashes from the executed program.

pledge(1) is mainly intended for when the program being sandboxed
is either potentially malicious, or doesn’t use pledge(2) and
unveil(2) to the fullest extent possible.  For example, ftp(1)
can’t use unveil(2) without significant additional complexity,
as it doesn’t know in advance where the TLS certificates are
located.  I often know that in advance, however, so I can apply
tighter sandboxing than ftp(1) can on its own.  I can also use
pledge(1) to sandbox untrusted code, for instance in a program like
the Rust Playground (https://play.rust-lang.org) that (by design)
needs to execute arbitrary user-supplied code.  While it might
also be possible to achieve this using chroots and routing domains,
using them requires root privileges, whereas pledge(1) works as an
unprivileged user.

There are several potential improvements to be made, but I believe
that the current code is already useful.  I use it to sandbox ftp(1)
in a modified version of sysupgrade(8), for example.  Bug reports
and feature requests would be greatly appreciated, as would general
suggestions for improving the code.  If there is interest, I would
also like to turn pledge(1) into a proper OpenBSD package at some
point, so that it can be installed using pkg_add(1).

The source code is less than 50 lines, so I have included it inline
to make it easier for others to comment on it, if they wish.
---
#include <unistd.h>
#include <string.h>
#include <err.h>

extern char **environ;

int
main (int argc, char **argv) {
        char *end_perms, *arg0 = NULL, *progpath = NULL;
        int ch;

        while ((ch = getopt(argc, argv, "u:0:")) != -1) {
                switch (ch) {
                case '0':
                        if (arg0 != NULL)
                                errx(3, "arg0 cannot be set twice");
                        arg0 = optarg;
                        break;
                case 'u':
                        if ((end_perms = strchr(optarg, ':')) == NULL)
                                errx(3, "no colon after permissions");
                        *end_perms = '\0';
                        if (unveil(end_perms + 1, optarg))
                                err(2, "unveil");
                        break;
                }
        }

        if (argc - optind < 2)
                errx(3, "not enough arguments");
        argc -= optind;
        argv += optind;
        if (optind < 2 || strcmp(argv[-1], "--") != 0)
                errx(3, "no -- before pledge: optind %d", optind);
        progpath = argv[1];
        if (arg0 != NULL)
                argv[1] = arg0;
        if (unveil("/usr/libexec/ld.so", "r") ||
            unveil("/usr/lib", "r") ||
            unveil(argv[1], "x") ||
            unveil(NULL, NULL))
                err(2, "unveil");
        if (pledge(NULL, *argv))
                err(2, "pledge");
        execve(progpath, argv + 1, environ);
        err(1, "execve");
}
---

Sincerely,

Demi M. Obenour
#include <unistd.h>
#include <string.h>
#include <err.h>

extern char **environ;

int
main (int argc, char **argv) {
	char *end_perms, *arg0 = NULL, *progpath = NULL;
	int ch;

	while ((ch = getopt(argc, argv, "u:0:")) != -1) {
		switch (ch) {
		case '0':
			if (arg0 != NULL)
				errx(3, "arg0 cannot be set twice");
			arg0 = optarg;
			break;
		case 'u':
			if ((end_perms = strchr(optarg, ':')) == NULL)
				errx(3, "no colon after permissions");
			*end_perms = '\0';
			if (unveil(end_perms + 1, optarg))
				err(2, "unveil");
			break;
		}
	}

	if (argc - optind < 2)
		errx(3, "not enough arguments");
	argc -= optind;
	argv += optind;
	if (optind < 2 || strcmp(argv[-1], "--") != 0)
		errx(3, "no -- before pledge: optind %d", optind);
	progpath = argv[1];
	if (arg0 != NULL)
		argv[1] = arg0;
	if (unveil("/usr/libexec/ld.so", "r") ||
	    unveil("/usr/lib", "r") ||
	    unveil(argv[1], "x") ||
	    unveil(NULL, NULL))
		err(2, "unveil");
	if (pledge(NULL, *argv))
		err(2, "pledge");
	execve(progpath, argv + 1, environ);
	err(1, "execve");
}

Reply via email to