For posterity, I must make one correction to the earlier execpromises
example I gave. If you do not care about sandboxing Rust development,
there's no need to read this, and apologies for the noise.
In my example, I unveiled "~/.cargo" with "crwx" permissions. That's
maybe narrowly okay if you're strictly going to use Cargo in a sandboxed
manner, but it's not sufficient if you'll use it both with and without
sandboxing, and it doesn't fully isolate one sandboxed project from
another.
A better approach is to use a separate "$CARGO_HOME" per project. If you
do that, you can make it so only the sandboxed project itself is
writable, and malicious crates cannot tamper with a shared Cargo home
directory. I've included an example of such an approach below.
Disclaimer: I've used neither of these examples in anger, and I mostly
wrote the former to demonstrate the utility of execpromises for
sandboxing. You may have to unveil additional paths to make this work
for some crates, and there may be other issues I haven't considered. You
also may want to further isolate things by clearing environment
variables before entering the sandbox.
-wq
---
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
static _Noreturn void die_alloc(void);
static _Noreturn void die_errno(void);
int
main(int const argc, char **const argv)
{
if (argc != 2 || !argv[1]) {
fputs("usage: rbox DIR\n", stderr);
exit(1);
}
char *const home_path = getenv("HOME");
if (!home_path) {
fputs("error: HOME environment variable not set\n", stderr);
exit(1);
}
char *const alloced_sandbox_path = realpath(argv[1], NULL);
if (!alloced_sandbox_path) {
fputs("error: could not resolve provided path\n", stderr);
exit(1);
}
if (chdir(alloced_sandbox_path)) {
die_errno();
}
char *alloced_cargo_path;
if (
asprintf(
&alloced_cargo_path,
"%s/.cargo",
alloced_sandbox_path
) == -1
) {
die_alloc();
}
char *alloced_tmp_path;
if (
asprintf(
&alloced_tmp_path,
"%s/target",
alloced_sandbox_path
) == -1
) {
die_alloc();
}
if ( unveil("/bin", "rx")
|| unveil("/etc/ssl/cert.pem", "r")
|| unveil("/usr", "r")
|| unveil("/usr/bin/ar", "rx")
|| unveil("/usr/bin/cc", "rx")
|| unveil("/usr/bin/ld", "rx")
|| unveil("/usr/local", "rx")
|| unveil(alloced_sandbox_path, "crwx")
|| unveil(NULL, NULL)
) {
die_errno();
}
if (
pledge(
NULL,
"stdio rpath wpath cpath fattr inet "
"flock unix dns tty proc exec error"
)
) {
die_errno();
}
if ( setenv("CARGO_HOME", alloced_cargo_path, 1)
|| setenv("LD_LIBRARY_PATH", "/usr/local/lib", 1)
|| setenv("TMPDIR", alloced_tmp_path, 1)
) {
die_errno();
}
free(alloced_sandbox_path);
free(alloced_cargo_path);
free(alloced_tmp_path);
puts("\033[92mEntering sandbox.\033[0m");
fflush(stdout);
pid_t const pid = fork();
if (pid == -1) {
die_errno();
}
if (!pid) {
if (execl("/bin/sh", "/bin/sh", NULL)) {
die_errno();
}
}
int status;
waitpid(pid, &status, 0);
puts("\033[93mLeaving sandbox.\033[0m");
if (WIFEXITED(status)) {
exit(WEXITSTATUS(status));
}
fputs("error: sandbox did not exit normally\n", stderr);
exit(1);
}
static _Noreturn void
die_alloc(void)
{
fputs("error: insufficient memory\n", stderr);
exit(1);
}
static _Noreturn void
die_errno(void)
{
perror("error");
exit(1);
}