Write some tests for unveil()

See #490
This commit is contained in:
Justine Tunney 2022-07-18 02:11:06 -07:00
parent 1c6b5c0acd
commit 38c3fa63fe
14 changed files with 646 additions and 352 deletions

View file

@ -16,6 +16,14 @@
#define LANDLOCK_ACCESS_FS_MAKE_FIFO 0x0400ul #define LANDLOCK_ACCESS_FS_MAKE_FIFO 0x0400ul
#define LANDLOCK_ACCESS_FS_MAKE_BLOCK 0x0800ul #define LANDLOCK_ACCESS_FS_MAKE_BLOCK 0x0800ul
#define LANDLOCK_ACCESS_FS_MAKE_SYM 0x1000ul #define LANDLOCK_ACCESS_FS_MAKE_SYM 0x1000ul
/**
* Allow renaming or linking file to a different directory.
*
* @see https://lore.kernel.org/r/20220329125117.1393824-8-mic@digikod.net
* @see https://docs.kernel.org/userspace-api/landlock.html
* @note ABI 2+
*/
#define LANDLOCK_ACCESS_FS_REFER 0x2000ul #define LANDLOCK_ACCESS_FS_REFER 0x2000ul
#if !(__ASSEMBLER__ + __LINKER__ + 0) #if !(__ASSEMBLER__ + __LINKER__ + 0)

View file

