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

@ -17,6 +17,7 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/bits/likely.h"
#include "libc/bits/weaken.h"
#include "libc/calls/calls.h"
#include "libc/calls/strace.internal.h"
#include "libc/calls/syscall-nt.internal.h"
@ -24,10 +25,13 @@
#include "libc/dce.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/promises.internal.h"
#include "libc/log/libfatal.internal.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"
int sys_pledge_linux(unsigned long);
/**
* Replaces current process with program.
*
@ -66,7 +70,13 @@ int execve(const char *prog, char *const argv[], char *const envp[]) {
}
#endif
if (!IsWindows()) {
rc = 0;
if (IsLinux() && __execpromises && weaken(sys_pledge_linux)) {
rc = weaken(sys_pledge_linux)(__execpromises);
}
if (!rc) {
rc = sys_execve(prog, argv, envp);
}
} else {
rc = sys_execve_nt(prog, argv, envp);
}

View file

@ -22,6 +22,7 @@
#include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/sysv/consts/at.h"
@ -36,18 +37,27 @@
* @param path is a filename or directory
* @param mode can be R_OK, W_OK, X_OK, F_OK
* @param flags can have AT_EACCESS, AT_SYMLINK_NOFOLLOW
* @note on Linux flags is only supported on Linux 5.8+
* @return 0 if ok, or -1 and sets errno
* @asyncsignalsafe
*/
int faccessat(int dirfd, const char *path, int mode, uint32_t flags) {
int rc;
int e, rc;
if (IsAsan() && !__asan_is_valid(path, 1)) {
rc = efault();
} else if (weaken(__zipos_notat) &&
weaken(__zipos_notat)(dirfd, path) == -1) {
rc = -1; /* TODO(jart): implement me */
} else if (!IsWindows()) {
e = errno;
if (!flags) goto NoFlags;
if ((rc = sys_faccessat2(dirfd, path, mode, flags)) == -1) {
if (errno == ENOSYS) {
errno = e;
NoFlags:
rc = sys_faccessat(dirfd, path, mode, flags);
}
}
} else {
rc = sys_faccessat_nt(dirfd, path, mode, flags);
}

View file

@ -33,6 +33,7 @@ i32 sys_dup2(i32, i32) hidden;
i32 sys_dup3(i32, i32, i32) hidden;
i32 sys_execve(const char *, char *const[], char *const[]) hidden;
i32 sys_faccessat(i32, const char *, i32, u32) hidden;
i32 sys_faccessat2(i32, const char *, i32, u32) hidden;
i32 sys_fadvise(i32, i64, i64, i32) hidden;
i32 sys_fchdir(i32) hidden;
i32 sys_fchmod(i32, u32) hidden;
@ -95,6 +96,7 @@ i32 sys_tkill(i32, i32, void *) hidden;
i32 sys_truncate(const char *, u64, u64) hidden;
i32 sys_uname(char *) hidden;
i32 sys_unlinkat(i32, const char *, i32) hidden;
i32 sys_unveil(const char *, const char *) hidden;
i64 sys_copy_file_range(i32, long *, i32, long *, u64, u32) hidden;
i64 sys_getrandom(void *, u64, u32) hidden;
i64 sys_pread(i32, void *, u64, i64, i64) hidden;
@ -111,7 +113,6 @@ u32 sys_geteuid(void) hidden;
u32 sys_getgid(void) hidden;
u32 sys_getuid(void) hidden;
u32 sys_umask(u32) hidden;
i32 sys_unveil(const char *, const char *) hidden;
void *__sys_mmap(void *, u64, u32, u32, i64, i64, i64) hidden;
void *sys_mremap(void *, u64, u64, i32, void *) hidden;
void sys_exit(int) hidden;

View file

@ -19,3 +19,4 @@
#include "libc/intrin/promises.internal.h"
unsigned long __promises;
unsigned long __execpromises;

View file

@ -1,33 +1,33 @@
#ifndef COSMOPOLITAN_LIBC_INTRIN_PROMISES_H_
#define COSMOPOLITAN_LIBC_INTRIN_PROMISES_H_
#define PROMISE_DEFAULT 0
#define PROMISE_STDIO 1
#define PROMISE_RPATH 2
#define PROMISE_WPATH 3
#define PROMISE_CPATH 4
#define PROMISE_DPATH 5
#define PROMISE_FLOCK 6
#define PROMISE_FATTR 7
#define PROMISE_INET 8
#define PROMISE_UNIX 9
#define PROMISE_DNS 10
#define PROMISE_TTY 11
#define PROMISE_RECVFD 12
#define PROMISE_PROC 13
#define PROMISE_THREAD 14
#define PROMISE_EXEC 15
#define PROMISE_EXECNATIVE 16
#define PROMISE_ID 17
#define PROMISE_UNVEIL 18
#define PROMISE_MAX 18
#define PROMISE_STDIO 0
#define PROMISE_RPATH 1
#define PROMISE_WPATH 2
#define PROMISE_CPATH 3
#define PROMISE_DPATH 4
#define PROMISE_FLOCK 5
#define PROMISE_FATTR 6
#define PROMISE_INET 7
#define PROMISE_UNIX 8
#define PROMISE_DNS 9
#define PROMISE_TTY 10
#define PROMISE_RECVFD 11
#define PROMISE_PROC 12
#define PROMISE_THREAD 13
#define PROMISE_EXEC 14
#define PROMISE_EXECNATIVE 15
#define PROMISE_ID 16
#define PROMISE_UNVEIL 17
#define PROMISE_SENDFD 18
#define PLEDGED(x) (~__promises & (1L << PROMISE_##x))
#define PLEDGED(x) ((~__promises >> PROMISE_##x) & 1)
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
hidden extern unsigned long __promises;
hidden extern unsigned long __execpromises;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -25,7 +25,6 @@
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/calls/syscall_support-sysv.internal.h"
#include "libc/dce.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/promises.internal.h"
#include "libc/limits.h"
#include "libc/macros.internal.h"
@ -157,6 +156,7 @@ static const uint16_t kPledgeLinuxRpath[] = {
__NR_linux_fstatat, //
__NR_linux_access, //
__NR_linux_faccessat, //
__NR_linux_faccessat2, //
__NR_linux_readlink, //
__NR_linux_readlinkat, //
__NR_linux_statfs, //
@ -173,6 +173,7 @@ static const uint16_t kPledgeLinuxWpath[] = {
__NR_linux_fstatat, //
__NR_linux_access, //
__NR_linux_faccessat, //
__NR_linux_faccessat2, //
__NR_linux_readlinkat, //
__NR_linux_chmod, //
__NR_linux_fchmod, //
@ -254,6 +255,10 @@ static const uint16_t kPledgeLinuxRecvfd[] = {
__NR_linux_recvmsg, //
};
static const uint16_t kPledgeLinuxSendfd[] = {
__NR_linux_sendmsg, //
};
static const uint16_t kPledgeLinuxProc[] = {
__NR_linux_fork, //
__NR_linux_vfork, //
@ -270,6 +275,8 @@ static const uint16_t kPledgeLinuxProc[] = {
static const uint16_t kPledgeLinuxThread[] = {
__NR_linux_clone, //
__NR_linux_futex, //
__NR_linux_set_robust_list, //
__NR_linux_get_robust_list, //
};
static const uint16_t kPledgeLinuxId[] = {
@ -313,7 +320,6 @@ static const struct Pledges {
const uint16_t *syscalls;
const size_t len;
} kPledgeLinux[] = {
[PROMISE_DEFAULT] = {"default", PLEDGE(kPledgeLinuxDefault)}, //
[PROMISE_STDIO] = {"stdio", PLEDGE(kPledgeLinuxStdio)}, //
[PROMISE_RPATH] = {"rpath", PLEDGE(kPledgeLinuxRpath)}, //
[PROMISE_WPATH] = {"wpath", PLEDGE(kPledgeLinuxWpath)}, //
@ -326,13 +332,13 @@ static const struct Pledges {
[PROMISE_DNS] = {"dns", PLEDGE(kPledgeLinuxDns)}, //
[PROMISE_TTY] = {"tty", PLEDGE(kPledgeLinuxTty)}, //
[PROMISE_RECVFD] = {"recvfd", PLEDGE(kPledgeLinuxRecvfd)}, //
[PROMISE_SENDFD] = {"sendfd", PLEDGE(kPledgeLinuxSendfd)}, //
[PROMISE_PROC] = {"proc", PLEDGE(kPledgeLinuxProc)}, //
[PROMISE_THREAD] = {"thread", PLEDGE(kPledgeLinuxThread)}, //
[PROMISE_EXEC] = {"exec", PLEDGE(kPledgeLinuxExec)}, //
[PROMISE_EXECNATIVE] = {"execnative", PLEDGE(kPledgeLinuxExec2)}, //
[PROMISE_ID] = {"id", PLEDGE(kPledgeLinuxId)}, //
[PROMISE_UNVEIL] = {"unveil", PLEDGE(kPledgeLinuxUnveil)}, //
[PROMISE_MAX + 1] = {0}, //
};
static const struct sock_filter kFilterStart[] = {
@ -342,6 +348,13 @@ static const struct sock_filter kFilterStart[] = {
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
// each filter assumes ordinal is already loaded into accumulator
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)),
// Forbid some system calls with ENOSYS (rather than EPERM)
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_openat2, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (38 & SECCOMP_RET_DATA)),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_clone3, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (38 & SECCOMP_RET_DATA)),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_statx, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (38 & SECCOMP_RET_DATA)),
};
static const struct sock_filter kFilterEnd[] = {
@ -957,8 +970,8 @@ static bool AllowFchmodat(struct Filter *f) {
return AppendFilter(f, PLEDGE(fragment));
}
static bool AppendPledge(struct Filter *f, const uint16_t *p, size_t len, bool needmapexec,
bool needmorphing) {
static bool AppendPledge(struct Filter *f, const uint16_t *p, size_t len,
bool needmapexec, bool needmorphing) {
int i;
for (i = 0; i < len; ++i) {
switch (p[i]) {
@ -1045,83 +1058,100 @@ static bool AppendPledge(struct Filter *f, const uint16_t *p, size_t len, bool n
return true;
}
static int FindPromise(const struct Pledges *p, const char *name, size_t *len) {
int i;
for (i = 0; p[i].name; ++i) {
if (!strcasecmp(name, p[i].name)) {
*len = p[i].len;
return i;
}
}
return -1;
}
static int sys_pledge_linux(const char *promises, const char *execpromises) {
bool ok;
int rc = -1;
size_t plen;
int promise;
int sys_pledge_linux(unsigned long ipromises) {
bool ok = true;
int i, rc = -1;
bool needmapexec;
bool needexecnative;
bool needmorphing;
bool needexecnative;
struct Filter f = {0};
const uint16_t *pledge;
unsigned long ipromises = -1;
char *s, *tok, *state, *start;
if (execpromises) return einval();
needmapexec = strstr(promises, "exec");
needmorphing = strstr(promises, "thread");
needexecnative = strstr(promises, "execnative");
if ((start = s = strdup(promises)) && AppendFilter(&f, kFilterStart, ARRAYLEN(kFilterStart)) &&
ipromises = ~ipromises;
needmapexec = (ipromises >> PROMISE_EXEC) & 1;
needmorphing = (ipromises >> PROMISE_THREAD) & 1;
needexecnative = (ipromises >> PROMISE_EXECNATIVE) & 1;
if (AppendFilter(&f, kFilterStart, ARRAYLEN(kFilterStart)) &&
(needmapexec || needexecnative || AppendOriginVerification(&f)) &&
AppendPledge(&f, kPledgeLinuxDefault, ARRAYLEN(kPledgeLinuxDefault), needmapexec,
needmorphing)) {
for (ok = true; (tok = strtok_r(start, " \t\r\n", &state)); start = 0) {
if ((promise = FindPromise(kPledgeLinux, tok, &plen)) != -1) {
pledge = kPledgeLinux[promise].syscalls;
ipromises &= ~(1ULL << promise);
} else {
AppendPledge(&f, kPledgeLinuxDefault, ARRAYLEN(kPledgeLinuxDefault),
needmapexec, needmorphing)) {
for (i = 0; i < ARRAYLEN(kPledgeLinux); ++i) {
if ((ipromises & (1ul << i)) && kPledgeLinux[i].name) {
ipromises &= ~(1ul << i);
if (!AppendPledge(&f, kPledgeLinux[i].syscalls, kPledgeLinux[i].len,
needmapexec, needmorphing)) {
ok = false;
rc = einval();
break;
}
if (!AppendPledge(&f, pledge, plen, needmapexec, needmorphing)) {
ok = false;
break;
}
}
if (ipromises) {
ok = false;
rc = einval();
}
if (ok && AppendFilter(&f, kFilterEnd, ARRAYLEN(kFilterEnd)) &&
(rc = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) != -1) {
struct sock_fprog sandbox = {.len = f.n, .filter = f.p};
rc = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &sandbox);
}
if (!rc) {
__promises = ipromises;
}
}
free(f.p);
free(s);
return rc;
}
static void SetPromises(const char *promises) {
static int FindPromise(const char *name) {
int i;
for (i = 0; i < ARRAYLEN(kPledgeLinux); ++i) {
if (!strcasecmp(name, kPledgeLinux[i].name)) {
return i;
}
}
STRACE("unknown promise %s", name);
return -1;
}
static int ParsePromises(const char *promises, unsigned long *out) {
int rc = 0;
int promise;
size_t plen;
char *tok, *state, *start;
unsigned long ipromises = -1;
unsigned long ipromises;
char *tok, *state, *start, *freeme;
if (promises) {
ipromises = -1;
freeme = start = strdup(promises);
while ((tok = strtok_r(start, " \t\r\n", &state))) {
if ((promise = FindPromise(kPledgeLinux, tok, &plen)) != -1) {
if ((promise = FindPromise(tok)) != -1) {
ipromises &= ~(1ULL << promise);
} else {
rc = einval();
break;
}
start = 0;
}
__promises = ipromises;
free(freeme);
} else {
ipromises = 0;
}
if (!rc) {
*out = ipromises;
}
return rc;
}
static void FixupOpenbsdPromises(char *p) {
if (!p) return;
if ((p = strstr(p, "execnative"))) {
p[4] = ' ';
p[5] = ' ';
p[6] = ' ';
p[7] = ' ';
p[8] = ' ';
p[9] = ' ';
}
}
/**
* Restricts system operations, e.g.
*
* pledge("stdio tty", 0);
* pledge("stdio rfile tty", 0);
*
* Pledging causes most system calls to become unavailable. Your system
* call policy is enforced by the kernel, which means it can propagate
@ -1185,12 +1215,13 @@ static void SetPromises(const char *promises) {
* fcntl(F_GETFL), fcntl(F_SETFL).
*
* - "rpath" (read-only path ops) allows chdir, getcwd, open(O_RDONLY),
* openat(O_RDONLY), stat, fstat, lstat, fstatat, access, faccessat,
* readlink, readlinkat, statfs, fstatfs.
* openat(O_RDONLY), stat, fstat, lstat, fstatat, access,
* faccessat,faccessat2, readlink, readlinkat, statfs, fstatfs.
*
* - "wpath" (write path ops) allows getcwd, open(O_WRONLY),
* openat(O_WRONLY), stat, fstat, lstat, fstatat, access, faccessat,
* readlink, readlinkat, chmod, fchmod, fchmodat.
* openat(O_WRONLY), stat, fstat, lstat, fstatat, access,
* faccessat,faccessat2, readlink, readlinkat, chmod, fchmod,
* fchmodat.
*
* - "cpath" (create path ops) allows open(O_CREAT), openat(O_CREAT),
* rename, renameat, renameat2, link, linkat, symlink, symlinkat,
@ -1204,7 +1235,9 @@ static void SetPromises(const char *promises) {
* - "tty" allows ioctl(TIOCGWINSZ), ioctl(TCGETS), ioctl(TCSETS),
* ioctl(TCSETSW), ioctl(TCSETSF).
*
* - "recvfd" allows recvmsg(SCM_RIGHTS).
* - "recvfd" allows recvmsg in general (for SCM_RIGHTS).
*
* - "recvfd" allows sendmsg in general (for SCM_RIGHTS).
*
* - "fattr" allows chmod, fchmod, fchmodat, utime, utimes, futimens,
* utimensat.
@ -1236,24 +1269,70 @@ static void SetPromises(const char *promises) {
* 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.
* assimilate your APE binaries, then this should be preferred. On
* OpenBSD this will be rewritten to be "exec".
*
* - "unveil" allows unveil() to be called, as well as the underlying
* landlock_create_ruleset, landlock_add_rule, landlock_restrict_self
* calls on Linux.
*
* `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.
*
* @return 0 on success, or -1 w/ errno
* @raise ENOSYS if host os isn't Linux or OpenBSD
* @raise EINVAL if `execpromises` is used on Linux
* @raise EINVAL if `execpromises` on Linux isn't a subset of `promises`
*/
int pledge(const char *promises, const char *execpromises) {
int rc;
char *p, *q;
unsigned long ipromises, iexecpromises;
if (!(rc = ParsePromises(promises, &ipromises)) &&
!(rc = ParsePromises(execpromises, &iexecpromises))) {
if (IsLinux()) {
rc = sys_pledge_linux(promises, execpromises);
// copy exec and execnative from promises to execpromises
iexecpromises =
~(~iexecpromises | (~ipromises & ((1ul << PROMISE_EXEC) | //
(1ul << PROMISE_EXECNATIVE))));
// if bits are missing in execpromises that exist in promises
// then execpromises wouldn't be a monotonic access reduction
// this check only matters when exec / execnative are allowed
if ((ipromises & ~iexecpromises) &&
(~ipromises &
((1ul << PROMISE_EXEC) | (1ul << PROMISE_EXECNATIVE)))) {
STRACE("execpromises must be a subset of promises");
rc = einval();
} else {
rc = sys_pledge(promises, execpromises);
rc = sys_pledge_linux(ipromises);
}
} else {
// openbsd only supports execnative and calls it exec
if ((p = strdup(promises))) {
FixupOpenbsdPromises(p);
if ((q = execpromises ? strdup(execpromises) : 0) || !execpromises) {
FixupOpenbsdPromises(q);
rc = sys_pledge(p, q);
free(q);
} else {
rc = -1;
}
free(p);
} else {
rc = -1;
}
}
if (!rc) {
SetPromises(promises);
__promises = ipromises;
__execpromises = iexecpromises;
}
}
STRACE("pledge(%#s, %#s) → %d% m", promises, execpromises, rc);

View file

@ -18,6 +18,7 @@
*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/landlock.h"
#include "libc/calls/strace.internal.h"
#include "libc/calls/struct/stat.h"
@ -25,7 +26,10 @@
#include "libc/calls/syscall_support-sysv.internal.h"
#include "libc/errno.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/threaded.h"
#include "libc/runtime/internal.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/f.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/pr.h"
@ -126,10 +130,13 @@ static int sys_unveil_linux(const char *path, const char *permissions) {
if ((rc = sys_open(path, O_PATH | O_CLOEXEC, 0)) == -1) return rc;
pb.parent_fd = rc;
struct stat st;
if ((rc = fstat(pb.parent_fd, &st)) == -1) return err_close(rc, pb.parent_fd);
if (!S_ISDIR(st.st_mode)) pb.allowed_access &= FILE_BITS;
if ((rc = landlock_add_rule(State.fd, LANDLOCK_RULE_PATH_BENEATH, &pb, 0)))
if ((rc = sys_fstat(pb.parent_fd, &st)) == -1) {
return err_close(rc, pb.parent_fd);
}
if (!S_ISDIR(st.st_mode)) pb.allowed_access &= FILE_BITS;
if ((rc = landlock_add_rule(State.fd, LANDLOCK_RULE_PATH_BENEATH, &pb, 0))) {
return err_close(rc, pb.parent_fd);
}
sys_close(pb.parent_fd);
return rc;
}
@ -139,6 +146,7 @@ static int sys_unveil_linux(const char *path, const char *permissions) {
*/
int unveil(const char *path, const char *permissions) {
int rc;
__enable_tls();
if (IsLinux()) {
rc = sys_unveil_linux(path, permissions);
} else {

View file

@ -1,2 +0,0 @@
.include "o/libc/sysv/macros.internal.inc"
.scall faccessat2,0xfffffffffffff1b7,globl

View file

@ -1,2 +0,0 @@
.include "o/libc/sysv/macros.internal.inc"
.scall openat2,0xfffffffffffff1b5,globl

View file

@ -0,0 +1,2 @@
.include "o/libc/sysv/macros.internal.inc"
.scall sys_faccessat2,0xfffffffffffff1b7,globl,hidden

View file

@ -0,0 +1,2 @@
.include "o/libc/sysv/macros.internal.inc"
.scall sys_openat2,0xfffffffffffff1b5,globl,hidden

View file

@ -385,9 +385,9 @@ scall fspick 0xfffffffffffff1b1 globl
scall pidfd_open 0xfffffffffffff1b2 globl
scall clone3 0xfffffffffffff1b3 globl
scall close_range 0xfffffffffffff1b4 globl
scall openat2 0xfffffffffffff1b5 globl # Linux 5.6
scall sys_openat2 0xfffffffffffff1b5 globl hidden # Linux 5.6
scall pidfd_getfd 0xfffffffffffff1b6 globl
scall faccessat2 0xfffffffffffff1b7 globl
scall sys_faccessat2 0xfffffffffffff1b7 globl hidden
scall process_madvise 0xfffffffffffff1b8 globl
scall epoll_pwait2 0xfffffffffffff1b9 globl
scall mount_setattr 0xfffffffffffff1ba globl

View file

@ -28,7 +28,9 @@
#include "libc/calls/syscall_support-sysv.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/kprintf.h"
#include "libc/macros.internal.h"
#include "libc/mem/io.h"
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/sock.h"
@ -48,16 +50,33 @@
#include "libc/testlib/testlib.h"
#include "libc/thread/spawn.h"
STATIC_YOINK("zip_uri_support");
char testlib_enable_tmp_setup_teardown;
void OnSig(int sig) {
// do nothing
}
void SetUp(void) {
if (!__is_linux_2_6_23() && !IsOpenbsd()) {
exit(0);
int extract(const char *from, const char *to, int mode) {
int fdin, fdout;
if ((fdin = open(from, O_RDONLY)) == -1) return -1;
if ((fdout = creat(to, mode)) == -1) {
close(fdin);
return -1;
}
if (_copyfd(fdin, fdout, -1) == -1) {
close(fdout);
close(fdin);
return -1;
}
return close(fdout) | close(fdin);
}
void SetUp(void) {
if (!__is_linux_2_6_23() && !IsOpenbsd()) exit(0);
ASSERT_SYS(0, 0, extract("/zip/life.elf", "life.elf", 0755));
ASSERT_SYS(0, 0, extract("/zip/sock.elf", "sock.elf", 0755));
}
TEST(pledge, default_allowsExit) {
@ -149,8 +168,8 @@ TEST(pledge, multipleCalls_canOnlyBecomeMoreRestrictive2) {
int ws, pid;
ASSERT_NE(-1, (pid = fork()));
if (!pid) {
ASSERT_SYS(0, 0, pledge("stdio", 0));
ASSERT_SYS(EPERM, -1, pledge("stdio unix", 0));
ASSERT_SYS(0, 0, pledge("stdio", "stdio"));
ASSERT_SYS(EPERM, -1, pledge("stdio unix", "stdio unix"));
_Exit(0);
}
EXPECT_NE(-1, wait(&ws));
@ -293,7 +312,7 @@ TEST(pledge, mmapExec) {
int ws, pid;
ASSERT_NE(-1, (pid = fork()));
if (!pid) {
ASSERT_SYS(0, 0, pledge("stdio exec", 0));
ASSERT_SYS(0, 0, pledge("stdio exec", "stdio"));
ASSERT_NE(MAP_FAILED, (p = mmap(0, FRAMESIZE, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)));
ASSERT_SYS(0, 0, mprotect(p, FRAMESIZE, PROT_READ));
@ -414,3 +433,98 @@ TEST(pledge, sigaction_isFineButForbidsSigsys) {
EXPECT_NE(-1, wait(&ws));
EXPECT_TRUE(WIFEXITED(ws) && !WEXITSTATUS(ws));
}
TEST(pledge, execpromises_ok) {
if (IsOpenbsd()) return; // b/c testing linux bpf
int ws, pid;
struct stat st;
ASSERT_NE(-1, (pid = fork()));
if (!pid) {
ASSERT_SYS(0, 0, pledge("stdio execnative", "stdio"));
execl("life.elf", "life.elf", 0);
_Exit(127);
}
EXPECT_NE(-1, wait(&ws));
EXPECT_TRUE(WIFEXITED(ws));
EXPECT_EQ(42, WEXITSTATUS(ws));
}
TEST(pledge, execpromises_notok) {
if (IsOpenbsd()) return; // b/c testing linux bpf
int ws, pid;
struct stat st;
ASSERT_NE(-1, (pid = fork()));
if (!pid) {
ASSERT_SYS(0, 0, pledge("stdio execnative", "stdio"));
execl("sock.elf", "sock.elf", 0);
_Exit(127);
}
EXPECT_NE(-1, wait(&ws));
EXPECT_TRUE(WIFEXITED(ws));
EXPECT_EQ(128 + EPERM, WEXITSTATUS(ws));
}
TEST(pledge, execpromises_reducesAtExecOnLinux) {
if (IsOpenbsd()) return; // b/c testing linux bpf
int ws, pid;
struct stat st;
ASSERT_NE(-1, (pid = fork()));
if (!pid) {
ASSERT_SYS(0, 0, pledge("stdio inet tty execnative", "stdio tty"));
execl("sock.elf", "sock.elf", 0);
_Exit(127);
}
EXPECT_NE(-1, wait(&ws));
EXPECT_TRUE(WIFEXITED(ws));
EXPECT_EQ(128 + EPERM, WEXITSTATUS(ws));
}
TEST(pledge_openbsd, execpromisesIsNull_letsItDoAnything) {
if (!IsOpenbsd()) return;
int ws, pid;
struct stat st;
ASSERT_NE(-1, (pid = fork()));
if (!pid) {
ASSERT_SYS(0, 0, pledge("stdio execnative", 0));
execl("sock.elf", "sock.elf", 0);
_Exit(127);
}
EXPECT_NE(-1, wait(&ws));
EXPECT_TRUE(WIFEXITED(ws));
EXPECT_EQ(3, WEXITSTATUS(ws));
}
TEST(pledge_openbsd, execpromisesIsSuperset_letsItDoAnything) {
if (!IsOpenbsd()) return;
int ws, pid;
struct stat st;
ASSERT_NE(-1, (pid = fork()));
if (!pid) {
ASSERT_SYS(0, 0, pledge("stdio rpath execnative", "stdio rpath tty inet"));
execl("sock.elf", "sock.elf", 0);
_Exit(127);
}
EXPECT_NE(-1, wait(&ws));
EXPECT_TRUE(WIFEXITED(ws));
EXPECT_EQ(3, WEXITSTATUS(ws));
}
TEST(pledge_linux, execpromisesIsSuperset_notPossible) {
if (IsOpenbsd()) return;
ASSERT_SYS(EINVAL, -1, pledge("stdio execnative", "stdio inet execnative"));
}
TEST(pledge_openbsd, execpromises_notok) {
if (!IsOpenbsd()) return;
int ws, pid;
struct stat st;
ASSERT_NE(-1, (pid = fork()));
if (!pid) {
ASSERT_SYS(0, 0, pledge("stdio execnative", "stdio"));
execl("sock.elf", "sock.elf", 0);
_Exit(127);
}
EXPECT_NE(-1, wait(&ws));
EXPECT_TRUE(WIFSIGNALED(ws));
EXPECT_EQ(SIGABRT, WTERMSIG(ws));
}

31
test/libc/mem/prog/sock.c Normal file
View file

@ -0,0 +1,31 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/errno.h"
#include "libc/sock/sock.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/sock.h"
int main(int argc, char *argv[]) {
int fd;
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) != -1) {
return fd;
} else {
return 128 + errno;
}
}

View file

@ -61,34 +61,64 @@ o/$(MODE)/test/libc/mem/%.com.dbg: \
$(TEST_LIBC_MEM_DEPS) \
o/$(MODE)/test/libc/mem/%.o \
o/$(MODE)/test/libc/mem/mem.pkg \
o/$(MODE)/test/libc/mem/life.elf.zip.o \
o/$(MODE)/test/libc/mem/prog/life.elf.zip.o \
o/$(MODE)/test/libc/mem/prog/sock.elf.zip.o \
$(LIBC_TESTMAIN) \
$(CRT) \
$(APE_NO_MODIFY_SELF)
@$(APELINK)
o/$(MODE)/test/libc/mem/life.com.dbg: \
################################################################################
o/$(MODE)/test/libc/mem/prog/life.com.dbg: \
$(LIBC_RUNTIME) \
o/$(MODE)/test/libc/mem/life.o \
o/$(MODE)/test/libc/mem/prog/life.o \
$(CRT) \
$(APE)
@$(APELINK)
o/$(MODE)/test/libc/mem/life.elf: \
o/$(MODE)/test/libc/mem/prog/life.elf: \
o/$(MODE)/tool/build/assimilate.com \
o/$(MODE)/test/libc/mem/life.com
o/$(MODE)/test/libc/mem/prog/life.com
@$(COMPILE) -ACP -T$@ \
build/bootstrap/cp.com \
o/$(MODE)/test/libc/mem/life.com \
o/$(MODE)/test/libc/mem/life.elf
o/$(MODE)/test/libc/mem/prog/life.com \
o/$(MODE)/test/libc/mem/prog/life.elf
@$(COMPILE) -AASSIMILATE -T$@ \
o/$(MODE)/tool/build/assimilate.com \
o/$(MODE)/test/libc/mem/life.elf
o/$(MODE)/test/libc/mem/prog/life.elf
o/$(MODE)/test/libc/mem/life.elf.zip.o: \
o/$(MODE)/test/libc/mem/prog/life.elf.zip.o: \
ZIPOBJ_FLAGS += \
-B
################################################################################
o/$(MODE)/test/libc/mem/prog/sock.com.dbg: \
$(LIBC_RUNTIME) \
$(LIBC_SOCK) \
o/$(MODE)/test/libc/mem/prog/sock.o \
$(CRT) \
$(APE)
@$(APELINK)
o/$(MODE)/test/libc/mem/prog/sock.elf: \
o/$(MODE)/tool/build/assimilate.com \
o/$(MODE)/test/libc/mem/prog/sock.com
@$(COMPILE) -ACP -T$@ \
build/bootstrap/cp.com \
o/$(MODE)/test/libc/mem/prog/sock.com \
o/$(MODE)/test/libc/mem/prog/sock.elf
@$(COMPILE) -AASSIMILATE -T$@ \
o/$(MODE)/tool/build/assimilate.com \
o/$(MODE)/test/libc/mem/prog/sock.elf
o/$(MODE)/test/libc/mem/prog/sock.elf.zip.o: \
ZIPOBJ_FLAGS += \
-B
################################################################################
$(TEST_LIBC_MEM_OBJS): \
DEFAULT_CCFLAGS += \
-fno-builtin

View file

@ -1382,12 +1382,22 @@ static int LuaUnixSiocgifconf(lua_State *L) {
return 1;
}
// sandbox.pledge([promises:str])
// sandbox.pledge([promises:str[, execpromises:str]])
// ├─→ true
// └─→ nil, unix.Errno
static int LuaUnixPledge(lua_State *L) {
int olderr = errno;
return SysretBool(L, "pledge", olderr, pledge(luaL_checkstring(L, 1), 0));
return SysretBool(L, "pledge", olderr,
pledge(luaL_checkstring(L, 1), luaL_optstring(L, 2, 0)));
}
// sandbox.unveil(path:str, permissions:str)
// ├─→ true
// └─→ nil, unix.Errno
static int LuaUnixUnveil(lua_State *L) {
int olderr = errno;
return SysretBool(L, "unveil", olderr,
unveil(luaL_checkstring(L, 1), luaL_checkstring(L, 2)));
}
// unix.gethostname()
@ -2636,6 +2646,7 @@ static const luaL_Reg kLuaUnix[] = {
{"truncate", LuaUnixTruncate}, // shrink or extend file medium
{"umask", LuaUnixUmask}, // set default file mask
{"unlink", LuaUnixUnlink}, // remove file
{"unveil", LuaUnixUnveil}, // filesystem sandboxing
{"utimensat", LuaUnixUtimensat}, // change access/modified time
{"wait", LuaUnixWait}, // wait for child to change status
{"write", LuaUnixWrite}, // write to file or socket

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"
@ -53,7 +55,8 @@ usage: pledge.com [-hnN] PROG ARGS...\n\
-g GID call setgid()\n\
-u UID call setuid()\n\
-c PATH call chroot()\n\
-n maximum niceness\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\
@ -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