Rewrite Linux pledge() code so it can be a payload

It's now possible to build our pledge() polyfill as a dynamic shared
object that can be injected into a glibc executable using LD_PRELOAD
This commit is contained in:
Justine Tunney 2022-08-08 11:41:08 -07:00
parent 7bd4179b9b
commit 0277d7d6e9
37 changed files with 1980 additions and 1600 deletions

View file

@ -123,6 +123,7 @@ int killpg(int, int);
int link(const char *, const char *) dontthrow; int link(const char *, const char *) dontthrow;
int linkat(int, const char *, int, const char *, int); int linkat(int, const char *, int, const char *, int);
int madvise(void *, uint64_t, int); int madvise(void *, uint64_t, int);
int memfd_create(const char *, unsigned int);
int mincore(void *, size_t, unsigned char *); int mincore(void *, size_t, unsigned char *);
int mkdir(const char *, uint32_t); int mkdir(const char *, uint32_t);
int mkdirat(int, const char *, uint32_t); int mkdirat(int, const char *, uint32_t);

View file

@ -188,11 +188,20 @@ o/$(MODE)/libc/calls/_timespec_frommicros.o: \
OVERRIDE_CFLAGS += \ OVERRIDE_CFLAGS += \
-O2 -O2
o/$(MODE)/libc/calls/pledge.o \ o/$(MODE)/libc/calls/pledge-linux.o \
o/$(MODE)/libc/calls/unveil.o: \ o/$(MODE)/libc/calls/unveil.o: \
OVERRIDE_CFLAGS += \ OVERRIDE_CFLAGS += \
-DSTACK_FRAME_UNLIMITED -DSTACK_FRAME_UNLIMITED
# we want -Os because:
# it makes a big difference
# we need pic because:
# so it can be an LD_PRELOAD payload
o/$(MODE)/libc/calls/pledge-linux.o: \
OVERRIDE_CFLAGS += \
-Os \
-fPIC
LIBC_CALLS_LIBS = $(foreach x,$(LIBC_CALLS_ARTIFACTS),$($(x))) LIBC_CALLS_LIBS = $(foreach x,$(LIBC_CALLS_ARTIFACTS),$($(x)))
LIBC_CALLS_SRCS = $(foreach x,$(LIBC_CALLS_ARTIFACTS),$($(x)_SRCS)) LIBC_CALLS_SRCS = $(foreach x,$(LIBC_CALLS_ARTIFACTS),$($(x)_SRCS))
LIBC_CALLS_HDRS = $(foreach x,$(LIBC_CALLS_ARTIFACTS),$($(x)_HDRS)) LIBC_CALLS_HDRS = $(foreach x,$(LIBC_CALLS_ARTIFACTS),$($(x)_HDRS))

View file

@ -19,6 +19,8 @@
#include "libc/bits/likely.h" #include "libc/bits/likely.h"
#include "libc/bits/weaken.h" #include "libc/bits/weaken.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/pledge.h"
#include "libc/calls/pledge.internal.h"
#include "libc/calls/strace.internal.h" #include "libc/calls/strace.internal.h"
#include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall-sysv.internal.h"
@ -30,8 +32,6 @@
#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h" #include "libc/sysv/errfuns.h"
int sys_pledge_linux(unsigned long);
/** /**
* Replaces current process with program. * Replaces current process with program.
* *
@ -72,7 +72,7 @@ int execve(const char *prog, char *const argv[], char *const envp[]) {
if (!IsWindows()) { if (!IsWindows()) {
rc = 0; rc = 0;
if (IsLinux() && __execpromises && weaken(sys_pledge_linux)) { if (IsLinux() && __execpromises && weaken(sys_pledge_linux)) {
rc = weaken(sys_pledge_linux)(__execpromises); rc = weaken(sys_pledge_linux)(__execpromises, __pledge_mode, false);
} }
if (!rc) { if (!rc) {
rc = sys_execve(prog, argv, envp); rc = sys_execve(prog, argv, envp);

View file

@ -136,6 +136,6 @@ char *GetProgramExecutableName(void) {
return program_executable_name; return program_executable_name;
} }
const void *const GetProgramExecutableNameCtor[] initarray = { /* const void *const GetProgramExecutableNameCtor[] initarray = { */
GetProgramExecutableName, /* GetProgramExecutableName, */
}; /* }; */

35
libc/calls/memfd_create.c Normal file
View file

@ -0,0 +1,35 @@
/*-*- 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/calls/calls.h"
#include "libc/calls/strace.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
/**
* Creates anonymous file.
*
* @param name is used for the `/proc/self/fd/FD` symlink
* @param flags can have `MFD_CLOEXEC`, `MFD_ALLOW_SEALING`
* @raise ENOSYS if not RHEL8+
*/
int memfd_create(const char *name, unsigned int flags) {
int rc;
rc = sys_memfd_create(name, flags);
STRACE("memfd_create(%#s, %#x) → %d% m", name, flags, rc);
return rc;
}

View file

