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"); }