mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-03-06 08:56:22 +00:00
Further improve unveil() implementation
This change addresses review comments from Günther Noack on GitHub. We're now blacklisting truncate() and setxattr() since Landlock lets them operate on veiled files. The restriction has been lifted on using unveil() multiple times, since Landlock does that well.
This commit is contained in:
parent
8593580d0a
commit
6c71bd5969
6 changed files with 154 additions and 43 deletions
|
@ -103,7 +103,7 @@ int geteuid(void) nosideeffect;
|
||||||
int getgid(void) nosideeffect;
|
int getgid(void) nosideeffect;
|
||||||
int gethostname(char *, size_t);
|
int gethostname(char *, size_t);
|
||||||
int getloadavg(double *, int);
|
int getloadavg(double *, int);
|
||||||
int getpgid(int) nosideeffect libcesque;
|
int getpgid(int) libcesque;
|
||||||
int getpgrp(void) nosideeffect;
|
int getpgrp(void) nosideeffect;
|
||||||
int getpid(void) nosideeffect libcesque;
|
int getpid(void) nosideeffect libcesque;
|
||||||
int getppid(void);
|
int getppid(void);
|
||||||
|
@ -112,7 +112,7 @@ int getresgid(uint32_t *, uint32_t *, uint32_t *);
|
||||||
int getresuid(uint32_t *, uint32_t *, uint32_t *);
|
int getresuid(uint32_t *, uint32_t *, uint32_t *);
|
||||||
int getsid(int) nosideeffect libcesque;
|
int getsid(int) nosideeffect libcesque;
|
||||||
int gettid(void) libcesque;
|
int gettid(void) libcesque;
|
||||||
int getuid(void) nosideeffect libcesque;
|
int getuid(void) libcesque;
|
||||||
int ioprio_get(int, int);
|
int ioprio_get(int, int);
|
||||||
int ioprio_set(int, int, int);
|
int ioprio_set(int, int, int);
|
||||||
int kill(int, int);
|
int kill(int, int);
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
#include "libc/calls/strace.internal.h"
|
#include "libc/calls/strace.internal.h"
|
||||||
#include "libc/calls/syscall-sysv.internal.h"
|
#include "libc/calls/syscall-sysv.internal.h"
|
||||||
#include "libc/dce.h"
|
#include "libc/dce.h"
|
||||||
|
#include "libc/runtime/runtime.h"
|
||||||
|
#include "libc/sysv/consts/auxv.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns effective group ID of calling process.
|
* Returns effective group ID of calling process.
|
||||||
|
@ -27,10 +29,12 @@
|
||||||
*/
|
*/
|
||||||
int getegid(void) {
|
int getegid(void) {
|
||||||
int rc;
|
int rc;
|
||||||
if (!IsWindows()) {
|
if (!(rc = getauxval(AT_EGID))) {
|
||||||
rc = sys_getegid();
|
if (!IsWindows()) {
|
||||||
} else {
|
rc = sys_getegid();
|
||||||
rc = getgid();
|
} else {
|
||||||
|
rc = getgid();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
STRACE("%s() → %d% m", "getegid", rc);
|
STRACE("%s() → %d% m", "getegid", rc);
|
||||||
return rc;
|
return rc;
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
#include "libc/calls/calls.h"
|
#include "libc/calls/calls.h"
|
||||||
#include "libc/calls/strace.internal.h"
|
#include "libc/calls/strace.internal.h"
|
||||||
#include "libc/calls/syscall-sysv.internal.h"
|
#include "libc/calls/syscall-sysv.internal.h"
|
||||||
|
#include "libc/runtime/runtime.h"
|
||||||
|
#include "libc/sysv/consts/auxv.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns effective user ID of calling process.
|
* Returns effective user ID of calling process.
|
||||||
|
@ -26,10 +28,12 @@
|
||||||
*/
|
*/
|
||||||
int geteuid(void) {
|
int geteuid(void) {
|
||||||
int rc;
|
int rc;
|
||||||
if (!IsWindows()) {
|
if (!(rc = getauxval(AT_EUID))) {
|
||||||
rc = sys_geteuid();
|
if (!IsWindows()) {
|
||||||
} else {
|
rc = sys_geteuid();
|
||||||
rc = getuid();
|
} else {
|
||||||
|
rc = getuid();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
STRACE("%s() → %d% m", "geteuid", rc);
|
STRACE("%s() → %d% m", "geteuid", rc);
|
||||||
return rc;
|
return rc;
|
||||||
|
|
|
@ -53,10 +53,12 @@ static textwindows dontinline uint32_t GetUserNameHash(void) {
|
||||||
*/
|
*/
|
||||||
int getuid(void) {
|
int getuid(void) {
|
||||||
int rc;
|
int rc;
|
||||||
if (!IsWindows()) {
|
if (!(rc = getauxval(AT_UID))) {
|
||||||
rc = sys_getuid();
|
if (!IsWindows()) {
|
||||||
} else {
|
rc = sys_getuid();
|
||||||
rc = GetUserNameHash();
|
} else {
|
||||||
|
rc = GetUserNameHash();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
STRACE("%s() → %d% m", "getuid", rc);
|
STRACE("%s() → %d% m", "getuid", rc);
|
||||||
return rc;
|
return rc;
|
||||||
|
@ -73,10 +75,12 @@ int getuid(void) {
|
||||||
*/
|
*/
|
||||||
int getgid(void) {
|
int getgid(void) {
|
||||||
int rc;
|
int rc;
|
||||||
if (!IsWindows()) {
|
if (!(rc = getauxval(AT_GID))) {
|
||||||
rc = sys_getgid();
|
if (!IsWindows()) {
|
||||||
} else {
|
rc = sys_getgid();
|
||||||
rc = GetUserNameHash();
|
} else {
|
||||||
|
rc = GetUserNameHash();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
STRACE("%s() → %d% m", "getgid", rc);
|
STRACE("%s() → %d% m", "getgid", rc);
|
||||||
return rc;
|
return rc;
|
||||||
|
|
|
@ -64,18 +64,15 @@
|
||||||
(LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE | \
|
(LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE | \
|
||||||
LANDLOCK_ACCESS_FS_EXECUTE)
|
LANDLOCK_ACCESS_FS_EXECUTE)
|
||||||
|
|
||||||
static const struct sock_filter kBlacklistLandlock[] = {
|
static const struct sock_filter kUnveilBlacklist[] = {
|
||||||
// clang-format off
|
|
||||||
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(arch)),
|
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(arch)),
|
||||||
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0),
|
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0),
|
||||||
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
|
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
|
||||||
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_landlock_create_ruleset, 2, 0),
|
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_truncate, 1, 0),
|
||||||
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_landlock_add_rule, 1, 0),
|
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_setxattr, 0, 1),
|
||||||
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_landlock_restrict_self, 0, 1),
|
|
||||||
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (1 & SECCOMP_RET_DATA)),
|
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (1 & SECCOMP_RET_DATA)),
|
||||||
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
|
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
|
||||||
// clang-format on
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -98,8 +95,8 @@ _Thread_local static struct {
|
||||||
static int unveil_final(void) {
|
static int unveil_final(void) {
|
||||||
int rc;
|
int rc;
|
||||||
struct sock_fprog sandbox = {
|
struct sock_fprog sandbox = {
|
||||||
.filter = kBlacklistLandlock,
|
.filter = kUnveilBlacklist,
|
||||||
.len = ARRAYLEN(kBlacklistLandlock),
|
.len = ARRAYLEN(kUnveilBlacklist),
|
||||||
};
|
};
|
||||||
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 &&
|
||||||
|
@ -264,14 +261,13 @@ static int sys_unveil_linux(const char *path, const char *permissions) {
|
||||||
* unveil("/etc", "r"); // make /etc readable too
|
* unveil("/etc", "r"); // make /etc readable too
|
||||||
* unveil(0, 0); // commit and lock policy
|
* unveil(0, 0); // commit and lock policy
|
||||||
*
|
*
|
||||||
* Unveiling restricts a thread's view of the filesystem to a set of
|
* Unveiling restricts a view of the filesystem to a set of allowed
|
||||||
* allowed paths with specific privileges.
|
* paths with specific privileges.
|
||||||
*
|
*
|
||||||
* Once you start using unveil(), the entire file system is considered
|
* Once you start using unveil(), the entire file system is considered
|
||||||
* hidden. You then specify, by repeatedly calling unveil(), which paths
|
* hidden. You then specify, by repeatedly calling unveil(), which paths
|
||||||
* should become unhidden. When you're finished, you call `unveil(0,0)`
|
* should become unhidden. When you're finished, you call `unveil(0,0)`
|
||||||
* which commits your policy, after which further use is forbidden, in
|
* which commits your policy.
|
||||||
* the current thread, as well as any threads or processes it spawns.
|
|
||||||
*
|
*
|
||||||
* There are some differences between unveil() on Linux versus OpenBSD.
|
* There are some differences between unveil() on Linux versus OpenBSD.
|
||||||
*
|
*
|
||||||
|
@ -299,8 +295,28 @@ static 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. Always specify at least one path. OpenBSD has unclear semantics
|
* 5. Use ftruncate() rather than truncate(). One of the backdoors with
|
||||||
* when `pledge(0,0)` is used without any previous calls.
|
* Landlock is it currently can't restrict truncate() and setxattr()
|
||||||
|
* which permits certain kinds of modifications to files outside the
|
||||||
|
* sandbox. When your policy is committed, we install a SECCOMP BPF
|
||||||
|
* filter to disable those calls, however similar trickery may be
|
||||||
|
* possible through other unaddressed calls like ioctl(). Using the
|
||||||
|
* pledge() function in addition to unveil() will solve this, since
|
||||||
|
* it installs a strong system call access policy.
|
||||||
|
*
|
||||||
|
* 6. Set your process-wide policy at startup from the main thread. On
|
||||||
|
* OpenBSD unveil() will apply process-wide even when called from a
|
||||||
|
* child thread; whereas with Linux, calling unveil() from a thread
|
||||||
|
* will cause your ruleset to only apply to that thread in addition
|
||||||
|
* to any descendent threads it creates.
|
||||||
|
*
|
||||||
|
* 7. Always specify at least one path. OpenBSD has unclear semantics
|
||||||
|
* when `unveil(0,0)` is used without any previous calls.
|
||||||
|
*
|
||||||
|
* 8. On OpenBSD calling `unveil(0,0)` will prevent unveil() from being
|
||||||
|
* used again. On Linux this is allowed, because Landlock is able to
|
||||||
|
* do that securely, i.e. the second ruleset can only be a subset of
|
||||||
|
* the previous ones.
|
||||||
*
|
*
|
||||||
* This system call is supported natively on OpenBSD and polyfilled on
|
* This system call is supported natively on OpenBSD and polyfilled on
|
||||||
* Linux using the Landlock LSM[1].
|
* Linux using the Landlock LSM[1].
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
#include "libc/sysv/consts/sig.h"
|
#include "libc/sysv/consts/sig.h"
|
||||||
#include "libc/sysv/consts/sock.h"
|
#include "libc/sysv/consts/sock.h"
|
||||||
#include "libc/testlib/testlib.h"
|
#include "libc/testlib/testlib.h"
|
||||||
|
#include "libc/thread/spawn.h"
|
||||||
#include "libc/x/x.h"
|
#include "libc/x/x.h"
|
||||||
|
|
||||||
STATIC_YOINK("zip_uri_support");
|
STATIC_YOINK("zip_uri_support");
|
||||||
|
@ -241,19 +242,107 @@ TEST(unveil, overlappingDirectories_inconsistentBehavior) {
|
||||||
EXITS(0);
|
EXITS(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(unveil, usedTwice_forbidden) {
|
TEST(unveil, usedTwice_allowedOnLinux) {
|
||||||
|
if (IsOpenbsd()) return;
|
||||||
|
SPAWN(fork);
|
||||||
|
ASSERT_SYS(0, 0, mkdir("jail", 0755));
|
||||||
|
ASSERT_SYS(0, 0, xbarf("jail/ok.txt", "hello", 5));
|
||||||
|
ASSERT_SYS(0, 0, mkdir("garden", 0755));
|
||||||
|
ASSERT_SYS(0, 0, xbarf("garden/secret.txt", "hello", 5));
|
||||||
|
ASSERT_SYS(0, 0, mkdir("heaven", 0755));
|
||||||
|
ASSERT_SYS(0, 0, xbarf("heaven/verysecret.txt", "hello", 5));
|
||||||
|
ASSERT_SYS(0, 0, unveil("jail", "rw"));
|
||||||
|
ASSERT_SYS(0, 0, unveil("garden", "rw"));
|
||||||
|
ASSERT_SYS(0, 0, unveil(0, 0));
|
||||||
|
ASSERT_SYS(0, 3, open("garden/secret.txt", O_RDONLY));
|
||||||
|
ASSERT_SYS(0, 0, unveil("jail", "rw"));
|
||||||
|
ASSERT_SYS(0, 0, unveil("heaven", "rw")); // not allowed, superset of parent
|
||||||
|
ASSERT_SYS(0, 0, unveil(0, 0));
|
||||||
|
ASSERT_SYS(0, 4, open("jail/ok.txt", O_RDONLY));
|
||||||
|
ASSERT_SYS(EACCES, -1, open("garden/secret.txt", O_RDONLY));
|
||||||
|
ASSERT_SYS(EACCES, -1, open("heaven/verysecret.txt", O_RDONLY));
|
||||||
|
EXITS(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unveil, truncate_isForbiddenBySeccomp) {
|
||||||
SPAWN(fork);
|
SPAWN(fork);
|
||||||
ASSERT_SYS(0, 0, mkdir("jail", 0755));
|
ASSERT_SYS(0, 0, mkdir("jail", 0755));
|
||||||
ASSERT_SYS(0, 0, mkdir("garden", 0755));
|
ASSERT_SYS(0, 0, mkdir("garden", 0755));
|
||||||
ASSERT_SYS(0, 0, xbarf("garden/secret.txt", "hello", 5));
|
ASSERT_SYS(0, 0, xbarf("garden/secret.txt", "hello", 5));
|
||||||
ASSERT_SYS(0, 0, unveil("jail", "rw"));
|
ASSERT_SYS(0, 0, unveil("jail", "rw"));
|
||||||
ASSERT_SYS(0, 0, unveil(0, 0));
|
ASSERT_SYS(0, 0, unveil(0, 0));
|
||||||
ASSERT_SYS(EACCES_OR_ENOENT, -1, open("garden/secret.txt", O_RDONLY));
|
ASSERT_SYS(IsOpenbsd() ? ENOENT : EPERM, -1,
|
||||||
ASSERT_SYS(EPERM, -1, unveil("jail", "rw"));
|
truncate("garden/secret.txt", 0));
|
||||||
ASSERT_SYS(EACCES_OR_ENOENT, -1, open("garden/secret.txt", O_RDONLY));
|
|
||||||
if (IsLinux()) {
|
if (IsLinux()) {
|
||||||
ASSERT_SYS(0, 0, stat("garden/secret.txt", &st));
|
ASSERT_SYS(0, 0, stat("garden/secret.txt", &st));
|
||||||
ASSERT_EQ(5, st.st_size); // wut linux metadata is accessible
|
ASSERT_EQ(5, st.st_size);
|
||||||
|
}
|
||||||
|
EXITS(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unveil, ftruncate_isForbidden) {
|
||||||
|
if (IsOpenbsd()) return; // b/c O_PATH is a Linux thing
|
||||||
|
SPAWN(fork);
|
||||||
|
ASSERT_SYS(0, 0, mkdir("jail", 0755));
|
||||||
|
ASSERT_SYS(0, 0, mkdir("garden", 0755));
|
||||||
|
ASSERT_SYS(0, 0, xbarf("garden/secret.txt", "hello", 5));
|
||||||
|
ASSERT_SYS(0, 0, unveil("jail", "rw"));
|
||||||
|
ASSERT_SYS(0, 0, unveil(0, 0));
|
||||||
|
ASSERT_SYS(0, 3, open("garden/secret.txt", O_PATH));
|
||||||
|
ASSERT_SYS(EBADF, -1, ftruncate(3, 0));
|
||||||
|
ASSERT_SYS(0, 0, close(3));
|
||||||
|
ASSERT_SYS(0, 0, stat("garden/secret.txt", &st));
|
||||||
|
ASSERT_EQ(5, st.st_size);
|
||||||
|
EXITS(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unveil, procfs_isForbiddenByDefault) {
|
||||||
|
if (IsOpenbsd()) return;
|
||||||
|
SPAWN(fork);
|
||||||
|
ASSERT_SYS(0, 0, mkdir("jail", 0755));
|
||||||
|
ASSERT_SYS(0, 0, unveil("jail", "rw"));
|
||||||
|
ASSERT_SYS(0, 0, unveil(0, 0));
|
||||||
|
ASSERT_SYS(EACCES, -1, open("/proc/self/cmdline", O_RDONLY));
|
||||||
|
EXITS(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Worker(void *arg, int tid) {
|
||||||
|
ASSERT_SYS(EACCES_OR_ENOENT, -1, open("garden/secret.txt", O_RDONLY));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unveil, isInheritedAcrossThreads) {
|
||||||
|
struct spawn t;
|
||||||
|
SPAWN(fork);
|
||||||
|
ASSERT_SYS(0, 0, mkdir("jail", 0755));
|
||||||
|
ASSERT_SYS(0, 0, mkdir("garden", 0755));
|
||||||
|
ASSERT_SYS(0, 0, xbarf("garden/secret.txt", "hello", 5));
|
||||||
|
ASSERT_SYS(0, 0, unveil("jail", "rw"));
|
||||||
|
ASSERT_SYS(0, 0, unveil(0, 0));
|
||||||
|
ASSERT_SYS(0, 0, _spawn(Worker, 0, &t));
|
||||||
|
EXPECT_SYS(0, 0, _join(&t));
|
||||||
|
EXITS(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Worker2(void *arg, int tid) {
|
||||||
|
ASSERT_SYS(0, 0, unveil("jail", "rw"));
|
||||||
|
ASSERT_SYS(0, 0, unveil(0, 0));
|
||||||
|
ASSERT_SYS(EACCES_OR_ENOENT, -1, open("garden/secret.txt", O_RDONLY));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(unveil, isThreadSpecificOnLinux_isProcessWideOnOpenbsd) {
|
||||||
|
struct spawn t;
|
||||||
|
SPAWN(fork);
|
||||||
|
ASSERT_SYS(0, 0, mkdir("jail", 0755));
|
||||||
|
ASSERT_SYS(0, 0, mkdir("garden", 0755));
|
||||||
|
ASSERT_SYS(0, 0, xbarf("garden/secret.txt", "hello", 5));
|
||||||
|
ASSERT_SYS(0, 0, _spawn(Worker2, 0, &t));
|
||||||
|
EXPECT_SYS(0, 0, _join(&t));
|
||||||
|
if (IsOpenbsd()) {
|
||||||
|
ASSERT_SYS(ENOENT, -1, open("garden/secret.txt", O_RDONLY));
|
||||||
|
} else {
|
||||||
|
ASSERT_SYS(0, 3, open("garden/secret.txt", O_RDONLY));
|
||||||
}
|
}
|
||||||
EXITS(0);
|
EXITS(0);
|
||||||
}
|
}
|
||||||
|
@ -275,12 +364,6 @@ TEST(unveil, usedTwice_forbidden_worksWithPledge) {
|
||||||
ASSERT_SYS(0, 0, unveil(0, 0));
|
ASSERT_SYS(0, 0, unveil(0, 0));
|
||||||
ASSERT_SYS(EACCES_OR_ENOENT, -1, open("garden/secret.txt", O_RDONLY));
|
ASSERT_SYS(EACCES_OR_ENOENT, -1, open("garden/secret.txt", O_RDONLY));
|
||||||
// verify the second filter is working
|
// verify the second filter is working
|
||||||
ASSERT_SYS(EPERM, -1, unveil("jail", "rw"));
|
|
||||||
if (IsLinux()) {
|
|
||||||
ASSERT_SYS(
|
|
||||||
EPERM, -1,
|
|
||||||
landlock_create_ruleset(0, 0, LANDLOCK_CREATE_RULESET_VERSION));
|
|
||||||
}
|
|
||||||
ASSERT_SYS(EACCES_OR_ENOENT, -1, open("garden/secret.txt", O_RDONLY));
|
ASSERT_SYS(EACCES_OR_ENOENT, -1, open("garden/secret.txt", O_RDONLY));
|
||||||
// verify the first filter is still working
|
// verify the first filter is still working
|
||||||
*gotsome = true;
|
*gotsome = true;
|
||||||
|
|
Loading…
Add table
Reference in a new issue