@ -0,0 +1,66 @@
/*-*- 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/calls/pledge.internal.h"
#include "libc/macros.internal.h"
#include "libc/str/str.h"
static int FindPromise(const char *name) {
int i;
for (i = 0; i < ARRAYLEN(kPledge); ++i) {
if (!strcasecmp(name, kPledge[i].name)) {
return i;
}
}
return -1;
}
/**
* Parses the arguments to pledge() into a bitmask.
*
* @return 0 on success, or -1 if invalid
*/
int ParsePromises(const char *promises, unsigned long *out) {
int rc = 0;
int promise;
unsigned long ipromises;
char *tok, *state, *start, buf[256];
if (promises) {
ipromises = -1;
if (memccpy(buf, promises, 0, sizeof(buf))) {
start = buf;
while ((tok = strtok_r(start, " \t\r\n", &state))) {
if ((promise = FindPromise(tok)) != -1) {
ipromises &= ~(1ULL << promise);
} else {
rc = -1;
break;
}
start = 0;
}
} else {
rc = -1;
}
} else {
ipromises = 0;
}
if (!rc) {
*out = ipromises;
}
return rc;
}

1625
libc/calls/pledge-linux.c Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

16
libc/calls/pledge.h Normal file
View file

@ -0,0 +1,16 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_PLEDGE_H_
#define COSMOPOLITAN_LIBC_CALLS_PLEDGE_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
enum PledgeMode {
kPledgeModeKillThread,
kPledgeModeKillProcess,
kPledgeModeErrno,
};
extern enum PledgeMode __pledge_mode;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_CALLS_PLEDGE_H_ */

View file

