mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-05-23 13:52:28 +00:00
Refactor pledge() to be more configurable
The earlier iterations did too much guesswork when it came to things like stderr logging and syscall origin verification. This change will make things more conformant to existing practices. The __pledge_mode extension now can be configured in a better way. There's also a new `-q` flag added to pledge.com, e.g. o//tool/build/pledge.com -qv. ls Is a good way to disable warnings about `tty` access attempts.
This commit is contained in:
parent
6b8b58fdf5
commit
625aa365f1
36 changed files with 203 additions and 168 deletions
|
@ -30,12 +30,8 @@ o/%.zip.o: o/% ; @$(COMPILE) -wAZIPOBJ $(ZIPOBJ) $(ZIPOBJ_FL
|
||||||
o/$(MODE)/%: o/$(MODE)/%.dbg ; @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@
|
o/$(MODE)/%: o/$(MODE)/%.dbg ; @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@
|
||||||
o/$(MODE)/%.o: %.s ; @$(COMPILE) -AOBJECTIFY.s $(OBJECTIFY.s) $(OUTPUT_OPTION) $<
|
o/$(MODE)/%.o: %.s ; @$(COMPILE) -AOBJECTIFY.s $(OBJECTIFY.s) $(OUTPUT_OPTION) $<
|
||||||
o/$(MODE)/%.o: o/$(MODE)/%.s ; @$(COMPILE) -AOBJECTIFY.s $(OBJECTIFY.s) $(OUTPUT_OPTION) $<
|
o/$(MODE)/%.o: o/$(MODE)/%.s ; @$(COMPILE) -AOBJECTIFY.s $(OBJECTIFY.s) $(OUTPUT_OPTION) $<
|
||||||
o/$(MODE)/%.s: %.S ; @$(COMPILE) -APREPROCESS $(PREPROCESS) $(OUTPUT_OPTION) $<
|
|
||||||
o/$(MODE)/%.s: o/$(MODE)/%.S ; @$(COMPILE) -APREPROCESS $(PREPROCESS) $(OUTPUT_OPTION) $<
|
|
||||||
o/$(MODE)/%.o: %.f ; @$(COMPILE) -AOBJECTIFY.f $(OBJECTIFY.f) $(OUTPUT_OPTION) $<
|
o/$(MODE)/%.o: %.f ; @$(COMPILE) -AOBJECTIFY.f $(OBJECTIFY.f) $(OUTPUT_OPTION) $<
|
||||||
o/$(MODE)/%.o: %.F ; @$(COMPILE) -AOBJECTIFY.F $(OBJECTIFY.F) $(OUTPUT_OPTION) $<
|
o/$(MODE)/%.o: %.F ; @$(COMPILE) -AOBJECTIFY.F $(OBJECTIFY.F) $(OUTPUT_OPTION) $<
|
||||||
o/$(MODE)/%.ss: %.c ; @$(COMPILE) -ACOMPILE.c $(COMPILE.c) $(OUTPUT_OPTION) $<
|
|
||||||
o/$(MODE)/%.ss: o/$(MODE)/%.c ; @$(COMPILE) -AOBJECTIFY.s $(COMPILE.c) $(OUTPUT_OPTION) $<
|
|
||||||
o/$(MODE)/%.h: %.c ; @$(COMPILE) -AAMALGAMATE $(PREPROCESS) $(OUTPUT_OPTION) -fdirectives-only -P $<
|
o/$(MODE)/%.h: %.c ; @$(COMPILE) -AAMALGAMATE $(PREPROCESS) $(OUTPUT_OPTION) -fdirectives-only -P $<
|
||||||
o/$(MODE)/%.h: o/$(MODE)/%.c ; @$(COMPILE) -AAMALGAMATE $(PREPROCESS) $(OUTPUT_OPTION) -fdirectives-only -P $<
|
o/$(MODE)/%.h: o/$(MODE)/%.c ; @$(COMPILE) -AAMALGAMATE $(PREPROCESS) $(OUTPUT_OPTION) -fdirectives-only -P $<
|
||||||
o/$(MODE)/%.o: %.S ; @$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) $<
|
o/$(MODE)/%.o: %.S ; @$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) $<
|
||||||
|
|
|
@ -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, __pledge_mode, false);
|
rc = weaken(sys_pledge_linux)(__execpromises, __pledge_mode);
|
||||||
}
|
}
|
||||||
if (!rc) {
|
if (!rc) {
|
||||||
rc = sys_execve(prog, argv, envp);
|
rc = sys_execve(prog, argv, envp);
|
||||||
|
|
|
@ -903,15 +903,13 @@ static privileged int SigProcMask(int how, int64_t set, int64_t *old) {
|
||||||
|
|
||||||
static privileged void KillThisProcess(void) {
|
static privileged void KillThisProcess(void) {
|
||||||
int ax;
|
int ax;
|
||||||
struct sigaction dfl = {.sa_sigaction = SIG_DFL};
|
SigAction(Sigabrt, &(struct sigaction){0}, 0);
|
||||||
if (!SigAction(Sigabrt, &dfl, 0)) {
|
SigProcMask(Sig_Setmask, -1, 0);
|
||||||
SigProcMask(Sig_Setmask, -1, 0);
|
asm volatile("syscall"
|
||||||
asm volatile("syscall"
|
: "=a"(ax)
|
||||||
: "=a"(ax)
|
: "0"(__NR_linux_kill), "D"(GetPid()), "S"(Sigabrt)
|
||||||
: "0"(__NR_linux_kill), "D"(GetPid()), "S"(Sigabrt)
|
: "rcx", "r11", "memory");
|
||||||
: "rcx", "r11", "memory");
|
SigProcMask(Sig_Setmask, 0, 0);
|
||||||
SigProcMask(Sig_Setmask, 0, 0);
|
|
||||||
}
|
|
||||||
asm volatile("syscall"
|
asm volatile("syscall"
|
||||||
: "=a"(ax)
|
: "=a"(ax)
|
||||||
: "0"(__NR_linux_exit_group), "D"(128 + Sigabrt)
|
: "0"(__NR_linux_exit_group), "D"(128 + Sigabrt)
|
||||||
|
@ -920,15 +918,13 @@ static privileged void KillThisProcess(void) {
|
||||||
|
|
||||||
static privileged void KillThisThread(void) {
|
static privileged void KillThisThread(void) {
|
||||||
int ax;
|
int ax;
|
||||||
struct sigaction dfl = {.sa_sigaction = SIG_DFL};
|
SigAction(Sigabrt, &(struct sigaction){0}, 0);
|
||||||
if (!SigAction(Sigabrt, &dfl, 0)) {
|
SigProcMask(Sig_Setmask, -1, 0);
|
||||||
SigProcMask(Sig_Setmask, -1, 0);
|
asm volatile("syscall"
|
||||||
asm volatile("syscall"
|
: "=a"(ax)
|
||||||
: "=a"(ax)
|
: "0"(__NR_linux_tkill), "D"(GetTid()), "S"(Sigabrt)
|
||||||
: "0"(__NR_linux_tkill), "D"(GetTid()), "S"(Sigabrt)
|
: "rcx", "r11", "memory");
|
||||||
: "rcx", "r11", "memory");
|
SigProcMask(Sig_Setmask, 0, 0);
|
||||||
SigProcMask(Sig_Setmask, 0, 0);
|
|
||||||
}
|
|
||||||
asm volatile("syscall"
|
asm volatile("syscall"
|
||||||
: /* no outputs */
|
: /* no outputs */
|
||||||
: "a"(__NR_linux_exit), "D"(128 + Sigabrt)
|
: "a"(__NR_linux_exit), "D"(128 + Sigabrt)
|
||||||
|
@ -959,10 +955,9 @@ static privileged int HasSyscall(struct Pledges *p, uint16_t n) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static privileged void OnSigSys(int sig, siginfo_t *si, ucontext_t *ctx) {
|
static privileged void OnSigSys(int sig, siginfo_t *si, ucontext_t *ctx) {
|
||||||
int i, ok;
|
|
||||||
bool found;
|
bool found;
|
||||||
char ord[17], rip[17];
|
char ord[17], rip[17];
|
||||||
enum PledgeMode mode = si->si_errno;
|
int i, ok, mode = si->si_errno;
|
||||||
ctx->uc_mcontext.rax = -Eperm;
|
ctx->uc_mcontext.rax = -Eperm;
|
||||||
FixCpy(ord, si->si_syscall, 12);
|
FixCpy(ord, si->si_syscall, 12);
|
||||||
HexCpy(rip, ctx->uc_mcontext.rip);
|
HexCpy(rip, ctx->uc_mcontext.rip);
|
||||||
|
@ -978,11 +973,11 @@ static privileged void OnSigSys(int sig, siginfo_t *si, ucontext_t *ctx) {
|
||||||
Log("error: bad syscall (", GetSyscallName(si->si_syscall), " ord=", ord,
|
Log("error: bad syscall (", GetSyscallName(si->si_syscall), " ord=", ord,
|
||||||
" rip=", rip, ")\n", 0);
|
" rip=", rip, ")\n", 0);
|
||||||
}
|
}
|
||||||
switch (mode) {
|
switch (mode & PLEDGE_PENALTY_MASK) {
|
||||||
case kPledgeModeKillProcess:
|
case PLEDGE_PENALTY_KILL_PROCESS:
|
||||||
KillThisProcess();
|
KillThisProcess();
|
||||||
// fallthrough
|
// fallthrough
|
||||||
case kPledgeModeKillThread:
|
case PLEDGE_PENALTY_KILL_THREAD:
|
||||||
KillThisThread();
|
KillThisThread();
|
||||||
unreachable;
|
unreachable;
|
||||||
default:
|
default:
|
||||||
|
@ -1016,6 +1011,7 @@ static privileged void AppendFilter(struct Filter *f, struct sock_filter *p,
|
||||||
// - kill(getpid(), SIGABRT) to abort process
|
// - kill(getpid(), SIGABRT) to abort process
|
||||||
// - tkill(gettid(), SIGABRT) to abort thread
|
// - tkill(gettid(), SIGABRT) to abort thread
|
||||||
// - sigaction(SIGABRT) to force default signal handler
|
// - sigaction(SIGABRT) to force default signal handler
|
||||||
|
// - sigreturn() to return from signal handler
|
||||||
// - sigprocmask() to force signal delivery
|
// - sigprocmask() to force signal delivery
|
||||||
//
|
//
|
||||||
static privileged void AllowMonitor(struct Filter *f) {
|
static privileged void AllowMonitor(struct Filter *f) {
|
||||||
|
@ -1044,6 +1040,7 @@ static privileged void AllowMonitor(struct Filter *f) {
|
||||||
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, Sigabrt, 0, 1),
|
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, Sigabrt, 0, 1),
|
||||||
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
|
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
|
||||||
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)),
|
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)),
|
||||||
|
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_sigreturn, 1, 0),
|
||||||
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_sigprocmask, 0, 1),
|
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_sigprocmask, 0, 1),
|
||||||
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
|
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
|
||||||
};
|
};
|
||||||
|
@ -1897,17 +1894,12 @@ static privileged void AppendPledge(struct Filter *f, //
|
||||||
* Installs SECCOMP BPF filter on Linux thread.
|
* Installs SECCOMP BPF filter on Linux thread.
|
||||||
*
|
*
|
||||||
* @param ipromises is inverted integer bitmask of pledge() promises
|
* @param ipromises is inverted integer bitmask of pledge() promises
|
||||||
* @param mode configures the course of action on sandbox violations
|
|
||||||
* @param want_msyscall if set will cause syscall origin checking to be
|
|
||||||
* enabled, but only if `exec` hasn't been pledged
|
|
||||||
* @return 0 on success, or negative error number on error
|
* @return 0 on success, or negative error number on error
|
||||||
* @asyncsignalsafe
|
* @asyncsignalsafe
|
||||||
* @threadsafe
|
* @threadsafe
|
||||||
* @vforksafe
|
* @vforksafe
|
||||||
*/
|
*/
|
||||||
privileged int sys_pledge_linux(unsigned long ipromises, //
|
privileged int sys_pledge_linux(unsigned long ipromises, int mode) {
|
||||||
enum PledgeMode mode, //
|
|
||||||
bool want_msyscall) { //
|
|
||||||
struct Filter f;
|
struct Filter f;
|
||||||
int i, e, rc = -1;
|
int i, e, rc = -1;
|
||||||
struct sock_filter sf[1] = {BPF_STMT(BPF_RET | BPF_K, 0)};
|
struct sock_filter sf[1] = {BPF_STMT(BPF_RET | BPF_K, 0)};
|
||||||
|
@ -1921,9 +1913,6 @@ privileged int sys_pledge_linux(unsigned long ipromises, //
|
||||||
// when _Exit() gets called since we need to fallback to _Exit1()
|
// when _Exit() gets called since we need to fallback to _Exit1()
|
||||||
AppendFilter(&f, PLEDGE(kFilterIgnoreExitGroup));
|
AppendFilter(&f, PLEDGE(kFilterIgnoreExitGroup));
|
||||||
}
|
}
|
||||||
if (want_msyscall && !(~ipromises & (1ul << PROMISE_EXEC))) {
|
|
||||||
AppendOriginVerification(&f);
|
|
||||||
}
|
|
||||||
AppendPledge(&f, PLEDGE(kPledgeDefault));
|
AppendPledge(&f, PLEDGE(kPledgeDefault));
|
||||||
for (i = 0; i < ARRAYLEN(kPledge); ++i) {
|
for (i = 0; i < ARRAYLEN(kPledge); ++i) {
|
||||||
if (~ipromises & (1ul << i)) {
|
if (~ipromises & (1ul << i)) {
|
||||||
|
@ -1936,25 +1925,9 @@ privileged int sys_pledge_linux(unsigned long ipromises, //
|
||||||
}
|
}
|
||||||
|
|
||||||
// now determine what we'll do on sandbox violations
|
// now determine what we'll do on sandbox violations
|
||||||
if (~ipromises & (1ul << PROMISE_EXEC)) {
|
if (mode & PLEDGE_STDERR_LOGGING) {
|
||||||
// our sigsys error message handler can't be inherited across
|
// trapping mode
|
||||||
// execve() boundaries so if you've pledged exec then that'll
|
//
|
||||||
// mean no error messages for you.
|
|
||||||
switch (mode) {
|
|
||||||
case kPledgeModeKillThread:
|
|
||||||
sf[0].k = SECCOMP_RET_KILL_THREAD;
|
|
||||||
break;
|
|
||||||
case kPledgeModeKillProcess:
|
|
||||||
sf[0].k = SECCOMP_RET_KILL_PROCESS;
|
|
||||||
break;
|
|
||||||
case kPledgeModeErrno:
|
|
||||||
sf[0].k = SECCOMP_RET_ERRNO | Eperm;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
unreachable;
|
|
||||||
}
|
|
||||||
AppendFilter(&f, PLEDGE(sf));
|
|
||||||
} else {
|
|
||||||
// if we haven't pledged exec, then we can monitor SIGSYS
|
// if we haven't pledged exec, then we can monitor SIGSYS
|
||||||
// and print a helpful error message when things do break
|
// and print a helpful error message when things do break
|
||||||
// to avoid tls / static memory, we embed mode within bpf
|
// to avoid tls / static memory, we embed mode within bpf
|
||||||
|
@ -1962,6 +1935,29 @@ privileged int sys_pledge_linux(unsigned long ipromises, //
|
||||||
AllowMonitor(&f);
|
AllowMonitor(&f);
|
||||||
sf[0].k = SECCOMP_RET_TRAP | (mode & SECCOMP_RET_DATA);
|
sf[0].k = SECCOMP_RET_TRAP | (mode & SECCOMP_RET_DATA);
|
||||||
AppendFilter(&f, PLEDGE(sf));
|
AppendFilter(&f, PLEDGE(sf));
|
||||||
|
} else {
|
||||||
|
// non-trapping mode
|
||||||
|
//
|
||||||
|
// 1. our sigsys error message handler can't be inherited across
|
||||||
|
// execve() boundaries so if you've pledged exec then that'll
|
||||||
|
// mean no error messages for you.
|
||||||
|
//
|
||||||
|
// 2. we do not trap pledge("", 0) because that would go against
|
||||||
|
// its documented purpose of only permitted exit().
|
||||||
|
switch (mode & PLEDGE_PENALTY_MASK) {
|
||||||
|
case PLEDGE_PENALTY_KILL_THREAD:
|
||||||
|
sf[0].k = SECCOMP_RET_KILL_THREAD;
|
||||||
|
break;
|
||||||
|
case PLEDGE_PENALTY_KILL_PROCESS:
|
||||||
|
sf[0].k = SECCOMP_RET_KILL_PROCESS;
|
||||||
|
break;
|
||||||
|
case PLEDGE_PENALTY_RETURN_EPERM:
|
||||||
|
sf[0].k = SECCOMP_RET_ERRNO | Eperm;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
AppendFilter(&f, PLEDGE(sf));
|
||||||
}
|
}
|
||||||
|
|
||||||
// drop privileges
|
// drop privileges
|
||||||
|
|
|
@ -44,23 +44,8 @@
|
||||||
* an error, because the specified operations will be permitted.
|
* an error, because the specified operations will be permitted.
|
||||||
*
|
*
|
||||||
* The promises you give pledge() define which system calls are allowed.
|
* The promises you give pledge() define which system calls are allowed.
|
||||||
* Error messages are logged when sandbox violations occur that well you
|
* Error messages are logged when sandbox violations occur, but how that
|
||||||
* which promise was needed, to stderr on Linux and /var/log/messages on
|
* happens depends on the `mode` parameter (see below).
|
||||||
* OpenBSD, and the unwatchable termination signal should be SIGABRT.
|
|
||||||
*
|
|
||||||
* Standard error logging can't happen on Linux if you use the `exec`
|
|
||||||
* promise, since we polyfill logging in userspace which can't cross
|
|
||||||
* execve() boundaries. However once you pledge away `exec` it works.
|
|
||||||
* Another inconsistency that pledging `exec` causes, is your process
|
|
||||||
* termination signal may become SIGSYS rather than SIGABRT.
|
|
||||||
*
|
|
||||||
* On Linux, our SECCOMP BPF polyfill offers more configurability in
|
|
||||||
* terms of behavior. It's possible to choose different behaviors that
|
|
||||||
* determine how sandbox violations are handled.
|
|
||||||
*
|
|
||||||
* __pledge_mode = kPledgeModeKillThread; // kill thread [default]
|
|
||||||
* __pledge_mode = kPledgeModeKillProcess; // kill all threads
|
|
||||||
* __pledge_mode = kPledgeModeErrno; // just return EPERM
|
|
||||||
*
|
*
|
||||||
* Timing is everything with pledge. It's designed to be a voluntary
|
* Timing is everything with pledge. It's designed to be a voluntary
|
||||||
* self-imposed security model. That works best when programs perform
|
* self-imposed security model. That works best when programs perform
|
||||||
|
@ -91,13 +76,6 @@
|
||||||
* User and group IDs can't be changed once pledge is in effect. OpenBSD
|
* User and group IDs can't be changed once pledge is in effect. OpenBSD
|
||||||
* should ignore chown without crashing; whereas Linux will just EPERM.
|
* should ignore chown without crashing; whereas Linux will just EPERM.
|
||||||
*
|
*
|
||||||
* Memory functions won't permit creating executable code after pledge.
|
|
||||||
* Restrictions on origin of SYSCALL instructions will become enforced
|
|
||||||
* on Linux (cf. msyscall) after pledge too, which means the process
|
|
||||||
* gets killed if SYSCALL is used outside the .privileged section. One
|
|
||||||
* exception is if the "exec" group is specified, in which case these
|
|
||||||
* restrictions need to be loosened.
|
|
||||||
*
|
|
||||||
* Using pledge is irreversible. On Linux it causes PR_SET_NO_NEW_PRIVS
|
* Using pledge is irreversible. On Linux it causes PR_SET_NO_NEW_PRIVS
|
||||||
* to be set on your process; however, if "id" or "recvfd" are allowed
|
* to be set on your process; however, if "id" or "recvfd" are allowed
|
||||||
* then then they theoretically could permit the gaining of some new
|
* then then they theoretically could permit the gaining of some new
|
||||||
|
@ -177,16 +155,13 @@
|
||||||
*
|
*
|
||||||
* - "settime" allows settimeofday and clock_adjtime.
|
* - "settime" allows settimeofday and clock_adjtime.
|
||||||
*
|
*
|
||||||
* - "exec" allows execve, execveat. On Linux, using this promise will
|
* - "exec" allows execve, execveat. Note that `exec` alone might not be
|
||||||
* cause (1) system call origin verification to be disabled; (2) error
|
* enough by itself to let your executable be executed. For dynamic,
|
||||||
* logging will be disabled; and (3) your termination signals might
|
* interpreted, and ape binaries, you'll usually want `rpath` and
|
||||||
* become SIGSYS instead of SIGABRT. Another thing to note is that
|
* `prot_exec` too. With APE it's possible to work around this
|
||||||
* `exec` alone might not be enough by itself to let your executable
|
* requirement, by "assimilating" your binaries beforehand. See the
|
||||||
* be executed. For dynamic, interpreted, and ape binaries, you'll
|
* assimilate.com program and `--assimilate` flag which can be used to
|
||||||
* usually want `rpath` and `prot_exec` too. With APE it's possible to
|
* turn APE binaries into static native binaries.
|
||||||
* work around this requirement, by "assimilating" your binaries
|
|
||||||
* beforehand. See the assimilate.com program and `--assimilate` flag
|
|
||||||
* which can be used to turn APE binaries into static native binaries.
|
|
||||||
*
|
*
|
||||||
* - "prot_exec" allows mmap(PROT_EXEC) and mprotect(PROT_EXEC). This is
|
* - "prot_exec" allows mmap(PROT_EXEC) and mprotect(PROT_EXEC). This is
|
||||||
* needed to (1) code morph mutexes in __enable_threads(), and it's
|
* needed to (1) code morph mutexes in __enable_threads(), and it's
|
||||||
|
@ -218,6 +193,41 @@
|
||||||
* has to do this before calling sys_execve(), the executed process will
|
* has to do this before calling sys_execve(), the executed process will
|
||||||
* be weakened to have execute permissions too.
|
* be weakened to have execute permissions too.
|
||||||
*
|
*
|
||||||
|
* `__pledge_mode` is available to improve the experience of pledge() on
|
||||||
|
* Linux. It should specify one of the following penalties:
|
||||||
|
*
|
||||||
|
* - `PLEDGE_PENALTY_KILL_THREAD` causes the violating thread to be
|
||||||
|
* killed. This is the default on Linux. It's effectively the same as
|
||||||
|
* killing the process, since redbean has no threads. The termination
|
||||||
|
* signal can't be caught and will be either `SIGSYS` or `SIGABRT`.
|
||||||
|
* Consider enabling stderr logging below so you'll know why your
|
||||||
|
* program failed. Otherwise check the system log.
|
||||||
|
*
|
||||||
|
* - `PLEDGE_PENALTY_KILL_PROCESS` causes the process and all its
|
||||||
|
* threads to be killed. This is always the case on OpenBSD.
|
||||||
|
*
|
||||||
|
* - `PLEDGE_PENALTY_RETURN_EPERM` causes system calls to just return an
|
||||||
|
* `EPERM` error instead of killing. This is a gentler solution that
|
||||||
|
* allows code to display a friendly warning. Please note this may
|
||||||
|
* lead to weird behaviors if the software being sandboxed is lazy
|
||||||
|
* about checking error results.
|
||||||
|
*
|
||||||
|
* `mode` may optionally bitwise or the following flags:
|
||||||
|
*
|
||||||
|
* - `PLEDGE_STDERR_LOGGING` enables friendly error message logging
|
||||||
|
* letting you know which promises are needed whenever violations
|
||||||
|
* occur. Without this, violations will be logged to `dmesg` on Linux
|
||||||
|
* if the penalty is to kill the process. You would then need to
|
||||||
|
* manually look up the system call number and then cross reference it
|
||||||
|
* with the cosmopolitan libc pledge() documentation. You can also use
|
||||||
|
* `strace -ff` which is easier. This is ignored OpenBSD, which
|
||||||
|
* already has a good system log. Turning on stderr logging (which
|
||||||
|
* uses SECCOMP trapping) also means that the `WTERMSIG()` on your
|
||||||
|
* killed processes will always be `SIGABRT` on both Linux and
|
||||||
|
* OpenBSD. Otherwise, Linux prefers to raise `SIGSYS`. Enabling this
|
||||||
|
* option might not be a good idea if you're pledging `exec` because
|
||||||
|
* subprocesses can't inherit the `SIGSYS` handler this installs.
|
||||||
|
*
|
||||||
* @return 0 on success, or -1 w/ errno
|
* @return 0 on success, or -1 w/ errno
|
||||||
* @raise EINVAL if `execpromises` on Linux isn't a subset of `promises`
|
* @raise EINVAL if `execpromises` on Linux isn't a subset of `promises`
|
||||||
* @raise EINVAL if `promises` allows exec and `execpromises` is null
|
* @raise EINVAL if `promises` allows exec and `execpromises` is null
|
||||||
|
@ -240,7 +250,7 @@ int pledge(const char *promises, const char *execpromises) {
|
||||||
STRACE("execpromises must be a subset of promises");
|
STRACE("execpromises must be a subset of promises");
|
||||||
rc = einval();
|
rc = einval();
|
||||||
} else {
|
} else {
|
||||||
rc = sys_pledge_linux(ipromises, __pledge_mode, true);
|
rc = sys_pledge_linux(ipromises, __pledge_mode);
|
||||||
if (rc > -4096u) errno = -rc, rc = -1;
|
if (rc > -4096u) errno = -rc, rc = -1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
#ifndef COSMOPOLITAN_LIBC_CALLS_PLEDGE_H_
|
#ifndef COSMOPOLITAN_LIBC_CALLS_PLEDGE_H_
|
||||||
#define COSMOPOLITAN_LIBC_CALLS_PLEDGE_H_
|
#define COSMOPOLITAN_LIBC_CALLS_PLEDGE_H_
|
||||||
|
|
||||||
|
#define PLEDGE_PENALTY_KILL_THREAD 0x0000
|
||||||
|
#define PLEDGE_PENALTY_KILL_PROCESS 0x0001
|
||||||
|
#define PLEDGE_PENALTY_RETURN_EPERM 0x0002
|
||||||
|
#define PLEDGE_PENALTY_MASK 0x000f
|
||||||
|
#define PLEDGE_STDERR_LOGGING 0x0010
|
||||||
|
|
||||||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||||
COSMOPOLITAN_C_START_
|
COSMOPOLITAN_C_START_
|
||||||
|
|
||||||
enum PledgeMode {
|
extern int __pledge_mode;
|
||||||
kPledgeModeKillThread,
|
|
||||||
kPledgeModeKillProcess,
|
|
||||||
kPledgeModeErrno,
|
|
||||||
};
|
|
||||||
|
|
||||||
extern enum PledgeMode __pledge_mode;
|
|
||||||
|
|
||||||
COSMOPOLITAN_C_END_
|
COSMOPOLITAN_C_END_
|
||||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||||
|
|
|
@ -13,7 +13,7 @@ struct Pledges {
|
||||||
|
|
||||||
hidden extern const struct Pledges kPledge[PROMISE_LEN_];
|
hidden extern const struct Pledges kPledge[PROMISE_LEN_];
|
||||||
|
|
||||||
int sys_pledge_linux(unsigned long, enum PledgeMode, bool) hidden;
|
int sys_pledge_linux(unsigned long, int) hidden;
|
||||||
int ParsePromises(const char *, unsigned long *) hidden;
|
int ParsePromises(const char *, unsigned long *) hidden;
|
||||||
|
|
||||||
COSMOPOLITAN_C_END_
|
COSMOPOLITAN_C_END_
|
||||||
|
|
|
@ -21,6 +21,6 @@
|
||||||
|
|
||||||
// XXX: should be inherited thread local
|
// XXX: should be inherited thread local
|
||||||
// see also sys_pledge_linux() which is 100% pure
|
// see also sys_pledge_linux() which is 100% pure
|
||||||
enum PledgeMode __pledge_mode;
|
int __pledge_mode;
|
||||||
unsigned long __promises;
|
unsigned long __promises;
|
||||||
unsigned long __execpromises;
|
unsigned long __execpromises;
|
||||||
|
|
|
@ -61,7 +61,7 @@ void SetUp(void) {
|
||||||
TEST(pledge, testSoftError) {
|
TEST(pledge, testSoftError) {
|
||||||
if (IsOpenbsd()) return;
|
if (IsOpenbsd()) return;
|
||||||
SPAWN(fork);
|
SPAWN(fork);
|
||||||
__pledge_mode = kPledgeModeErrno;
|
__pledge_mode = PLEDGE_PENALTY_RETURN_EPERM;
|
||||||
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);
|
||||||
|
@ -70,7 +70,7 @@ TEST(pledge, testSoftError) {
|
||||||
|
|
||||||
TEST(pledge, testKillThreadMode) {
|
TEST(pledge, testKillThreadMode) {
|
||||||
SPAWN(fork);
|
SPAWN(fork);
|
||||||
__pledge_mode = kPledgeModeKillThread;
|
__pledge_mode = PLEDGE_PENALTY_KILL_THREAD | PLEDGE_STDERR_LOGGING;
|
||||||
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(SIGABRT);
|
TERMS(SIGABRT);
|
||||||
|
@ -78,7 +78,7 @@ TEST(pledge, testKillThreadMode) {
|
||||||
|
|
||||||
TEST(pledge, testKillProcessMode) {
|
TEST(pledge, testKillProcessMode) {
|
||||||
SPAWN(fork);
|
SPAWN(fork);
|
||||||
__pledge_mode = kPledgeModeKillProcess;
|
__pledge_mode = PLEDGE_PENALTY_KILL_PROCESS | PLEDGE_STDERR_LOGGING;
|
||||||
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(SIGABRT);
|
TERMS(SIGABRT);
|
||||||
|
@ -90,7 +90,7 @@ TEST(pledge, testLogMessage_inSoftyMode) {
|
||||||
char msg[256] = {0};
|
char msg[256] = {0};
|
||||||
ASSERT_SYS(0, 0, pipe(fds));
|
ASSERT_SYS(0, 0, pipe(fds));
|
||||||
SPAWN(fork);
|
SPAWN(fork);
|
||||||
__pledge_mode = kPledgeModeErrno;
|
__pledge_mode = PLEDGE_PENALTY_RETURN_EPERM | PLEDGE_STDERR_LOGGING;
|
||||||
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));
|
||||||
|
@ -108,7 +108,7 @@ TEST(pledge, testLogMessage_onKillProcess) {
|
||||||
char msg[256] = {0};
|
char msg[256] = {0};
|
||||||
ASSERT_SYS(0, 0, pipe(fds));
|
ASSERT_SYS(0, 0, pipe(fds));
|
||||||
SPAWN(fork);
|
SPAWN(fork);
|
||||||
__pledge_mode = kPledgeModeKillThread;
|
__pledge_mode = PLEDGE_PENALTY_KILL_THREAD | PLEDGE_STDERR_LOGGING;
|
||||||
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);
|
||||||
|
@ -121,25 +121,18 @@ TEST(pledge, testLogMessage_onKillProcess) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(pledge, testNoLogOrAbrtsignoPossibleSadly_becausePledgedExec) {
|
|
||||||
int fds[2];
|
|
||||||
char msg[256] = {0};
|
|
||||||
ASSERT_SYS(0, 0, pipe(fds));
|
|
||||||
SPAWN(fork);
|
|
||||||
ASSERT_SYS(0, 2, dup2(fds[1], 2));
|
|
||||||
ASSERT_SYS(0, 0, pledge("stdio exec", "stdio exec"));
|
|
||||||
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
||||||
TERMS(IsOpenbsd() ? SIGABRT : SIGSYS);
|
|
||||||
close(fds[1]);
|
|
||||||
read(fds[0], msg, sizeof(msg));
|
|
||||||
close(fds[0]);
|
|
||||||
ASSERT_STREQ("", msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(pledge, testDoublePledge_isFine) {
|
TEST(pledge, testDoublePledge_isFine) {
|
||||||
SPAWN(fork);
|
SPAWN(fork);
|
||||||
__pledge_mode = kPledgeModeKillThread;
|
__pledge_mode = PLEDGE_PENALTY_KILL_THREAD;
|
||||||
ASSERT_SYS(0, 0, pledge("stdio", 0));
|
ASSERT_SYS(0, 0, pledge("stdio", 0));
|
||||||
ASSERT_SYS(0, 0, pledge("stdio", 0));
|
ASSERT_SYS(0, 0, pledge("stdio", 0));
|
||||||
EXITS(0);
|
EXITS(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(pledge, testEmptyPledge_doesntUseTrapping) {
|
||||||
|
SPAWN(fork);
|
||||||
|
__pledge_mode = PLEDGE_PENALTY_KILL_PROCESS;
|
||||||
|
ASSERT_SYS(0, 0, pledge("", 0));
|
||||||
|
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||||
|
TERMS(IsOpenbsd() ? SIGABRT : SIGSYS);
|
||||||
|
}
|
||||||
|
|
|
@ -86,7 +86,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;
|
__pledge_mode = PLEDGE_PENALTY_RETURN_EPERM;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(pledge, default_allowsExit) {
|
TEST(pledge, default_allowsExit) {
|
||||||
|
@ -116,7 +116,7 @@ TEST(pledge, execpromises_notok) {
|
||||||
int ws, pid;
|
int ws, pid;
|
||||||
ASSERT_NE(-1, (pid = fork()));
|
ASSERT_NE(-1, (pid = fork()));
|
||||||
if (!pid) {
|
if (!pid) {
|
||||||
__pledge_mode = kPledgeModeErrno;
|
__pledge_mode = PLEDGE_PENALTY_RETURN_EPERM;
|
||||||
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);
|
||||||
|
@ -158,7 +158,7 @@ TEST(pledge, stdio_forbidsOpeningPasswd1) {
|
||||||
|
|
||||||
TEST(pledge, stdio_forbidsOpeningPasswd2) {
|
TEST(pledge, stdio_forbidsOpeningPasswd2) {
|
||||||
int ws, pid;
|
int ws, pid;
|
||||||
__pledge_mode = kPledgeModeKillProcess;
|
__pledge_mode = PLEDGE_PENALTY_KILL_PROCESS;
|
||||||
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));
|
||||||
|
|
|
@ -375,7 +375,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(SIGABRT, WTERMSIG(ws));
|
ASSERT_EQ(IsOpenbsd() ? SIGABRT : SIGSYS, WTERMSIG(ws));
|
||||||
EXPECT_SYS(0, 0, munmap(gotsome, FRAMESIZE));
|
EXPECT_SYS(0, 0, munmap(gotsome, FRAMESIZE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
-- 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.
|
||||||
|
|
||||||
unix.pledge("stdio")
|
assert(unix.pledge("stdio"))
|
||||||
|
|
||||||
assert(assert(argon2.hash_encoded("password", "somesalt", {
|
assert(assert(argon2.hash_encoded("password", "somesalt", {
|
||||||
variant = argon2.variants.argon2_i,
|
variant = argon2.variants.argon2_i,
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
-- 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.
|
||||||
|
|
||||||
unix.pledge('stdio')
|
assert(unix.pledge('stdio'))
|
||||||
|
|
||||||
local function test(s, b64)
|
local function test(s, b64)
|
||||||
assert(EncodeBase64(s) == b64)
|
assert(EncodeBase64(s) == b64)
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
-- 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.
|
||||||
|
|
||||||
unix.pledge("stdio")
|
assert(unix.pledge("stdio"))
|
||||||
|
|
||||||
assert(EncodeJson(nil) == "null")
|
assert(EncodeJson(nil) == "null")
|
||||||
assert(EncodeJson(true) == "true")
|
assert(EncodeJson(true) == "true")
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
-- 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.
|
||||||
|
|
||||||
unix.pledge("stdio")
|
assert(unix.pledge("stdio"))
|
||||||
|
|
||||||
assert(EncodeLua(nil) == "nil")
|
assert(EncodeLua(nil) == "nil")
|
||||||
assert(EncodeLua(true) == "true")
|
assert(EncodeLua(true) == "true")
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
unix.pledge("stdio")
|
assert(unix.pledge("stdio"))
|
||||||
|
|
||||||
-- https://www.json.org/JSON_checker/test.zip
|
-- https://www.json.org/JSON_checker/test.zip
|
||||||
-- JSON parsing sample test case: fail11.json
|
-- JSON parsing sample test case: fail11.json
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
unix.pledge("stdio")
|
assert(unix.pledge("stdio"))
|
||||||
|
|
||||||
-- https://www.json.org/JSON_checker/test.zip
|
-- https://www.json.org/JSON_checker/test.zip
|
||||||
-- JSON parsing sample test case: pass1.json
|
-- JSON parsing sample test case: pass1.json
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
-- SOFTWARE.
|
-- SOFTWARE.
|
||||||
--
|
--
|
||||||
|
|
||||||
unix.pledge("stdio")
|
assert(unix.pledge("stdio"))
|
||||||
|
|
||||||
-- these test cases are prefixed with n_
|
-- these test cases are prefixed with n_
|
||||||
-- ljson should reject all of them as invalid
|
-- ljson should reject all of them as invalid
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
-- SOFTWARE.
|
-- SOFTWARE.
|
||||||
--
|
--
|
||||||
|
|
||||||
unix.pledge("stdio")
|
assert(unix.pledge("stdio"))
|
||||||
|
|
||||||
-- these test cases are prefixed with n_
|
-- these test cases are prefixed with n_
|
||||||
-- ljson should reject all of them as invalid
|
-- ljson should reject all of them as invalid
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
-- SOFTWARE.
|
-- SOFTWARE.
|
||||||
--
|
--
|
||||||
|
|
||||||
unix.pledge("stdio")
|
assert(unix.pledge("stdio"))
|
||||||
|
|
||||||
-- these test cases are prefixed with n_
|
-- these test cases are prefixed with n_
|
||||||
-- ljson should reject all of them as invalid
|
-- ljson should reject all of them as invalid
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
-- SOFTWARE.
|
-- SOFTWARE.
|
||||||
--
|
--
|
||||||
|
|
||||||
unix.pledge("stdio")
|
assert(unix.pledge("stdio"))
|
||||||
|
|
||||||
-- these test cases are prefixed with n_
|
-- these test cases are prefixed with n_
|
||||||
-- ljson should reject all of them as invalid
|
-- ljson should reject all of them as invalid
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
-- SOFTWARE.
|
-- SOFTWARE.
|
||||||
--
|
--
|
||||||
|
|
||||||
unix.pledge("stdio")
|
assert(unix.pledge("stdio"))
|
||||||
|
|
||||||
-- these test cases are prefixed with i_
|
-- these test cases are prefixed with i_
|
||||||
-- ljson is free to accept or reject,
|
-- ljson is free to accept or reject,
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
-- SOFTWARE.
|
-- SOFTWARE.
|
||||||
--
|
--
|
||||||
|
|
||||||
unix.pledge("stdio")
|
assert(unix.pledge("stdio"))
|
||||||
|
|
||||||
-- these test cases are prefixed with y_
|
-- these test cases are prefixed with y_
|
||||||
-- ljson should accept all of them as valid
|
-- ljson should accept all of them as valid
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
-- 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.
|
||||||
|
|
||||||
unix.pledge("stdio")
|
assert(unix.pledge("stdio"))
|
||||||
|
|
||||||
x = Rdtsc()
|
x = Rdtsc()
|
||||||
y = Rdtsc()
|
y = Rdtsc()
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
-- 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.
|
||||||
|
|
||||||
unix.pledge("stdio")
|
assert(unix.pledge("stdio"))
|
||||||
|
|
||||||
assert(EncodeLua(assert(DecodeJson[[ 0 ]])) == '0' )
|
assert(EncodeLua(assert(DecodeJson[[ 0 ]])) == '0' )
|
||||||
assert(EncodeLua(assert(DecodeJson[[ [1] ]])) == '{1}')
|
assert(EncodeLua(assert(DecodeJson[[ [1] ]])) == '{1}')
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
-- 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.
|
||||||
|
|
||||||
unix.pledge("stdio")
|
assert(unix.pledge("stdio"))
|
||||||
|
|
||||||
assert(string.match("127.123.231.1", "%d+.%d+.%d+.%d+"))
|
assert(string.match("127.123.231.1", "%d+.%d+.%d+.%d+"))
|
||||||
assert(re.search([[^\d{1,3}(\.\d{1,3}){3}$]], "127.123.231.1"))
|
assert(re.search([[^\d{1,3}(\.\d{1,3}){3}$]], "127.123.231.1"))
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
-- 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.
|
||||||
|
|
||||||
unix.pledge("stdio")
|
assert(unix.pledge("stdio"))
|
||||||
|
|
||||||
-- test redbean lua language extensions
|
-- test redbean lua language extensions
|
||||||
assert(0b100 == 4)
|
assert(0b100 == 4)
|
||||||
|
|
|
@ -59,7 +59,7 @@ function UnixTest()
|
||||||
-- fork
|
-- fork
|
||||||
-- basic subprocess creation
|
-- basic subprocess creation
|
||||||
if assert(unix.fork()) == 0 then
|
if assert(unix.fork()) == 0 then
|
||||||
unix.pledge("")
|
assert(unix.pledge(""))
|
||||||
unix.exit(42)
|
unix.exit(42)
|
||||||
end
|
end
|
||||||
pid, ws = assert(unix.wait())
|
pid, ws = assert(unix.wait())
|
||||||
|
@ -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.SIGABRT)
|
assert(unix.WTERMSIG(ws) == unix.SIGSYS)
|
||||||
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"))
|
||||||
|
@ -160,7 +160,7 @@ function main()
|
||||||
assert(unix.makedirs(tmpdir))
|
assert(unix.makedirs(tmpdir))
|
||||||
unix.unveil(tmpdir, "rwc")
|
unix.unveil(tmpdir, "rwc")
|
||||||
unix.unveil(nil, nil)
|
unix.unveil(nil, nil)
|
||||||
unix.pledge("stdio rpath wpath cpath proc")
|
assert(unix.pledge("stdio rpath wpath cpath proc"))
|
||||||
ok, err = pcall(UnixTest)
|
ok, err = pcall(UnixTest)
|
||||||
if ok then
|
if ok then
|
||||||
assert(unix.rmrf(tmpdir))
|
assert(unix.rmrf(tmpdir))
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
-- 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.
|
||||||
|
|
||||||
unix.pledge('stdio')
|
assert(unix.pledge('stdio'))
|
||||||
|
|
||||||
assert(Md5(nil) == nil)
|
assert(Md5(nil) == nil)
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
-- 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.
|
||||||
|
|
||||||
unix.pledge("stdio")
|
assert(unix.pledge("stdio"))
|
||||||
|
|
||||||
assert("/usr/lib" == path.dirname("/usr/lib/foo.bar"))
|
assert("/usr/lib" == path.dirname("/usr/lib/foo.bar"))
|
||||||
assert("/usr" == path.dirname("/usr/lib"))
|
assert("/usr" == path.dirname("/usr/lib"))
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
-- 5869). As with RFC 4634, code to perform SHA-based Hashed Message
|
-- 5869). As with RFC 4634, code to perform SHA-based Hashed Message
|
||||||
-- Authentication Codes (HMACs) is also included.
|
-- Authentication Codes (HMACs) is also included.
|
||||||
|
|
||||||
unix.pledge('stdio')
|
assert(unix.pledge('stdio'))
|
||||||
|
|
||||||
-- SHA-1
|
-- SHA-1
|
||||||
assert(Sha1(nil) == nil)
|
assert(Sha1(nil) == nil)
|
||||||
|
|
|
@ -40,7 +40,7 @@ local function main()
|
||||||
assert(unix.makedirs(tmpdir))
|
assert(unix.makedirs(tmpdir))
|
||||||
unix.unveil(tmpdir, "rwc")
|
unix.unveil(tmpdir, "rwc")
|
||||||
unix.unveil(nil, nil)
|
unix.unveil(nil, nil)
|
||||||
unix.pledge("stdio rpath wpath cpath")
|
assert(unix.pledge("stdio rpath wpath cpath"))
|
||||||
ok, err = pcall(SlurpTest)
|
ok, err = pcall(SlurpTest)
|
||||||
if ok then
|
if ok then
|
||||||
assert(unix.rmrf(tmpdir))
|
assert(unix.rmrf(tmpdir))
|
||||||
|
|
10
third_party/lua/lunix.c
vendored
10
third_party/lua/lunix.c
vendored
|
@ -1377,12 +1377,12 @@ static int LuaUnixSiocgifconf(lua_State *L) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sandbox.pledge([promises:str[, execpromises:str]])
|
// sandbox.pledge([promises:str[, execpromises:str[, mode:int]]])
|
||||||
// ├─→ true
|
// ├─→ true
|
||||||
// └─→ nil, unix.Errno
|
// └─→ nil, unix.Errno
|
||||||
static int LuaUnixPledge(lua_State *L) {
|
static int LuaUnixPledge(lua_State *L) {
|
||||||
int olderr = errno;
|
int olderr = errno;
|
||||||
__pledge_mode = 0;
|
__pledge_mode = luaL_optinteger(L, 3, 0);
|
||||||
return SysretBool(L, "pledge", olderr,
|
return SysretBool(L, "pledge", olderr,
|
||||||
pledge(luaL_checkstring(L, 1), luaL_optstring(L, 2, 0)));
|
pledge(luaL_checkstring(L, 1), luaL_optstring(L, 2, 0)));
|
||||||
}
|
}
|
||||||
|
@ -2829,5 +2829,11 @@ int LuaUnix(lua_State *L) {
|
||||||
LuaSetIntField(L, "PATH_MAX", _PATH_MAX);
|
LuaSetIntField(L, "PATH_MAX", _PATH_MAX);
|
||||||
LuaSetIntField(L, "PIPE_BUF", PIPE_BUF);
|
LuaSetIntField(L, "PIPE_BUF", PIPE_BUF);
|
||||||
|
|
||||||
|
// pledge() flags
|
||||||
|
LuaSetIntField(L, "PLEDGE_PENALTY_KILL_THREAD", PLEDGE_PENALTY_KILL_THREAD);
|
||||||
|
LuaSetIntField(L, "PLEDGE_PENALTY_KILL_PROCESS", PLEDGE_PENALTY_KILL_PROCESS);
|
||||||
|
LuaSetIntField(L, "PLEDGE_PENALTY_RETURN_EPERM", PLEDGE_PENALTY_RETURN_EPERM);
|
||||||
|
LuaSetIntField(L, "PLEDGE_STDERR_LOGGING", PLEDGE_STDERR_LOGGING);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* runs pledge at glibc executable load time, e.g.
|
* runs pledge at glibc executable load time, e.g.
|
||||||
* strace -vff bash -c '_PLEDGE=4194303,0,1 LD_PRELOAD=$HOME/sandbox.so ls'
|
* strace -vff bash -c '_PLEDGE=4194303,0 LD_PRELOAD=$HOME/sandbox.so ls'
|
||||||
*/
|
*/
|
||||||
|
|
||||||
hidden uint8_t __privileged_start[1];
|
hidden uint8_t __privileged_start[1];
|
||||||
|
@ -33,9 +33,9 @@ hidden uint8_t __privileged_end[1];
|
||||||
__attribute__((__constructor__)) void init(void) {
|
__attribute__((__constructor__)) void init(void) {
|
||||||
int c, i, j;
|
int c, i, j;
|
||||||
const char *s;
|
const char *s;
|
||||||
uint64_t arg[3] = {0};
|
uint64_t arg[2] = {0};
|
||||||
s = getenv("_PLEDGE");
|
s = getenv("_PLEDGE");
|
||||||
for (i = j = 0; i < 3; ++i) {
|
for (i = j = 0; i < 2; ++i) {
|
||||||
while ((c = s[j] & 255)) {
|
while ((c = s[j] & 255)) {
|
||||||
++j;
|
++j;
|
||||||
if ('0' <= c & c <= '9') {
|
if ('0' <= c & c <= '9') {
|
||||||
|
@ -46,5 +46,5 @@ __attribute__((__constructor__)) void init(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sys_pledge_linux(~arg[0], arg[1], arg[2]);
|
sys_pledge_linux(~arg[0], arg[1]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,7 @@ usage: pledge.com [-hnN] PROG ARGS...\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\
|
||||||
-V disable unveiling (only pledge)\n\
|
-V disable unveiling (only pledge)\n\
|
||||||
|
-q disable stderr violation logging\n\
|
||||||
-k kill process rather than eperm'ing\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\
|
||||||
|
@ -130,6 +131,7 @@ int g_uflag;
|
||||||
int g_kflag;
|
int g_kflag;
|
||||||
int g_hflag;
|
int g_hflag;
|
||||||
bool g_nice;
|
bool g_nice;
|
||||||
|
bool g_qflag;
|
||||||
bool isdynamic;
|
bool isdynamic;
|
||||||
bool g_noclose;
|
bool g_noclose;
|
||||||
long g_cpuquota;
|
long g_cpuquota;
|
||||||
|
@ -157,11 +159,14 @@ static void GetOpts(int argc, char *argv[]) {
|
||||||
g_proquota = GetCpuCount() * 100;
|
g_proquota = GetCpuCount() * 100;
|
||||||
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, "hnkNVT:p:u:g:c:C:D:P:M:F:v:")) != -1) {
|
while ((opt = getopt(argc, argv, "hnqkNVT:p: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 'q':
|
||||||
|
g_qflag = true;
|
||||||
|
break;
|
||||||
case 'k':
|
case 'k':
|
||||||
g_kflag = true;
|
g_kflag = true;
|
||||||
break;
|
break;
|
||||||
|
@ -758,15 +763,18 @@ int main(int argc, char *argv[]) {
|
||||||
// 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.
|
||||||
if (g_kflag) {
|
if (g_kflag) {
|
||||||
__pledge_mode = kPledgeModeKillProcess;
|
__pledge_mode = PLEDGE_PENALTY_KILL_PROCESS;
|
||||||
} else {
|
} else {
|
||||||
__pledge_mode = kPledgeModeErrno;
|
__pledge_mode = PLEDGE_PENALTY_RETURN_EPERM;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we need to be able to call execv and mmap the dso
|
// we need to be able to call execv and mmap the dso
|
||||||
// it'll be pledged away once/if the dso gets loaded
|
// it'll be pledged away once/if the dso gets loaded
|
||||||
if (!(~ipromises & (1ul << PROMISE_EXEC))) {
|
if (!(~ipromises & (1ul << PROMISE_EXEC))) {
|
||||||
g_promises = xstrcat(g_promises, ' ', "exec");
|
g_promises = xstrcat(g_promises, ' ', "exec");
|
||||||
|
if (!g_qflag) {
|
||||||
|
__pledge_mode |= PLEDGE_STDERR_LOGGING;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (isdynamic) {
|
if (isdynamic) {
|
||||||
g_promises = xstrcat(g_promises, ' ', "prot_exec");
|
g_promises = xstrcat(g_promises, ' ', "prot_exec");
|
||||||
|
@ -774,8 +782,7 @@ int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
// pass arguments to pledge() inside the dso
|
// pass arguments to pledge() inside the dso
|
||||||
if (isdynamic) {
|
if (isdynamic) {
|
||||||
ksnprintf(buf, sizeof(buf), "_PLEDGE=%ld,%ld,%ld", ~ipromises,
|
ksnprintf(buf, sizeof(buf), "_PLEDGE=%ld,%ld", ~ipromises, __pledge_mode);
|
||||||
__pledge_mode, false);
|
|
||||||
putenv(buf);
|
putenv(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3832,7 +3832,7 @@ UNIX MODULE
|
||||||
|
|
||||||
See the unix.Rusage section below for details on returned fields.
|
See the unix.Rusage section below for details on returned fields.
|
||||||
|
|
||||||
unix.pledge([promises:str[, execpromises:str]])
|
unix.pledge([promises:str[, execpromises:str[, mode:int]]])
|
||||||
├─→ true
|
├─→ true
|
||||||
└─→ nil, unix.Errno
|
└─→ nil, unix.Errno
|
||||||
|
|
||||||
|
@ -3862,13 +3862,6 @@ UNIX MODULE
|
||||||
OpenBSD should ignore the chown functions without crashing. Linux
|
OpenBSD should ignore the chown functions without crashing. Linux
|
||||||
will just EPERM.
|
will just EPERM.
|
||||||
|
|
||||||
Memory functions won't permit creating executable code after pledge.
|
|
||||||
Restrictions on origin of SYSCALL instructions will become enforced
|
|
||||||
on Linux (cf. msyscall) after pledge too, which means the process
|
|
||||||
gets killed if SYSCALL is used outside the .privileged section. One
|
|
||||||
exception is if the "exec" group is specified, in which case these
|
|
||||||
restrictions need to be loosened.
|
|
||||||
|
|
||||||
`promises` is a string that may include any of the following groups
|
`promises` is a string that may include any of the following groups
|
||||||
delimited by spaces. This list has been curated to focus on the
|
delimited by spaces. This list has been curated to focus on the
|
||||||
system calls for which this module provides wrappers. See the
|
system calls for which this module provides wrappers. See the
|
||||||
|
@ -3985,6 +3978,39 @@ UNIX MODULE
|
||||||
Since Linux has to do this before calling sys_execve(), the executed
|
Since Linux has to do this before calling sys_execve(), the executed
|
||||||
process will be weakened to have execute permissions too.
|
process will be weakened to have execute permissions too.
|
||||||
|
|
||||||
|
`mode` if specified should specify one penalty:
|
||||||
|
|
||||||
|
- `unix.PLEDGE_PENALTY_KILL_THREAD` causes the violating thread to
|
||||||
|
be killed. This is the default on Linux. It's effectively the
|
||||||
|
same as killing the process, since redbean has no threads. The
|
||||||
|
termination signal can't be caught and will be either `SIGSYS`
|
||||||
|
or `SIGABRT`. Consider enabling stderr logging below so you'll
|
||||||
|
know why your program failed. Otherwise check the system log.
|
||||||
|
|
||||||
|
- `unix.PLEDGE_PENALTY_KILL_PROCESS` causes the process and all
|
||||||
|
its threads to be killed. This is always the case on OpenBSD.
|
||||||
|
|
||||||
|
- `unix.PLEDGE_PENALTY_RETURN_EPERM` causes system calls to just
|
||||||
|
return an `EPERM` error instead of killing. This is a gentler
|
||||||
|
solution that allows code to display a friendly warning. Please
|
||||||
|
note this may lead to weird behaviors if the software being
|
||||||
|
sandboxed is lazy about checking error results.
|
||||||
|
|
||||||
|
`mode` may optionally bitwise or the following flags:
|
||||||
|
|
||||||
|
- `unix.PLEDGE_STDERR_LOGGING` enables friendly error message
|
||||||
|
logging letting you know which promises are needed whenever
|
||||||
|
violations occur. Without this, violations will be logged to
|
||||||
|
`dmesg` on Linux if the penalty is to kill the process. You
|
||||||
|
would then need to manually look up the system call number and
|
||||||
|
then cross reference it with the cosmopolitan libc pledge()
|
||||||
|
documentation. You can also use `strace -ff` which is easier.
|
||||||
|
This is ignored OpenBSD, which already has a good system log.
|
||||||
|
Turning on stderr logging (which uses SECCOMP trapping) also
|
||||||
|
means that the `unix.WTERMSIG()` on your killed processes will
|
||||||
|
always be `unix.SIGABRT` on both Linux and OpenBSD. Otherwise,
|
||||||
|
Linux prefers to raise `unix.SIGSYS`.
|
||||||
|
|
||||||
unix.unveil(path:str, permissions:str)
|
unix.unveil(path:str, permissions:str)
|
||||||
├─→ true
|
├─→ true
|
||||||
└─→ nil, unix.Errno
|
└─→ nil, unix.Errno
|
||||||
|
|
|
@ -6588,7 +6588,7 @@ static void UnveilRedbean(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static int EnableSandbox(void) {
|
static int EnableSandbox(void) {
|
||||||
__pledge_mode = kPledgeModeErrno;
|
__pledge_mode = PLEDGE_PENALTY_RETURN_EPERM | PLEDGE_STDERR_LOGGING;
|
||||||
switch (sandboxed) {
|
switch (sandboxed) {
|
||||||
case 0:
|
case 0:
|
||||||
return 0;
|
return 0;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue