Improve pledge() and unveil()

The pledge.com command now supports the new [WIP] unveil() support. For
example, to strongly sandbox our command for listing directories.

    o//tool/build/assimilate.com o//examples/ls.com
    pledge.com -v /etc -p 'stdio rpath' o//examples/ls.com /etc

This file system sandboxing is going to be perfect for us, because APE
binaries are self-contained static executables that really don't use the
filesystem that much. On the other hand, with non-static executables,
sandboxing is going to be more difficult. For example, here's how to
sandbox the `ls` command on the latest Alpine:

    pledge.com -v rx:/lib -v /usr/lib -v /etc -p 'stdio rpath exec' ls /etc

This change fixes the `execpromises` API with pledge().

This change also adds unix.unveil() to redbean.

Fixes #494
This commit is contained in:
Justine Tunney 2022-07-18 07:23:15 -07:00
parent b1d9d11be1
commit e81edf7b04
19 changed files with 535 additions and 150 deletions

View file

@ -27,12 +27,14 @@
#include "libc/intrin/kprintf.h"
#include "libc/macros.internal.h"
#include "libc/math.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/kcpuids.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/sysconf.h"
#include "libc/sock/sock.h"
#include "libc/sock/struct/pollfd.h"
#include "libc/stdio/stdio.h"
#include "libc/stdio/strlist.internal.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/ioprio.h"
#include "libc/sysv/consts/o.h"
@ -49,17 +51,18 @@ STATIC_YOINK("strerror_wr");
#define USAGE \
"\
usage: pledge.com [-hnN] PROG ARGS...\n\
-h show help\n\
-g GID call setgid()\n\
-u UID call setuid()\n\
-c PATH call chroot()\n\
-n maximum niceness\n\
-N don't normalize file descriptors\n\
-C SECS set cpu limit [default: inherited]\n\
-M BYTES set virtual memory limit [default: 4gb]\n\
-P PROCS set process limit [default: GetCpuCount()*2]\n\
-F BYTES set individual file size limit [default: 4gb]\n\
-p PLEDGE may contain any of following separated by spaces\n\
-h show help\n\
-g GID call setgid()\n\
-u UID call setuid()\n\
-c PATH call chroot()\n\
-v [PERM:]PATH call unveil(PATH,PERM) where PERM can have rwxc\n\
-n set maximum niceness\n\
-N don't normalize file descriptors\n\
-C SECS set cpu limit [default: inherited]\n\
-M BYTES set virtual memory limit [default: 4gb]\n\
-P PROCS set process limit [default: GetCpuCount()*2]\n\
-F BYTES set individual file size limit [default: 4gb]\n\
-p PLEDGE may contain any of following separated by spaces\n\
- stdio: allow stdio and benign system calls\n\
- rpath: read-only path ops\n\
- wpath: write path ops\n\
@ -68,6 +71,7 @@ usage: pledge.com [-hnN] PROG ARGS...\n\
- flock: file locks\n\
- tty: terminal ioctls\n\
- recvfd: allow SCM_RIGHTS\n\
- sendfd: allow SCM_RIGHTS\n\
- fattr: allow changing some struct stat bits\n\
- inet: allow IPv4 and IPv6\n\
- unix: allow local sockets\n\
@ -75,9 +79,9 @@ usage: pledge.com [-hnN] PROG ARGS...\n\
- proc: allow fork, clone and friends\n\
- thread: allow clone\n\
- id: allow setuid and friends\n\
- exec: allow executing ape binaries\n\
- exec: make execution more permissive\n\
\n\
pledge.com v1.o\n\
pledge.com v1.1\n\
copyright 2022 justine alexandra roberts tunney\n\
https://twitter.com/justinetunney\n\
https://linkedin.com/in/jtunney\n\
@ -102,6 +106,11 @@ long g_proquota;
const char *g_chroot;
const char *g_promises;
struct {
int n;
char **p;
} unveils;
static void GetOpts(int argc, char *argv[]) {
int opt;
struct sysinfo si;
@ -111,7 +120,7 @@ static void GetOpts(int argc, char *argv[]) {
g_fszquota = 4 * 1000 * 1000 * 1000;
g_memquota = 4L * 1024 * 1024 * 1024;
if (!sysinfo(&si)) g_memquota = si.totalram;
while ((opt = getopt(argc, argv, "hnNp:u:g:c:C:P:M:F:")) != -1) {
while ((opt = getopt(argc, argv, "hnNp:u:g:c:C:P:M:F:v:")) != -1) {
switch (opt) {
case 'n':
g_nice = true;
@ -147,6 +156,10 @@ static void GetOpts(int argc, char *argv[]) {
g_promises = optarg;
}
break;
case 'v':
unveils.p = realloc(unveils.p, ++unveils.n * sizeof(*unveils.p));
unveils.p[unveils.n - 1] = optarg;
break;
case 'h':
case '?':
write(1, USAGE, sizeof(USAGE) - 1);
@ -157,9 +170,8 @@ static void GetOpts(int argc, char *argv[]) {
}
}
if (!g_promises) {
g_promises = "stdio rpath execnative";
g_promises = "stdio rpath";
}
g_promises = xstrcat(g_promises, ' ', "execnative");
}
const char *prog;
@ -290,6 +302,7 @@ void MakeProcessNice(void) {
}
int main(int argc, char *argv[]) {
int i;
bool hasfunbits;
int useruid, usergid;
int owneruid, ownergid;
@ -430,14 +443,50 @@ int main(int argc, char *argv[]) {
}
}
if (unveils.n) {
if (unveil(prog, "rx") == -1) {
kprintf("error: unveil(0, 0) failed: %m\n", prog, "rx");
_Exit(20);
}
if (strstr(g_promises, "exec") && isexecutable("/usr/bin/ape")) {
if (unveil("/usr/bin/ape", "rx") == -1) {
kprintf("error: unveil(0, 0) failed: %m\n", "/usr/bin/ape", "rx");
_Exit(20);
}
}
for (i = 0; i < unveils.n; ++i) {
char *s, *t;
const char *path;
const char *perm;
s = unveils.p[i];
if ((t = strchr(s, ':'))) {
*t = 0;
perm = s;
path = t + 1;
} else {
perm = "r";
path = s;
}
if (unveil(path, perm) == -1) {
kprintf("error: unveil(%#s, %#s) failed: %m\n", path, perm);
_Exit(20);
}
}
if (unveil(0, 0) == -1) {
kprintf("error: unveil(0, 0) failed: %m\n");
_Exit(20);
}
}
// apply sandbox
if (pledge(g_promises, 0) == -1) {
g_promises = xstrcat(g_promises, ' ', "execnative");
if (pledge(g_promises, g_promises) == -1) {
kprintf("error: pledge(%#s) failed: %m\n", g_promises);
_Exit(19);
}
// launch program
__sys_execve(prog, argv + optind, environ);
execve(prog, argv + optind, environ);
kprintf("%s: execve failed: %m\n", prog);
return 127;
}

View file

@ -3619,7 +3619,7 @@ UNIX MODULE
See the unix.Rusage section below for details on returned fields.
unix.pledge([promises:str])
unix.pledge([promises:str[, execpromises:str]])
├─→ true
└─→ nil, unix.Errno
@ -3654,11 +3654,15 @@ UNIX MODULE
restrictions need to be loosened.
`promises` is a string that may include any of the following groups
delimited by spaces.
delimited by spaces. This list has been curated to focus on the
system calls for which this module provides wrappers. See the
Cosmopolitan Libc pledge() documentation for a comprehensive and
authoritative list of raw system calls. Having the raw system call
list may be useful if you're executing foreign programs.
stdio
Allows read, write, send, recv, recvfrom, recvmsg, close,
Allows read, write, send, recv, recvfrom, close,
clock_getres, clock_gettime, dup, dup2, dup3, fchdir, fstat,
fsync, fdatasync, ftruncate, getdents, getegid, getrandom,
geteuid, getgid, getgroups, getitimer, getpgid, getpgrp, getpid,
@ -3721,12 +3725,49 @@ UNIX MODULE
exec
Allows execve.
Allows execve, access.
If this is used then APE binaries should be assimilated in order
to work on OpenBSD. On Linux, mmap() will be loosened up to allow
creating PROT_EXEC memory (for APE loader) and system call origin
verification won't be activated.
On Linux this also weakens some security to permit running APE
binaries. However on OpenBSD they must be assimilate beforehand.
On Linux, mmap() will be loosened up to allow creating PROT_EXEC
memory (for APE loader) and system call origin verification won't
be activated.
execnative
Allows execve, execveat.
Can only be used to run native executables; you won't be able to
run APE binaries. mmap() and mprotect() are still prevented from
creating executable memory. System call origin verification can't
be enabled. If you always assimilate your APE binaries, then this
should be preferred. On OpenBSD this will be rewritten to be
"exec".
`execpromises` only matters if "exec" or "execnative" are specified
in `promises`. In that case, this specifies the promises that'll
apply once execve() happens. If this is NULL then the default is
used, which is unrestricted. OpenBSD allows child processes to escape
the sandbox (so a pledged OpenSSH server process can do things like
spawn a root shell). Linux however requires monotonically decreasing
privileges. This function will will perform some validation on Linux
to make sure that `execpromises` is a subset of `promises`. Your libc
wrapper for execve() will then apply its SECCOMP BPF filter later.
Since Linux has to do this before calling sys_execve(), the executed
process will be weakened to have execute permissions too.
unix.unveil(path:str, permissions:str)
├─→ true
└─→ nil, unix.Errno
Unveil parts of a restricted filesystem view, e.g.
unix.unveil(".", "r")
unix.unveil(nil, nil)
This can be used for sandboxing file system access.
Unveil support is a work in progress.
unix.gmtime(unixts:int)
├─→ year,mon,mday,hour,min,sec,gmtoffsec,wday,yday,dst:int,zone:str