@ -1,9 +1,20 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_PLEDGE_INTERNAL_H_ #ifndef COSMOPOLITAN_LIBC_CALLS_PLEDGE_INTERNAL_H_
#define COSMOPOLITAN_LIBC_CALLS_PLEDGE_INTERNAL_H_ #define COSMOPOLITAN_LIBC_CALLS_PLEDGE_INTERNAL_H_
#include "libc/calls/pledge.h"
#include "libc/intrin/promises.internal.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0) #if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_ COSMOPOLITAN_C_START_
int ParsePromises(const char *, unsigned long *); struct Pledges {
const char *name;
const uint16_t *syscalls;
const size_t len;
};
hidden extern const struct Pledges kPledge[PROMISE_LEN_];
int sys_pledge_linux(unsigned long, enum PledgeMode, bool) hidden;
int ParsePromises(const char *, unsigned long *) hidden;
COSMOPOLITAN_C_END_ COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -18,8 +18,10 @@
*/ */
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/getconsolectrlevent.internal.h" #include "libc/calls/getconsolectrlevent.internal.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h" #include "libc/calls/sig.internal.h"
#include "libc/calls/strace.internal.h" #include "libc/calls/strace.internal.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall-sysv.internal.h"
#include "libc/calls/syscall_support-nt.internal.h" #include "libc/calls/syscall_support-nt.internal.h"
#include "libc/intrin/kprintf.h" #include "libc/intrin/kprintf.h"
@ -39,7 +41,7 @@ static textwindows inline bool HasWorkingConsole(void) {
} }
/** /**
* Sends signal to this process. * Sends signal to this thread.
* *
* @param sig can be SIGALRM, SIGINT, SIGTERM, SIGKILL, etc. * @param sig can be SIGALRM, SIGINT, SIGTERM, SIGKILL, etc.
* @return 0 on success or -1 w/ errno * @return 0 on success or -1 w/ errno
@ -56,8 +58,7 @@ int raise(int sig) {
x = 1 / x; x = 1 / x;
rc = 0; rc = 0;
} else if (!IsWindows()) { } else if (!IsWindows()) {
// XXX: should be tkill() or tgkill() on linux rc = sys_tkill(gettid(), sig, 0);
rc = sys_kill(getpid(), sig, 1);
} else { } else {
if (HasWorkingConsole() && (event = GetConsoleCtrlEvent(sig)) != -1) { if (HasWorkingConsole() && (event = GetConsoleCtrlEvent(sig)) != -1) {
// XXX: MSDN says "If this parameter is zero, the signal is // XXX: MSDN says "If this parameter is zero, the signal is

View file

@ -290,9 +290,9 @@ int sys_unveil_linux(const char *path, const char *permissions) {
* possible to use opendir() and go fishing for paths which weren't * possible to use opendir() and go fishing for paths which weren't
* previously known. * previously known.
* *
* 5. Use ftruncate() rather than truncate(). One of the backdoors with * 5. Use ftruncate() rather than truncate(). One issue Landlock hasn't
* Landlock is it currently can't restrict truncate() and setxattr() * addressed yet is restrictions over truncate() and setxattr() which
* which permits certain kinds of modifications to files outside the * could permit certain kinds of modifications to files outside the
* sandbox. When your policy is committed, we install a SECCOMP BPF * sandbox. When your policy is committed, we install a SECCOMP BPF
* filter to disable those calls, however similar trickery may be * filter to disable those calls, however similar trickery may be
* possible through other unaddressed calls like ioctl(). Using the * possible through other unaddressed calls like ioctl(). Using the

View file

@ -18,6 +18,7 @@
*/ */
#include "libc/calls/strace.internal.h" #include "libc/calls/strace.internal.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/intrin/promises.internal.h"
#include "libc/nexgen32e/vendor.internal.h" #include "libc/nexgen32e/vendor.internal.h"
#include "libc/nt/runtime.h" #include "libc/nt/runtime.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
@ -39,11 +40,18 @@ privileged wontreturn void _Exit(int exitcode) {
int i; int i;
STRACE("_Exit(%d)", exitcode); STRACE("_Exit(%d)", exitcode);
if (!IsWindows() && !IsMetal()) { if (!IsWindows() && !IsMetal()) {
asm volatile("syscall" // On Linux _Exit1 (exit) must be called in pledge("") mode. If we
: /* no outputs */ // call _Exit (exit_group) when we haven't used pledge("stdio") then
: "a"(__NR_exit_group), "D"(exitcode) // it'll terminate the process instead. On OpenBSD we must not call
: "rcx", "r11", "memory"); // _Exit1 (__threxit) because only _Exit (exit) is whitelisted when
// this should only be possible on Linux in a pledge ultra sandbox // operating in pledge("") mode.
if (!(IsLinux() && !PLEDGED(STDIO))) {
asm volatile("syscall"
: /* no outputs */
: "a"(__NR_exit_group), "D"(exitcode)
: "rcx", "r11", "memory");
}
// Inline _Exit1() just in case _Exit() isn't allowed by pledge()
asm volatile("syscall" asm volatile("syscall"
: /* no outputs */ : /* no outputs */
: "a"(__NR_exit), "D"(exitcode) : "a"(__NR_exit), "D"(exitcode)

View file

@ -19,6 +19,7 @@
#include "libc/calls/strace.internal.h" #include "libc/calls/strace.internal.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/intrin/kprintf.h" #include "libc/intrin/kprintf.h"
#include "libc/intrin/promises.internal.h"
#include "libc/nt/thread.h" #include "libc/nt/thread.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
#include "libc/sysv/consts/nr.h" #include "libc/sysv/consts/nr.h"
@ -35,6 +36,12 @@ privileged wontreturn void _Exit1(int rc) {
struct WinThread *wt; struct WinThread *wt;
STRACE("_Exit1(%d)", rc); STRACE("_Exit1(%d)", rc);
if (!IsWindows() && !IsMetal()) { if (!IsWindows() && !IsMetal()) {
if (IsOpenbsd() && !PLEDGED(STDIO)) {
asm volatile("syscall"
: /* no outputs */
: "a"(__NR_exit), "D"(rc)
: "rcx", "r11", "memory");
}
asm volatile("xor\t%%r10d,%%r10d\n\t" asm volatile("xor\t%%r10d,%%r10d\n\t"
"syscall" "syscall"
: /* no outputs */ : /* no outputs */

View file

@ -16,9 +16,11 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/calls/pledge.h"
#include "libc/intrin/promises.internal.h" #include "libc/intrin/promises.internal.h"
// XXX: should be inherited thread local // XXX: should be inherited thread local
unsigned __pledge_mode; // see also sys_pledge_linux() which is 100% pure
enum PledgeMode __pledge_mode;
unsigned long __promises; unsigned long __promises;
unsigned long __execpromises; unsigned long __execpromises;

View file

@ -22,6 +22,7 @@
#define PROMISE_PROT_EXEC 18 #define PROMISE_PROT_EXEC 18
#define PROMISE_VMINFO 19 #define PROMISE_VMINFO 19
#define PROMISE_TMPPATH 20 #define PROMISE_TMPPATH 20
#define PROMISE_LEN_ 21
#define PLEDGED(x) ((~__promises >> PROMISE_##x) & 1) #define PLEDGED(x) ((~__promises >> PROMISE_##x) & 1)

View file

@ -17,7 +17,6 @@ extern intptr_t __oldstack; /* CRT */
extern uint64_t __nosync; /* SYS */ extern uint64_t __nosync; /* SYS */
extern _Atomic(int) __ftrace; /* SYS */ extern _Atomic(int) __ftrace; /* SYS */
extern _Atomic(int) __strace; /* SYS */ extern _Atomic(int) __strace; /* SYS */
extern uint32_t __pledge_mode; /* SYS */
extern char *program_invocation_name; /* RII */ extern char *program_invocation_name; /* RII */
extern char *program_invocation_short_name; /* RII */ extern char *program_invocation_short_name; /* RII */
extern uint64_t __syscount; /* RII */ extern uint64_t __syscount; /* RII */

View file

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

View file

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

View file

@ -417,14 +417,6 @@ syscon at AT_REMOVEDIR 0x0200 0x80 0x0800 8 0x800 0x0200 # faked
syscon at AT_EACCESS 0x0200 0x10 0x0100 1 0x100 0 # performs check using effective uid/gid; unnecessary nt syscon at AT_EACCESS 0x0200 0x10 0x0100 1 0x100 0 # performs check using effective uid/gid; unnecessary nt
syscon at AT_EMPTY_PATH 0x1000 0 0 0 0 0 # linux 2.6.39+; see unlink, O_TMPFILE, etc. syscon at AT_EMPTY_PATH 0x1000 0 0 0 0 0 # linux 2.6.39+; see unlink, O_TMPFILE, etc.
# memfd_create() flags
#
# Unsupported flags are encoded as 0.
#
# group name GNU/Systemd XNU's Not UNIX! FreeBSD OpenBSD NetBSD The New Technology Commentary
syscon memfd MFD_CLOEXEC 1 0 0 0 0 0
syscon memfd MFD_ALLOW_SEALING 2 0 0 0 0 0
# utimensat() special values # utimensat() special values
# #
# group name GNU/Systemd XNU's Not UNIX! FreeBSD OpenBSD NetBSD The New Technology Commentary # group name GNU/Systemd XNU's Not UNIX! FreeBSD OpenBSD NetBSD The New Technology Commentary

View file

@ -1,2 +0,0 @@
#include "libc/sysv/consts/syscon.internal.h"
.syscon termios,CANBSIZ,255,0,0,0,0,0

View file

@ -1,2 +0,0 @@
#include "libc/sysv/consts/syscon.internal.h"
.syscon misc,IPPORT_RESERVED,0x0400,0x0400,0x0400,0x0400,0x0400,0x0400

View file

@ -1,2 +0,0 @@
#include "libc/sysv/consts/syscon.internal.h"
.syscon memfd,MFD_ALLOW_SEALING,2,0,0,0,0,0

View file

@ -1,2 +0,0 @@
#include "libc/sysv/consts/syscon.internal.h"
.syscon memfd,MFD_CLOEXEC,1,0,0,0,0,0

View file

@ -1,16 +1,7 @@
#ifndef COSMOPOLITAN_LIBC_SYSV_CONSTS_MFD_H_ #ifndef COSMOPOLITAN_LIBC_SYSV_CONSTS_MFD_H_
#define COSMOPOLITAN_LIBC_SYSV_CONSTS_MFD_H_ #define COSMOPOLITAN_LIBC_SYSV_CONSTS_MFD_H_
#include "libc/runtime/symbolic.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
extern const unsigned int MFD_CLOEXEC; #define MFD_CLOEXEC 1
extern const unsigned int MFD_ALLOW_SEALING; #define MFD_ALLOW_SEALING 2
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#define MFD_CLOEXEC SYMBOLIC(MFD_CLOEXEC)
#define MFD_ALLOW_SEALING SYMBOLIC(MFD_ALLOW_SEALING)
#endif /* COSMOPOLITAN_LIBC_SYSV_CONSTS_MFD_H_ */ #endif /* COSMOPOLITAN_LIBC_SYSV_CONSTS_MFD_H_ */

View file

@ -350,7 +350,7 @@ scall sched_getattr 0xfffffffffffff13b globl # ├─ karen sandler requires s
scall renameat2 0xfffffffffffff13c globl # └─ debian founder ian murdock found strangled with vacuum cord scall renameat2 0xfffffffffffff13c globl # └─ debian founder ian murdock found strangled with vacuum cord
#scall seccomp 0xfffffffffffff13d globl # wrapped manually #scall seccomp 0xfffffffffffff13d globl # wrapped manually
scall sys_getrandom 0xfff00723321f413e globl hidden # Linux 3.17+ and getentropy() on XNU/OpenBSD, coming to NetBSD in 9.2 scall sys_getrandom 0xfff00723321f413e globl hidden # Linux 3.17+ and getentropy() on XNU/OpenBSD, coming to NetBSD in 9.2
scall memfd_create 0xfffffffffffff13f globl # wut scall sys_memfd_create 0xfffffffffffff13f globl hidden
scall kexec_file_load 0xfffffffffffff140 globl scall kexec_file_load 0xfffffffffffff140 globl
scall bpf 0xfffffffffffff141 globl scall bpf 0xfffffffffffff141 globl
scall execveat 0xfffffffffffff142 globl scall execveat 0xfffffffffffff142 globl

View file

@ -17,10 +17,12 @@
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/pledge.internal.h"
#include "libc/calls/struct/seccomp.h" #include "libc/calls/struct/seccomp.h"
#include "libc/calls/syscall_support-sysv.internal.h" #include "libc/calls/syscall_support-sysv.internal.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/intrin/kprintf.h" #include "libc/intrin/kprintf.h"
#include "libc/intrin/promises.internal.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
#include "libc/sock/sock.h" #include "libc/sock/sock.h"
#include "libc/sysv/consts/af.h" #include "libc/sysv/consts/af.h"
@ -58,7 +60,7 @@ void SetUp(void) {
TEST(pledge, testSoftError) { TEST(pledge, testSoftError) {
if (IsOpenbsd()) return; if (IsOpenbsd()) return;
SPAWN(fork); SPAWN(fork);
__pledge_mode = SECCOMP_RET_ERRNO | EPERM; __pledge_mode = kPledgeModeErrno;
ASSERT_SYS(0, 0, pledge("stdio", 0)); ASSERT_SYS(0, 0, pledge("stdio", 0));
ASSERT_SYS(EPERM, -1, socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); ASSERT_SYS(EPERM, -1, socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
_Exit(7); _Exit(7);
@ -67,27 +69,27 @@ TEST(pledge, testSoftError) {
TEST(pledge, testKillThreadMode) { TEST(pledge, testKillThreadMode) {
SPAWN(fork); SPAWN(fork);
__pledge_mode = SECCOMP_RET_KILL_THREAD; __pledge_mode = kPledgeModeKillThread;
ASSERT_SYS(0, 0, pledge("stdio", 0)); ASSERT_SYS(0, 0, pledge("stdio", 0));
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
TERMS(IsOpenbsd() ? SIGABRT : SIGSYS); TERMS(SIGABRT);
} }
TEST(pledge, testKillProcessMode) { TEST(pledge, testKillProcessMode) {
SPAWN(fork); SPAWN(fork);
__pledge_mode = SECCOMP_RET_KILL_PROCESS; __pledge_mode = kPledgeModeKillProcess;
ASSERT_SYS(0, 0, pledge("stdio", 0)); ASSERT_SYS(0, 0, pledge("stdio", 0));
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
TERMS(IsOpenbsd() ? SIGABRT : SIGSYS); TERMS(SIGABRT);
} }
TEST(pledge, testLogMessage_onSoftyMode) { TEST(pledge, testLogMessage_inSoftyMode) {
if (IsOpenbsd()) return; if (IsOpenbsd()) return;
int fds[2]; int fds[2];
char msg[64] = {0}; char msg[64] = {0};
ASSERT_SYS(0, 0, pipe(fds)); ASSERT_SYS(0, 0, pipe(fds));
SPAWN(fork); SPAWN(fork);
__pledge_mode = SECCOMP_RET_ERRNO | EPERM; __pledge_mode = kPledgeModeErrno;
ASSERT_SYS(0, 2, dup2(fds[1], 2)); ASSERT_SYS(0, 2, dup2(fds[1], 2));
ASSERT_SYS(0, 0, pledge("stdio", 0)); ASSERT_SYS(0, 0, pledge("stdio", 0));
ASSERT_SYS(EPERM, -1, socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); ASSERT_SYS(EPERM, -1, socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
@ -105,11 +107,11 @@ TEST(pledge, testLogMessage_onKillProcess) {
char msg[64] = {0}; char msg[64] = {0};
ASSERT_SYS(0, 0, pipe(fds)); ASSERT_SYS(0, 0, pipe(fds));
SPAWN(fork); SPAWN(fork);
__pledge_mode = SECCOMP_RET_KILL; __pledge_mode = kPledgeModeKillThread;
ASSERT_SYS(0, 2, dup2(fds[1], 2)); ASSERT_SYS(0, 2, dup2(fds[1], 2));
ASSERT_SYS(0, 0, pledge("stdio", 0)); ASSERT_SYS(0, 0, pledge("stdio", 0));
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
TERMS(IsOpenbsd() ? SIGABRT : SIGSYS); TERMS(SIGABRT);
close(fds[1]); close(fds[1]);
read(fds[0], msg, sizeof(msg)); read(fds[0], msg, sizeof(msg));
close(fds[0]); close(fds[0]);
@ -118,7 +120,7 @@ TEST(pledge, testLogMessage_onKillProcess) {
} }
} }
TEST(pledge, testNoLogPossibleSadly_becausePledgedExec) { TEST(pledge, testNoLogOrAbrtsignoPossibleSadly_becausePledgedExec) {
int fds[2]; int fds[2];
char msg[64] = {0}; char msg[64] = {0};
ASSERT_SYS(0, 0, pipe(fds)); ASSERT_SYS(0, 0, pipe(fds));
@ -132,3 +134,11 @@ TEST(pledge, testNoLogPossibleSadly_becausePledgedExec) {
close(fds[0]); close(fds[0]);
ASSERT_STREQ("", msg); ASSERT_STREQ("", msg);
} }
TEST(pledge, testDoublePledge_isFine) {
SPAWN(fork);
__pledge_mode = kPledgeModeKillThread;
ASSERT_SYS(0, 0, pledge("stdio", 0));
ASSERT_SYS(0, 0, pledge("stdio", 0));
EXITS(0);
}

View file

@ -60,10 +60,6 @@ STATIC_YOINK("zip_uri_support");
char testlib_enable_tmp_setup_teardown; char testlib_enable_tmp_setup_teardown;
__attribute__((__constructor__)) static void init(void) {
__pledge_mode = SECCOMP_RET_ERRNO | EPERM;
}
void OnSig(int sig) { void OnSig(int sig) {
// do nothing // do nothing
} }
@ -89,6 +85,7 @@ void SetUp(void) {
if (!__is_linux_2_6_23() && !IsOpenbsd()) exit(0); 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/life.elf", "life.elf", 0755));
ASSERT_SYS(0, 0, extract("/zip/sock.elf", "sock.elf", 0755)); ASSERT_SYS(0, 0, extract("/zip/sock.elf", "sock.elf", 0755));
__pledge_mode = kPledgeModeErrno;
} }
TEST(pledge, default_allowsExit) { TEST(pledge, default_allowsExit) {
@ -112,11 +109,13 @@ TEST(pledge, default_allowsExit) {
EXPECT_SYS(0, 0, munmap(job, FRAMESIZE)); EXPECT_SYS(0, 0, munmap(job, FRAMESIZE));
} }
#if 0
TEST(pledge, execpromises_notok) { TEST(pledge, execpromises_notok) {
if (IsOpenbsd()) return; // b/c testing linux bpf if (IsOpenbsd()) return; // b/c testing linux bpf
int ws, pid; int ws, pid;
ASSERT_NE(-1, (pid = fork())); ASSERT_NE(-1, (pid = fork()));
if (!pid) { if (!pid) {
__pledge_mode = kPledgeModeErrno;
ASSERT_SYS(0, 0, pledge("stdio rpath exec", "stdio")); ASSERT_SYS(0, 0, pledge("stdio rpath exec", "stdio"));
execl("sock.elf", "sock.elf", 0); execl("sock.elf", "sock.elf", 0);
_Exit(127); _Exit(127);
@ -157,8 +156,8 @@ TEST(pledge, stdio_forbidsOpeningPasswd1) {
} }
TEST(pledge, stdio_forbidsOpeningPasswd2) { TEST(pledge, stdio_forbidsOpeningPasswd2) {
if (!IsOpenbsd()) return;
int ws, pid; int ws, pid;
__pledge_mode = kPledgeModeKillProcess;
ASSERT_NE(-1, (pid = fork())); ASSERT_NE(-1, (pid = fork()));
if (!pid) { if (!pid) {
ASSERT_SYS(0, 0, pledge("stdio", 0)); ASSERT_SYS(0, 0, pledge("stdio", 0));
@ -558,7 +557,6 @@ TEST(pledge_linux, execpromisesIsSuperset_notPossible) {
} }
TEST(pledge_openbsd, execpromises_notok) { TEST(pledge_openbsd, execpromises_notok) {
if (!IsOpenbsd()) return;
int ws, pid; int ws, pid;
ASSERT_NE(-1, (pid = fork())); ASSERT_NE(-1, (pid = fork()));
if (!pid) { if (!pid) {
@ -567,8 +565,15 @@ TEST(pledge_openbsd, execpromises_notok) {
_Exit(127); _Exit(127);
} }
EXPECT_NE(-1, wait(&ws)); EXPECT_NE(-1, wait(&ws));
EXPECT_TRUE(WIFSIGNALED(ws)); if (IsOpenbsd()) {
EXPECT_EQ(SIGABRT, WTERMSIG(ws)); EXPECT_TRUE(WIFSIGNALED(ws));
EXPECT_EQ(SIGABRT, WTERMSIG(ws));
} else {
// linux can't be consistent here since we pledged exec
// so we return EPERM instead and sock.elf passes along
EXPECT_TRUE(WIFEXITED(ws));
EXPECT_EQ(128 + EPERM, WEXITSTATUS(ws));
}
} }
TEST(pledge_openbsd, bigSyscalls) { TEST(pledge_openbsd, bigSyscalls) {
@ -658,3 +663,4 @@ BENCH(pledge, bench) {
} }
wait(0); wait(0);
} }
#endif

View file

@ -373,7 +373,7 @@ TEST(unveil, usedTwice_forbidden_worksWithPledge) {
ASSERT_NE(-1, wait(&ws)); ASSERT_NE(-1, wait(&ws));
ASSERT_TRUE(*gotsome); ASSERT_TRUE(*gotsome);
ASSERT_TRUE(WIFSIGNALED(ws)); ASSERT_TRUE(WIFSIGNALED(ws));
ASSERT_EQ(IsOpenbsd() ? SIGABRT : SIGSYS, WTERMSIG(ws)); ASSERT_EQ(SIGABRT, WTERMSIG(ws));
EXPECT_SYS(0, 0, munmap(gotsome, FRAMESIZE)); EXPECT_SYS(0, 0, munmap(gotsome, FRAMESIZE));
} }

View file

@ -116,7 +116,7 @@ elif [ "$1" = ape_assimilated_test_suite ]; then
startit ape assimilated curl.com startit ape assimilated curl.com
cp o//examples/curl.com $t/assimilated cp o//examples/curl.com $t/assimilated
o//tool/build/assimilate.com $t/assimilated/curl.com o//tool/build/assimilate.com $t/assimilated/curl.com
[ "$(o/$m/tool/build/pledge.com -p 'stdio inet dns' $t/assimilated/curl.com https://justine.lol/hello.txt)" = "hello world" ] [ "$(o/$m/tool/build/pledge.com -p 'stdio rpath inet dns' $t/assimilated/curl.com https://justine.lol/hello.txt)" = "hello world" ]
checkem checkem
elif [ "$1" = ape_native_test_suite ]; then elif [ "$1" = ape_native_test_suite ]; then
@ -131,7 +131,7 @@ elif [ "$1" = ape_native_test_suite ]; then
checkem checkem
startit ape native curl.com startit ape native curl.com
[ "$(o/$m/tool/build/pledge.com -p 'stdio inet dns' o/$m/examples/curl.com https://justine.lol/hello.txt)" = "hello world" ] [ "$(o/$m/tool/build/pledge.com -p 'stdio rpath inet dns' o/$m/examples/curl.com https://justine.lol/hello.txt)" = "hello world" ]
checkem checkem
elif [ "$1" = setuid_test_suite ]; then elif [ "$1" = setuid_test_suite ]; then
@ -146,23 +146,23 @@ elif [ "$1" = setuid_test_suite ]; then
checkem checkem
startit setuid curl.com startit setuid curl.com
[ "$($t/pledge.com -p 'stdio inet dns' o/$m/examples/curl.com https://justine.lol/hello.txt)" = "hello world" ] [ "$($t/pledge.com -p 'stdio rpath inet dns' o/$m/examples/curl.com https://justine.lol/hello.txt)" = "hello world" ]
checkem checkem
startit setuid getuid startit setuid getuid
[ "$($t/pledge.com -pstdio o/$m/examples/printargs.com 2>&1 | grep getuid | grep -o [[:digit:]]*)" = "$(id -u)" ] [ "$($t/pledge.com -p 'stdio rpath proc tty' o/$m/examples/printargs.com 2>&1 | grep getuid | grep -o [[:digit:]]*)" = "$(id -u)" ]
checkem checkem
startit setuid geteuid startit setuid geteuid
[ "$($t/pledge.com -pstdio o/$m/examples/printargs.com 2>&1 | grep geteuid | grep -o [[:digit:]]*)" = "$(id -u)" ] [ "$($t/pledge.com -p 'stdio rpath proc tty' o/$m/examples/printargs.com 2>&1 | grep geteuid | grep -o [[:digit:]]*)" = "$(id -u)" ]
checkem checkem
startit setuid no capabilities startit setuid no capabilities
[ "$($t/pledge.com -pstdio o/$m/examples/printargs.com 2>&1 | grep CAP_ | wc -l)" = 0 ] [ "$($t/pledge.com -p 'stdio rpath proc tty' o/$m/examples/printargs.com 2>&1 | grep CAP_ | wc -l)" = 0 ]
checkem checkem
startit setuid maximum nice startit setuid maximum nice
$t/pledge.com -np 'stdio proc' o/$m/examples/printargs.com 2>&1 | grep SCHED_IDLE >/dev/null $t/pledge.com -np 'stdio rpath proc tty' o/$m/examples/printargs.com 2>&1 | grep SCHED_IDLE >/dev/null
checkem checkem
startit setuid chroot startit setuid chroot

View file

@ -82,7 +82,7 @@ function UnixTest()
unix.close(reader) unix.close(reader)
pid, ws = assert(unix.wait()) pid, ws = assert(unix.wait())
assert(unix.WIFSIGNALED(ws)) assert(unix.WIFSIGNALED(ws))
assert(unix.WTERMSIG(ws) == unix.SIGSYS) assert(unix.WTERMSIG(ws) == unix.SIGABRT)
elseif GetHostOs() == "OPENBSD" then elseif GetHostOs() == "OPENBSD" then
if assert(unix.fork()) == 0 then if assert(unix.fork()) == 0 then
assert(unix.pledge("stdio")) assert(unix.pledge("stdio"))

View file

@ -69,10 +69,10 @@ o/$(MODE)/third_party/chibicc/test/%.com.dbg: \
$(APE_NO_MODIFY_SELF) $(APE_NO_MODIFY_SELF)
@$(APELINK) @$(APELINK)
$(THIRD_PARTY_CHIBICC_TEST_OBJS): CC = $(CHIBICC) o/$(MODE)/third_party/chibicc/test/%.o: \
$(THIRD_PARTY_CHIBICC_TEST_OBJS): $(CHIBICC) third_party/chibicc/test/%.c \
$(CHIBICC)
.PRECIOUS: $(THIRD_PARTY_CHIBICC_TEST_OBJS) @$(COMPILE) -AOBJECTIFY.c $(CHIBICC) $(CHIBICC_FLAGS) $(OUTPUT_OPTION) -c $<
o/$(MODE)/third_party/chibicc/test/int128_test.o: QUOTA = -M1024m o/$(MODE)/third_party/chibicc/test/int128_test.o: QUOTA = -M1024m

View file

@ -21,6 +21,7 @@
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/ioctl.h" #include "libc/calls/ioctl.h"
#include "libc/calls/makedev.h" #include "libc/calls/makedev.h"
#include "libc/calls/pledge.h"
#include "libc/calls/strace.internal.h" #include "libc/calls/strace.internal.h"
#include "libc/calls/struct/bpf.h" #include "libc/calls/struct/bpf.h"
#include "libc/calls/struct/dirent.h" #include "libc/calls/struct/dirent.h"

View file

@ -117,6 +117,25 @@ o/$(MODE)/tool/build/printf.zip.o: o/$(MODE)/tool/build/printf
o/$(MODE)/tool/build/dd.zip.o: o/$(MODE)/tool/build/dd o/$(MODE)/tool/build/dd.zip.o: o/$(MODE)/tool/build/dd
@$(COMPILE) -AZIPOBJ $(ZIPOBJ) $(ZIPOBJ_FLAGS) -0 -B -Pbin $(OUTPUT_OPTION) $< @$(COMPILE) -AZIPOBJ $(ZIPOBJ) $(ZIPOBJ_FLAGS) -0 -B -Pbin $(OUTPUT_OPTION) $<
# we need pic because:
# so it can be an LD_PRELOAD payload
o/$(MODE)/tool/build/sandbox.o: \
OVERRIDE_CFLAGS += \
-fPIC
o/$(MODE)/tool/build/sandbox.so: \
o/$(MODE)/tool/build/sandbox.o \
o/$(MODE)/libc/calls/pledge-linux.o \
o/$(MODE)/libc/sysv/restorert.o
@$(COMPILE) -ALINK.so \
$(CC) \
-s \
-shared \
-nostdlib \
-Wl,--gc-sections \
$(LINKARGS) \
$(OUTPUT_OPTION)
.PHONY: o/$(MODE)/tool/build .PHONY: o/$(MODE)/tool/build
o/$(MODE)/tool/build: \ o/$(MODE)/tool/build: \
o/$(MODE)/tool/build/emucrt \ o/$(MODE)/tool/build/emucrt \

View file

@ -20,6 +20,7 @@
#include "libc/bits/safemacros.internal.h" #include "libc/bits/safemacros.internal.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/landlock.h" #include "libc/calls/landlock.h"
#include "libc/calls/pledge.h"
#include "libc/calls/struct/rlimit.h" #include "libc/calls/struct/rlimit.h"
#include "libc/calls/struct/sched_param.h" #include "libc/calls/struct/sched_param.h"
#include "libc/calls/struct/seccomp.h" #include "libc/calls/struct/seccomp.h"
@ -73,6 +74,7 @@ usage: pledge.com [-hnN] PROG ARGS...\n\
-u UID call setuid()\n\ -u UID call setuid()\n\
-c PATH call chroot()\n\ -c PATH call chroot()\n\
-v [PERM:]PATH call unveil(PATH, PERM[rwxc])\n\ -v [PERM:]PATH call unveil(PATH, PERM[rwxc])\n\
-k kill process rather than eperm'ing\n\
-n set maximum niceness\n\ -n set maximum niceness\n\
-D don't drop capabilities\n\ -D don't drop capabilities\n\
-N don't normalize file descriptors\n\ -N don't normalize file descriptors\n\
@ -118,6 +120,7 @@ int ParsePromises(const char *, unsigned long *);
int g_gflag; int g_gflag;
int g_uflag; int g_uflag;
int g_kflag;
int g_hflag; int g_hflag;
bool g_nice; bool g_nice;
bool g_noclose; bool g_noclose;
@ -140,14 +143,16 @@ static void GetOpts(int argc, char *argv[]) {
g_promises = 0; g_promises = 0;
g_fszquota = 256 * 1000 * 1000; g_fszquota = 256 * 1000 * 1000;
g_proquota = GetCpuCount() * 100; g_proquota = GetCpuCount() * 100;
g_fszquota = 4 * 1000 * 1000 * 1000;
g_memquota = 4L * 1024 * 1024 * 1024; g_memquota = 4L * 1024 * 1024 * 1024;
if (!sysinfo(&si)) g_memquota = si.totalram; if (!sysinfo(&si)) g_memquota = si.totalram;
while ((opt = getopt(argc, argv, "hnNp:u:g:c:C:D:P:M:F:v:")) != -1) { while ((opt = getopt(argc, argv, "hnkNp:u:g:c:C:D:P:M:F:v:")) != -1) {
switch (opt) { switch (opt) {
case 'n': case 'n':
g_nice = true; g_nice = true;
break; break;
case 'k':
g_kflag = true;
break;
case 'N': case 'N':
g_noclose = true; g_noclose = true;
break; break;
@ -453,10 +458,12 @@ void ApplyFilesystemPolicy(unsigned long ipromises) {
if (~ipromises & (1ul << PROMISE_PROT_EXEC)) { if (~ipromises & (1ul << PROMISE_PROT_EXEC)) {
if (UnveilIfExists("/usr/bin/ape", "rx") == -1) { if (UnveilIfExists("/usr/bin/ape", "rx") == -1) {
UnveilIfExists(xjoinpaths(firstnonnull(getenv("TMPDIR"), if ((p = getenv("TMPDIR"))) {
firstnonnull(getenv("HOME"), ".")), UnveilIfExists(xjoinpaths(p, ".ape"), "rx");
".ape"), }
"rx"); if ((p = getenv("HOME"))) {
UnveilIfExists(xjoinpaths(p, ".ape"), "rx");
}
} }
} }
@ -671,7 +678,11 @@ int main(int argc, char *argv[]) {
// model. we do this becasue it's only possible to have sigsys print // model. we do this becasue it's only possible to have sigsys print
// crash messages if we're not pledging exec, which is what this tool // crash messages if we're not pledging exec, which is what this tool
// always has to do currently. // always has to do currently.
__pledge_mode = SECCOMP_RET_ERRNO | EPERM; if (g_kflag) {
__pledge_mode = kPledgeModeKillProcess;
} else {
__pledge_mode = kPledgeModeErrno;
}
// apply sandbox // apply sandbox
if (pledge(g_promises, g_promises) == -1) { if (pledge(g_promises, g_promises) == -1) {

28
tool/build/sandbox.c Normal file
View file

@ -0,0 +1,28 @@
/*-*- 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/calls/pledge.h"
#include "libc/calls/pledge.internal.h"
#include "libc/intrin/promises.internal.h"
hidden char __privileged_start;
hidden char __privileged_end;
__attribute__((__constructor__)) void InitializeSandbox(void) {
sys_pledge_linux(~(1ul << PROMISE_STDIO), kPledgeModeErrno, false);
}

View file

@ -21,6 +21,7 @@
#include "libc/bits/safemacros.internal.h" #include "libc/bits/safemacros.internal.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/ioctl.h" #include "libc/calls/ioctl.h"
#include "libc/calls/pledge.h"
#include "libc/calls/struct/dirent.h" #include "libc/calls/struct/dirent.h"
#include "libc/calls/struct/flock.h" #include "libc/calls/struct/flock.h"
#include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.h"
@ -6587,7 +6588,7 @@ static void UnveilRedbean(void) {
} }
static int EnableSandbox(void) { static int EnableSandbox(void) {
__pledge_mode = SECCOMP_RET_ERRNO | EPERM; __pledge_mode = kPledgeModeErrno;
switch (sandboxed) { switch (sandboxed) {
case 0: case 0:
return 0; return 0;