@ -19,6 +19,7 @@
#include "libc/assert.h" #include "libc/assert.h"
#include "libc/bits/weaken.h" #include "libc/bits/weaken.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall-sysv.internal.h"
#include "libc/intrin/kprintf.h" #include "libc/intrin/kprintf.h"
#include "libc/intrin/lockcmpxchgp.h" #include "libc/intrin/lockcmpxchgp.h"
@ -40,7 +41,7 @@ relegated void __assert_fail(const char *expr, const char *file, int line) {
owner = 0; owner = 0;
me = sys_gettid(); me = sys_gettid();
kprintf("%s:%d: assert(%s) failed (tid %d)\n", file, line, expr, me); kprintf("%s:%d: assert(%s) failed (tid %d)\n", file, line, expr, me);
if (_lockcmpxchgp(&sync, &owner, me)) { if (__vforked || _lockcmpxchgp(&sync, &owner, me)) {
__restore_tty(); __restore_tty();
if (weaken(ShowBacktrace)) { if (weaken(ShowBacktrace)) {
weaken(ShowBacktrace)(2, __builtin_frame_address(0)); weaken(ShowBacktrace)(2, __builtin_frame_address(0));

View file

@ -19,7 +19,8 @@
#define PROMISE_EXEC 15 #define PROMISE_EXEC 15
#define PROMISE_EXECNATIVE 16 #define PROMISE_EXECNATIVE 16
#define PROMISE_ID 17 #define PROMISE_ID 17
#define PROMISE_MAX 17 #define PROMISE_UNVEIL 18
#define PROMISE_MAX 18
#define PLEDGED(x) (~__promises & (1L << PROMISE_##x)) #define PLEDGED(x) (~__promises & (1L << PROMISE_##x))

View file

@ -288,7 +288,7 @@ relegated void __oncrash(int sig, struct siginfo *si, ucontext_t *ctx) {
--__strace; --__strace;
owner = 0; owner = 0;
me = sys_gettid(); me = sys_gettid();
if (_lockcmpxchgp(&sync, &owner, me)) { if (__vforked || _lockcmpxchgp(&sync, &owner, me)) {
if (!__vforked) { if (!__vforked) {
rip = ctx ? ctx->uc_mcontext.rip : 0; rip = ctx ? ctx->uc_mcontext.rip : 0;
err = errno; err = errno;

View file

@ -302,6 +302,12 @@ static const uint16_t kPledgeLinuxExec2[] = {
__NR_linux_execveat, // __NR_linux_execveat, //
}; };
static const uint16_t kPledgeLinuxUnveil[] = {
__NR_linux_landlock_create_ruleset, //
__NR_linux_landlock_add_rule, //
__NR_linux_landlock_restrict_self, //
};
static const struct Pledges { static const struct Pledges {
const char *name; const char *name;
const uint16_t *syscalls; const uint16_t *syscalls;
@ -325,6 +331,7 @@ static const struct Pledges {
[PROMISE_EXEC] = {"exec", PLEDGE(kPledgeLinuxExec)}, // [PROMISE_EXEC] = {"exec", PLEDGE(kPledgeLinuxExec)}, //
[PROMISE_EXECNATIVE] = {"execnative", PLEDGE(kPledgeLinuxExec2)}, // [PROMISE_EXECNATIVE] = {"execnative", PLEDGE(kPledgeLinuxExec2)}, //
[PROMISE_ID] = {"id", PLEDGE(kPledgeLinuxId)}, // [PROMISE_ID] = {"id", PLEDGE(kPledgeLinuxId)}, //
[PROMISE_UNVEIL] = {"unveil", PLEDGE(kPledgeLinuxUnveil)}, //
[PROMISE_MAX + 1] = {0}, // [PROMISE_MAX + 1] = {0}, //
}; };
@ -1231,6 +1238,10 @@ static void SetPromises(const char *promises) {
* System call origin verification can't be enabled. If you always * 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.
* *
* - "unveil" allows unveil() to be called, as well as the underlying
* landlock_create_ruleset, landlock_add_rule, landlock_restrict_self
* calls on Linux.
*
* @return 0 on success, or -1 w/ errno * @return 0 on success, or -1 w/ errno
* @raise ENOSYS if host os isn't Linux or OpenBSD * @raise ENOSYS if host os isn't Linux or OpenBSD
* @raise EINVAL if `execpromises` is used on Linux * @raise EINVAL if `execpromises` is used on Linux

View file

@ -26,6 +26,7 @@
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/mem/mem.h" #include "libc/mem/mem.h"
#include "libc/str/str.h" #include "libc/str/str.h"
#include "libc/sysv/consts/f.h"
#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/pr.h" #include "libc/sysv/consts/pr.h"
#include "libc/sysv/consts/s.h" #include "libc/sysv/consts/s.h"
@ -45,7 +46,7 @@
* - Integrate with pledge and remove the file access? * - Integrate with pledge and remove the file access?
* - Stuff state into the .protected section? * - Stuff state into the .protected section?
*/ */
static struct { _Thread_local static struct {
int abi; int abi;
int fd; int fd;
uint64_t read; uint64_t read;
@ -54,7 +55,6 @@ static struct {
uint64_t create; uint64_t create;
} State = { } State = {
.abi = 2, .abi = 2,
.fd = 0,
.read = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR | .read = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR |
LANDLOCK_ACCESS_FS_REFER, LANDLOCK_ACCESS_FS_REFER,
.write = LANDLOCK_ACCESS_FS_WRITE_FILE, .write = LANDLOCK_ACCESS_FS_WRITE_FILE,
@ -65,21 +65,27 @@ static struct {
LANDLOCK_ACCESS_FS_MAKE_BLOCK | LANDLOCK_ACCESS_FS_MAKE_SYM, LANDLOCK_ACCESS_FS_MAKE_BLOCK | LANDLOCK_ACCESS_FS_MAKE_SYM,
}; };
forceinline int unveil_final(void) { static int unveil_final(void) {
int rc; int rc;
if (State.fd == -1) return 0; if (State.fd == -1) return 0;
assert(State.fd > 0); assert(State.fd > 0);
if ((rc = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) != -1 && if ((rc = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) != -1 &&
(rc = landlock_restrict_self(State.fd, 0)) != -1 && (rc = landlock_restrict_self(State.fd, 0)) != -1 &&
(rc = close(State.fd)) != -1) (rc = sys_close(State.fd)) != -1)
State.fd = -1; State.fd = -1;
return rc; return rc;
} }
forceinline int unveil_init(void) { static int err_close(int rc, int fd) {
int rc; int serrno = errno;
if ((rc = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION)) < sys_close(fd);
0) errno = serrno;
return rc;
}
static int unveil_init(void) {
int rc, fd;
if ((rc = landlock_create_ruleset(0, 0, LANDLOCK_CREATE_RULESET_VERSION)) < 0)
return -1; return -1;
State.abi = rc; State.abi = rc;
if (State.abi < 2) State.read &= ~LANDLOCK_ACCESS_FS_REFER; if (State.abi < 2) State.read &= ~LANDLOCK_ACCESS_FS_REFER;
@ -87,21 +93,17 @@ forceinline int unveil_init(void) {
.handled_access_fs = State.read | State.write | State.exec | State.create, .handled_access_fs = State.read | State.write | State.exec | State.create,
}; };
if ((rc = landlock_create_ruleset(&attr, sizeof(attr), 0)) < 0) return -1; if ((rc = landlock_create_ruleset(&attr, sizeof(attr), 0)) < 0) return -1;
State.fd = rc; // grant file descriptor a higher number that's less likely to interfere
if ((fd = __sys_fcntl(rc, F_DUPFD, 100)) == -1) return err_close(-1, rc);
if (sys_close(rc) == -1) return err_close(-1, fd);
State.fd = fd;
return 0; return 0;
} }
forceinline int err_close(int rc, int fd) {
int serrno = errno;
close(fd);
errno = serrno;
return rc;
}
static int sys_unveil_linux(const char *path, const char *permissions) { static int sys_unveil_linux(const char *path, const char *permissions) {
int rc; int rc;
if (State.fd == 0 && (rc = unveil_init()) == -1) return rc; if (!State.fd && (rc = unveil_init()) == -1) return rc;
if (path == NULL && permissions == NULL) return unveil_final(); if (!path && !permissions) return unveil_final();
struct landlock_path_beneath_attr pb = {0}; struct landlock_path_beneath_attr pb = {0};
for (const char *c = permissions; *c != '\0'; c++) { for (const char *c = permissions; *c != '\0'; c++) {
switch (*c) { switch (*c) {
@ -121,17 +123,20 @@ static int sys_unveil_linux(const char *path, const char *permissions) {
return einval(); return einval();
} }
} }
if ((rc = open(path, O_RDONLY | O_PATH | O_CLOEXEC)) == -1) return rc; if ((rc = sys_open(path, O_PATH | O_CLOEXEC, 0)) == -1) return rc;
pb.parent_fd = rc; pb.parent_fd = rc;
struct stat st; struct stat st;
if ((rc = fstat(pb.parent_fd, &st)) == -1) return err_close(rc, pb.parent_fd); 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 (!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 = landlock_add_rule(State.fd, LANDLOCK_RULE_PATH_BENEATH, &pb, 0)))
return err_close(rc, pb.parent_fd); return err_close(rc, pb.parent_fd);
close(pb.parent_fd); sys_close(pb.parent_fd);
return rc; return rc;
} }
/**
* Unveil parts of a restricted filesystem view.
*/
int unveil(const char *path, const char *permissions) { int unveil(const char *path, const char *permissions) {
int rc; int rc;
if (IsLinux()) { if (IsLinux()) {

View file

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

View file

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

View file

@ -322,5 +322,29 @@
#define __NR_linux_io_uring_setup 0x01a9 #define __NR_linux_io_uring_setup 0x01a9
#define __NR_linux_io_uring_enter 0x01aa #define __NR_linux_io_uring_enter 0x01aa
#define __NR_linux_io_uring_register 0x01ab #define __NR_linux_io_uring_register 0x01ab
#define __NR_linux_open_tree 0x01ac
#define __NR_linux_move_mount 0x01ad
#define __NR_linux_fsopen 0x01ae
#define __NR_linux_fsconfig 0x01af
#define __NR_linux_fsmount 0x01b0
#define __NR_linux_fspick 0x01b1
#define __NR_linux_pidfd_open 0x01b2
#define __NR_linux_clone3 0x01b3
#define __NR_linux_close_range 0x01b4
#define __NR_linux_openat2 0x01b5
#define __NR_linux_pidfd_getfd 0x01b6
#define __NR_linux_faccessat2 0x01b7
#define __NR_linux_process_madvise 0x01b8
#define __NR_linux_epoll_pwait2 0x01b9
#define __NR_linux_mount_setattr 0x01ba
#define __NR_linux_quotactl_fd 0x01bb
#define __NR_linux_landlock_create_ruleset 0x01bc
#define __NR_linux_landlock_add_rule 0x01bd
#define __NR_linux_landlock_restrict_self 0x01be
#define __NR_linux_memfd_secret 0x01bf
#define __NR_linux_process_mrelease 0x01c0
#define __NR_linux_futex_waitv 0x01c1
#define __NR_linux_set_mempolicy_home_node 0x01c2
#define __NR_linux_sys_unveil 0x0fff
#endif /* COSMOPOLITAN_LIBC_SYSV_CONSTS_NRLINUX_H_ */ #endif /* COSMOPOLITAN_LIBC_SYSV_CONSTS_NRLINUX_H_ */

View file

@ -399,7 +399,6 @@ scall memfd_secret 0xfffffffffffff1bf globl
scall process_mrelease 0xfffffffffffff1c0 globl scall process_mrelease 0xfffffffffffff1c0 globl
scall futex_waitv 0xfffffffffffff1c1 globl scall futex_waitv 0xfffffffffffff1c1 globl
scall set_mempolicy_home_node 0xfffffffffffff1c2 globl scall set_mempolicy_home_node 0xfffffffffffff1c2 globl
scall sys_unveil 0xfff072ffffffffff globl
# The Fifth Bell System Interface, Community Edition # The Fifth Bell System Interface, Community Edition
# » besiyata dishmaya # » besiyata dishmaya
@ -799,7 +798,7 @@ scall sendsyslog 0xfff070ffffffffff globl
scall setrtable 0xfff136ffffffffff globl scall setrtable 0xfff136ffffffffff globl
scall swapctl 0x10f0c1ffffffffff globl scall swapctl 0x10f0c1ffffffffff globl
scall thrkill 0xfff077ffffffffff globl scall thrkill 0xfff077ffffffffff globl
scall unveil 0xfff072ffffffffff globl scall sys_unveil 0xfff072ffffffffff globl hidden
# The Fifth Bell System Interface, Community Edition # The Fifth Bell System Interface, Community Edition
# » beyond the pale # » beyond the pale

View file

@ -20,6 +20,7 @@
#include "libc/bits/atomic.h" #include "libc/bits/atomic.h"
#include "libc/bits/weaken.h" #include "libc/bits/weaken.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/strace.internal.h" #include "libc/calls/strace.internal.h"
#include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.h"
@ -63,7 +64,7 @@ void testlib_finish(void) {
void testlib_error_enter(const char *file, const char *func) { void testlib_error_enter(const char *file, const char *func) {
atomic_fetch_sub_explicit(&__ftrace, 1, memory_order_relaxed); atomic_fetch_sub_explicit(&__ftrace, 1, memory_order_relaxed);
atomic_fetch_sub_explicit(&__strace, 1, memory_order_relaxed); atomic_fetch_sub_explicit(&__strace, 1, memory_order_relaxed);
_spinlock(&testlib_error_lock); if (!__vforked) _spinlock(&testlib_error_lock);
if (!IsWindows()) sys_getpid(); /* make strace easier to read */ if (!IsWindows()) sys_getpid(); /* make strace easier to read */
if (!IsWindows()) sys_getpid(); if (!IsWindows()) sys_getpid();
if (g_testlib_shoulddebugbreak) { if (g_testlib_shoulddebugbreak) {

22
test/libc/mem/life.c Normal file
View file

@ -0,0 +1,22 @@
/*-*- 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.
*/
int main(int argc, char *argv[]) {
return 42;
}

View file

@ -42,8 +42,11 @@ TEST_LIBC_MEM_DIRECTDEPS = \
LIBC_STR \ LIBC_STR \
LIBC_STUBS \ LIBC_STUBS \
LIBC_SYSV \ LIBC_SYSV \
LIBC_THREAD \ LIBC_SYSV_CALLS \
LIBC_TESTLIB \ LIBC_TESTLIB \
LIBC_THREAD \
LIBC_X \
LIBC_ZIPOS \
THIRD_PARTY_DLMALLOC \ THIRD_PARTY_DLMALLOC \
THIRD_PARTY_LIBCXX THIRD_PARTY_LIBCXX
@ -58,11 +61,34 @@ o/$(MODE)/test/libc/mem/%.com.dbg: \
$(TEST_LIBC_MEM_DEPS) \ $(TEST_LIBC_MEM_DEPS) \
o/$(MODE)/test/libc/mem/%.o \ o/$(MODE)/test/libc/mem/%.o \
o/$(MODE)/test/libc/mem/mem.pkg \ o/$(MODE)/test/libc/mem/mem.pkg \
o/$(MODE)/test/libc/mem/life.elf.zip.o \
$(LIBC_TESTMAIN) \ $(LIBC_TESTMAIN) \
$(CRT) \ $(CRT) \
$(APE_NO_MODIFY_SELF) $(APE_NO_MODIFY_SELF)
@$(APELINK) @$(APELINK)
o/$(MODE)/test/libc/mem/life.com.dbg: \
$(LIBC_RUNTIME) \
o/$(MODE)/test/libc/mem/life.o \
$(CRT) \
$(APE)
@$(APELINK)
o/$(MODE)/test/libc/mem/life.elf: \
o/$(MODE)/tool/build/assimilate.com \
o/$(MODE)/test/libc/mem/life.com
@$(COMPILE) -ACP -T$@ \
build/bootstrap/cp.com \
o/$(MODE)/test/libc/mem/life.com \
o/$(MODE)/test/libc/mem/life.elf
@$(COMPILE) -AASSIMILATE -T$@ \
o/$(MODE)/tool/build/assimilate.com \
o/$(MODE)/test/libc/mem/life.elf
o/$(MODE)/test/libc/mem/life.elf.zip.o: \
ZIPOBJ_FLAGS += \
-B
$(TEST_LIBC_MEM_OBJS): \ $(TEST_LIBC_MEM_OBJS): \
DEFAULT_CCFLAGS += \ DEFAULT_CCFLAGS += \
-fno-builtin -fno-builtin

198
test/libc/mem/unveil_test.c Normal file
View file

@ -0,0 +1,198 @@
/*-*- 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/landlock.h"
#include "libc/calls/struct/dirent.h"
#include "libc/calls/struct/stat.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/kprintf.h"
#include "libc/mem/io.h"
#include "libc/runtime/gc.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/s.h"
#include "libc/testlib/testlib.h"
#include "libc/x/x.h"
STATIC_YOINK("zip_uri_support");
#define EACCES_OR_ENOENT (IsOpenbsd() ? ENOENT : EACCES)
#define SPAWN() \
{ \
int ws, pid; \
ASSERT_NE(-1, (pid = fork())); \
if (!pid) {
#define EXITS(rc) \
_Exit(0); \
} \
ASSERT_NE(-1, wait(&ws)); \
ASSERT_TRUE(WIFEXITED(ws)); \
ASSERT_EQ(rc, WEXITSTATUS(ws)); \
}
char testlib_enable_tmp_setup_teardown;
struct stat st;
static bool SupportsLandlock(void) {
int e = errno;
bool r = landlock_create_ruleset(0, 0, LANDLOCK_CREATE_RULESET_VERSION) >= 0;
errno = e;
return r;
}
__attribute__((__constructor__)) static void init(void) {
if (!(IsLinux() && SupportsLandlock()) && !IsOpenbsd()) exit(0);
}
void SetUp(void) {
// make sure zipos maps executable into memory early
ASSERT_SYS(0, 0, stat("/zip/life.elf", &st));
}
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);
}
TEST(unveil, api_differences) {
SPAWN();
ASSERT_SYS(0, 0, stat("/", &st));
ASSERT_SYS(0, 0, unveil(".", "rw"));
if (IsOpenbsd()) {
// openbsd imposes restrictions immediately
ASSERT_SYS(ENOENT, -1, open("/", O_RDONLY | O_DIRECTORY));
} else {
// restrictions on linux don't go into effect until unveil(0,0)
ASSERT_SYS(0, 3, open("/", O_RDONLY | O_DIRECTORY));
ASSERT_SYS(0, 0, close(3));
}
ASSERT_SYS(0, 0, unveil(0, 0));
// error numbers are inconsistent
ASSERT_SYS(EACCES_OR_ENOENT, -1, open("/", O_RDONLY | O_DIRECTORY));
// wut
if (IsLinux()) {
ASSERT_SYS(0, 3, open("/", O_PATH)); // wut
ASSERT_SYS(0, 0, stat("/", &st)); // wut
}
EXITS(0);
}
TEST(unveil, rx_readOnlyPreexistingExecutable_worksFine) {
SPAWN();
ASSERT_SYS(0, 0, mkdir("folder", 0755));
ASSERT_SYS(0, 0, extract("/zip/life.elf", "folder/life.elf", 0755));
ASSERT_SYS(0, 0, unveil("folder", "rx"));
ASSERT_SYS(0, 0, unveil(0, 0));
SPAWN();
execv("folder/life.elf", (char *[]){"folder/life.elf", 0});
EXITS(42);
EXITS(0);
}
TEST(unveil, r_noExecutePreexistingExecutable_raisesEacces) {
SPAWN();
ASSERT_SYS(0, 0, mkdir("folder", 0755));
ASSERT_SYS(0, 0, extract("/zip/life.elf", "folder/life.elf", 0755));
ASSERT_SYS(0, 0, unveil("folder", "r"));
ASSERT_SYS(0, 0, unveil(0, 0));
SPAWN();
ASSERT_SYS(EACCES, -1,
execv("folder/life.elf", (char *[]){"folder/life.elf", 0}));
EXITS(0);
EXITS(0);
}
TEST(unveil, rwc_createExecutableFile_isAllowedButCantBeRun) {
SPAWN();
ASSERT_SYS(0, 0, mkdir("folder", 0755));
ASSERT_SYS(0, 0, unveil("folder", "rwc"));
ASSERT_SYS(0, 0, unveil(0, 0));
ASSERT_SYS(0, 0, extract("/zip/life.elf", "folder/life.elf", 0755));
SPAWN();
ASSERT_SYS(0, 0, stat("folder/life.elf", &st));
ASSERT_SYS(EACCES, -1,
execv("folder/life.elf", (char *[]){"folder/life.elf", 0}));
EXITS(0);
EXITS(0);
}
TEST(unveil, rwcx_createExecutableFile_canAlsoBeRun) {
SPAWN();
ASSERT_SYS(0, 0, mkdir("folder", 0755));
ASSERT_SYS(0, 0, unveil("folder", "rwcx"));
ASSERT_SYS(0, 0, unveil(0, 0));
ASSERT_SYS(0, 0, extract("/zip/life.elf", "folder/life.elf", 0755));
SPAWN();
ASSERT_SYS(0, 0, stat("folder/life.elf", &st));
execv("folder/life.elf", (char *[]){"folder/life.elf", 0});
EXITS(42);
EXITS(0);
}
TEST(unveil, dirfdHacking_doesntWork) {
SPAWN();
ASSERT_SYS(0, 0, mkdir("jail", 0755));
ASSERT_SYS(0, 0, mkdir("garden", 0755));
ASSERT_SYS(0, 0, touch("garden/secret.txt", 0644));
ASSERT_SYS(0, 3, open("garden", O_RDONLY | O_DIRECTORY));
ASSERT_SYS(0, 0, unveil("jail", "rw"));
ASSERT_SYS(0, 0, unveil(0, 0));
ASSERT_SYS(EACCES_OR_ENOENT, -1, openat(3, "secret.txt", O_RDONLY));
EXITS(0);
}
TEST(unveil, overlappingDirectories_inconsistentBehavior) {
SPAWN();
ASSERT_SYS(0, 0, makedirs("f1/f2", 0755));
ASSERT_SYS(0, 0, extract("/zip/life.elf", "f1/f2/life.elf", 0755));
ASSERT_SYS(0, 0, unveil("f1", "x"));
ASSERT_SYS(0, 0, unveil("f1/f2", "r"));
ASSERT_SYS(0, 0, unveil(0, 0));
if (IsOpenbsd()) {
// OpenBSD favors the most restrictive policy
SPAWN();
ASSERT_SYS(0, 0, stat("f1/f2/life.elf", &st));
ASSERT_SYS(EACCES, -1,
execv("f1/f2/life.elf", (char *[]){"f1/f2/life.elf", 0}));
EXITS(0);
} else {
// Landlock (Linux) uses the union of policies
SPAWN();
ASSERT_SYS(0, 0, stat("f1/f2/life.elf", &st));
execv("f1/f2/life.elf", (char *[]){"f1/f2/life.elf", 0});
EXITS(42);
}
EXITS(0);
}