2022-07-18 09:12:42 +00:00
|
|
|
/*-*- 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 2020 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/assert.h"
|
2022-11-06 02:49:41 +00:00
|
|
|
#include "libc/calls/blockcancel.internal.h"
|
2022-07-18 09:12:42 +00:00
|
|
|
#include "libc/calls/calls.h"
|
|
|
|
#include "libc/calls/landlock.h"
|
2023-06-10 01:02:06 +00:00
|
|
|
#include "libc/calls/struct/bpf.internal.h"
|
|
|
|
#include "libc/calls/struct/filter.internal.h"
|
|
|
|
#include "libc/calls/struct/seccomp.internal.h"
|
2022-07-18 09:12:42 +00:00
|
|
|
#include "libc/calls/struct/stat.h"
|
2022-08-13 20:11:56 +00:00
|
|
|
#include "libc/calls/struct/stat.internal.h"
|
2022-07-18 09:12:42 +00:00
|
|
|
#include "libc/calls/syscall-sysv.internal.h"
|
|
|
|
#include "libc/calls/syscall_support-sysv.internal.h"
|
2023-06-03 15:12:13 +00:00
|
|
|
#include "libc/dce.h"
|
2022-07-18 09:12:42 +00:00
|
|
|
#include "libc/errno.h"
|
2022-07-20 04:18:33 +00:00
|
|
|
#include "libc/fmt/conv.h"
|
2023-06-18 12:39:31 +00:00
|
|
|
#include "libc/fmt/libgen.h"
|
2022-11-06 02:49:41 +00:00
|
|
|
#include "libc/intrin/strace.internal.h"
|
2022-07-19 09:11:04 +00:00
|
|
|
#include "libc/macros.internal.h"
|
2022-11-11 05:52:47 +00:00
|
|
|
#include "libc/nexgen32e/vendor.internal.h"
|
2022-07-18 14:23:15 +00:00
|
|
|
#include "libc/runtime/internal.h"
|
2022-07-21 16:16:38 +00:00
|
|
|
#include "libc/runtime/runtime.h"
|
2022-07-25 02:40:32 +00:00
|
|
|
#include "libc/runtime/stack.h"
|
2022-07-20 04:18:33 +00:00
|
|
|
#include "libc/str/path.h"
|
2022-07-18 09:12:42 +00:00
|
|
|
#include "libc/str/str.h"
|
2022-07-18 14:23:15 +00:00
|
|
|
#include "libc/sysv/consts/at.h"
|
2022-07-19 09:11:04 +00:00
|
|
|
#include "libc/sysv/consts/audit.h"
|
2022-07-18 09:11:06 +00:00
|
|
|
#include "libc/sysv/consts/f.h"
|
2022-07-19 04:05:46 +00:00
|
|
|
#include "libc/sysv/consts/fd.h"
|
2022-07-19 09:11:04 +00:00
|
|
|
#include "libc/sysv/consts/nrlinux.h"
|
2022-07-18 09:12:42 +00:00
|
|
|
#include "libc/sysv/consts/o.h"
|
|
|
|
#include "libc/sysv/consts/pr.h"
|
|
|
|
#include "libc/sysv/consts/s.h"
|
|
|
|
#include "libc/sysv/errfuns.h"
|
2022-11-06 02:49:41 +00:00
|
|
|
#include "libc/thread/tls.h"
|
2022-07-18 09:12:42 +00:00
|
|
|
|
2023-05-09 08:56:56 +00:00
|
|
|
#ifdef __x86_64__
|
2023-05-13 05:42:57 +00:00
|
|
|
#define ARCHITECTURE AUDIT_ARCH_X86_64
|
|
|
|
#elif defined(__aarch64__)
|
|
|
|
#define ARCHITECTURE AUDIT_ARCH_AARCH64
|
|
|
|
#else
|
|
|
|
#error "unsupported architecture"
|
|
|
|
#endif
|
2023-05-09 08:56:56 +00:00
|
|
|
|
2022-07-19 09:11:04 +00:00
|
|
|
#define OFF(f) offsetof(struct seccomp_data, f)
|
|
|
|
|
2022-07-18 15:26:40 +00:00
|
|
|
#define UNVEIL_READ \
|
|
|
|
(LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR | \
|
|
|
|
LANDLOCK_ACCESS_FS_REFER)
|
2023-04-27 03:45:01 +00:00
|
|
|
#define UNVEIL_WRITE \
|
|
|
|
(LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_TRUNCATE)
|
|
|
|
#define UNVEIL_EXEC (LANDLOCK_ACCESS_FS_EXECUTE)
|
2022-07-18 15:26:40 +00:00
|
|
|
#define UNVEIL_CREATE \
|
|
|
|
(LANDLOCK_ACCESS_FS_MAKE_CHAR | LANDLOCK_ACCESS_FS_MAKE_DIR | \
|
|
|
|
LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_MAKE_SOCK | \
|
|
|
|
LANDLOCK_ACCESS_FS_MAKE_FIFO | LANDLOCK_ACCESS_FS_MAKE_BLOCK | \
|
|
|
|
LANDLOCK_ACCESS_FS_MAKE_SYM)
|
|
|
|
|
2022-07-18 09:12:42 +00:00
|
|
|
#define FILE_BITS \
|
|
|
|
(LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE | \
|
|
|
|
LANDLOCK_ACCESS_FS_EXECUTE)
|
|
|
|
|
2023-04-17 23:17:02 +00:00
|
|
|
static const struct sock_filter kUnveilBlacklistAbiVersionBelow3[] = {
|
2022-07-19 09:11:04 +00:00
|
|
|
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(arch)),
|
2023-05-13 05:42:57 +00:00
|
|
|
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ARCHITECTURE, 1, 0),
|
2022-07-19 09:11:04 +00:00
|
|
|
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
|
|
|
|
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)),
|
2022-07-24 12:54:26 +00:00
|
|
|
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_truncate, 1, 0),
|
|
|
|
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_setxattr, 0, 1),
|
2022-07-19 09:11:04 +00:00
|
|
|
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (1 & SECCOMP_RET_DATA)),
|
|
|
|
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
|
|
|
|
};
|
|
|
|
|
2023-04-17 23:17:02 +00:00
|
|
|
static const struct sock_filter kUnveilBlacklistLatestAbi[] = {
|
|
|
|
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(arch)),
|
2023-05-13 05:42:57 +00:00
|
|
|
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ARCHITECTURE, 1, 0),
|
2023-04-17 23:17:02 +00:00
|
|
|
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
|
|
|
|
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)),
|
|
|
|
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_setxattr, 0, 1),
|
|
|
|
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (1 & SECCOMP_RET_DATA)),
|
|
|
|
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
|
|
|
|
};
|
|
|
|
|
|
|
|
static int landlock_abi_version;
|
2023-05-13 09:41:41 +00:00
|
|
|
static int landlock_abi_errno;
|
2023-04-17 23:17:02 +00:00
|
|
|
|
|
|
|
__attribute__((__constructor__)) void init_landlock_version() {
|
2023-06-03 15:12:13 +00:00
|
|
|
int e = errno;
|
2023-04-27 03:45:01 +00:00
|
|
|
landlock_abi_version =
|
|
|
|
landlock_create_ruleset(0, 0, LANDLOCK_CREATE_RULESET_VERSION);
|
2023-05-13 09:41:41 +00:00
|
|
|
landlock_abi_errno = errno;
|
2023-06-03 15:12:13 +00:00
|
|
|
errno = e;
|
2023-04-17 23:17:02 +00:00
|
|
|
}
|
|
|
|
|
2022-07-18 15:26:40 +00:00
|
|
|
/**
|
2022-07-18 09:12:42 +00:00
|
|
|
* Long living state for landlock calls.
|
2022-07-18 15:26:40 +00:00
|
|
|
* fs_mask is set to use all the access rights from the latest landlock ABI.
|
|
|
|
* On init, the current supported abi is checked and unavailable rights are
|
|
|
|
* masked off.
|
|
|
|
*
|
2023-04-17 23:17:02 +00:00
|
|
|
* As of 6.2, the latest abi is v3.
|
2022-07-18 09:12:42 +00:00
|
|
|
*
|
|
|
|
* TODO:
|
|
|
|
* - Integrate with pledge and remove the file access?
|
|
|
|
* - Stuff state into the .protected section?
|
|
|
|
*/
|
2022-07-18 09:11:06 +00:00
|
|
|
_Thread_local static struct {
|
2022-07-18 15:26:40 +00:00
|
|
|
uint64_t fs_mask;
|
2022-07-18 09:12:42 +00:00
|
|
|
int fd;
|
2022-07-20 20:58:52 +00:00
|
|
|
} State;
|
2022-07-18 09:12:42 +00:00
|
|
|
|
2022-07-18 09:11:06 +00:00
|
|
|
static int unveil_final(void) {
|
2022-08-11 07:15:29 +00:00
|
|
|
int e, rc;
|
2022-07-19 09:11:04 +00:00
|
|
|
struct sock_fprog sandbox = {
|
2023-04-17 23:17:02 +00:00
|
|
|
.filter = kUnveilBlacklistLatestAbi,
|
|
|
|
.len = ARRAYLEN(kUnveilBlacklistLatestAbi),
|
2022-07-19 09:11:04 +00:00
|
|
|
};
|
2023-04-17 23:17:02 +00:00
|
|
|
if (landlock_abi_version < 3) {
|
|
|
|
sandbox = (struct sock_fprog){
|
2023-04-27 03:45:01 +00:00
|
|
|
.filter = kUnveilBlacklistAbiVersionBelow3,
|
|
|
|
.len = ARRAYLEN(kUnveilBlacklistAbiVersionBelow3),
|
2023-04-17 23:17:02 +00:00
|
|
|
};
|
|
|
|
}
|
2022-08-11 07:15:29 +00:00
|
|
|
e = errno;
|
|
|
|
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
|
|
|
|
errno = e;
|
|
|
|
if ((rc = landlock_restrict_self(State.fd, 0)) != -1 &&
|
2022-07-19 09:11:04 +00:00
|
|
|
(rc = sys_close(State.fd)) != -1 &&
|
|
|
|
(rc = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &sandbox)) != -1) {
|
|
|
|
State.fd = 0;
|
|
|
|
}
|
2022-07-18 09:12:42 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2022-07-18 09:11:06 +00:00
|
|
|
static int err_close(int rc, int fd) {
|
|
|
|
int serrno = errno;
|
|
|
|
sys_close(fd);
|
|
|
|
errno = serrno;
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int unveil_init(void) {
|
|
|
|
int rc, fd;
|
2022-07-20 20:58:52 +00:00
|
|
|
State.fs_mask = UNVEIL_READ | UNVEIL_WRITE | UNVEIL_EXEC | UNVEIL_CREATE;
|
2023-04-17 23:17:02 +00:00
|
|
|
if (landlock_abi_version == -1) {
|
2023-05-13 09:41:41 +00:00
|
|
|
errno = landlock_abi_errno;
|
2022-08-11 07:15:29 +00:00
|
|
|
if (errno == EOPNOTSUPP) {
|
|
|
|
errno = ENOSYS;
|
|
|
|
}
|
2022-07-18 09:12:42 +00:00
|
|
|
return -1;
|
2022-07-18 15:26:40 +00:00
|
|
|
}
|
2023-04-17 23:17:02 +00:00
|
|
|
if (landlock_abi_version < 2) {
|
2022-07-21 16:16:38 +00:00
|
|
|
State.fs_mask &= ~LANDLOCK_ACCESS_FS_REFER;
|
|
|
|
}
|
2023-04-17 23:17:02 +00:00
|
|
|
if (landlock_abi_version < 3) {
|
|
|
|
State.fs_mask &= ~LANDLOCK_ACCESS_FS_TRUNCATE;
|
|
|
|
}
|
2022-07-18 09:12:42 +00:00
|
|
|
const struct landlock_ruleset_attr attr = {
|
2022-07-18 15:26:40 +00:00
|
|
|
.handled_access_fs = State.fs_mask,
|
2022-07-18 09:12:42 +00:00
|
|
|
};
|
2023-06-03 15:12:13 +00:00
|
|
|
// [undocumented] landlock_create_ruleset() always returns O_CLOEXEC
|
2022-07-19 04:05:46 +00:00
|
|
|
// assert(__sys_fcntl(rc, F_GETFD, 0) == FD_CLOEXEC);
|
2022-07-18 09:12:42 +00:00
|
|
|
if ((rc = landlock_create_ruleset(&attr, sizeof(attr), 0)) < 0) return -1;
|
2022-07-18 09:11:06 +00:00
|
|
|
// grant file descriptor a higher number that's less likely to interfere
|
2022-07-19 04:05:46 +00:00
|
|
|
if ((fd = __sys_fcntl(rc, F_DUPFD_CLOEXEC, 100)) == -1) {
|
|
|
|
return err_close(-1, rc);
|
|
|
|
}
|
|
|
|
if (sys_close(rc) == -1) {
|
|
|
|
return err_close(-1, fd);
|
|
|
|
}
|
2022-07-18 09:11:06 +00:00
|
|
|
State.fd = fd;
|
2022-07-18 09:12:42 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-07-06 16:07:42 +00:00
|
|
|
/**
|
|
|
|
* Joins paths, e.g.
|
|
|
|
*
|
|
|
|
* 0 + 0 → 0
|
|
|
|
* "" + "" → ""
|
|
|
|
* "a" + 0 → "a"
|
|
|
|
* "a" + "" → "a/"
|
|
|
|
* 0 + "b" → "b"
|
|
|
|
* "" + "b" → "b"
|
|
|
|
* "." + "b" → "./b"
|
|
|
|
* "b" + "." → "b/."
|
|
|
|
* "a" + "b" → "a/b"
|
|
|
|
* "a/" + "b" → "a/b"
|
|
|
|
* "a" + "b/" → "a/b/"
|
|
|
|
* "a" + "/b" → "/b"
|
|
|
|
*
|
|
|
|
* @return joined path, which may be `buf`, `path`, or `other`, or null
|
|
|
|
* if (1) `buf` didn't have enough space, or (2) both `path` and
|
|
|
|
* `other` were null
|
|
|
|
*/
|
|
|
|
static char *JoinPaths(char *buf, size_t size, const char *path,
|
|
|
|
const char *other) {
|
|
|
|
size_t pathlen, otherlen;
|
2023-09-02 03:49:13 +00:00
|
|
|
if (!other) return (char *)path;
|
|
|
|
if (!path) return (char *)other;
|
2023-07-06 16:07:42 +00:00
|
|
|
pathlen = strlen(path);
|
|
|
|
if (!pathlen || *other == '/') {
|
|
|
|
return (/*unconst*/ char *)other;
|
|
|
|
}
|
|
|
|
otherlen = strlen(other);
|
|
|
|
if (path[pathlen - 1] == '/') {
|
|
|
|
if (pathlen + otherlen + 1 <= size) {
|
|
|
|
memmove(buf, path, pathlen);
|
|
|
|
memmove(buf + pathlen, other, otherlen + 1);
|
|
|
|
return buf;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (pathlen + 1 + otherlen + 1 <= size) {
|
|
|
|
memmove(buf, path, pathlen);
|
|
|
|
buf[pathlen] = '/';
|
|
|
|
memmove(buf + pathlen + 1, other, otherlen + 1);
|
|
|
|
return buf;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-25 02:40:32 +00:00
|
|
|
int sys_unveil_linux(const char *path, const char *permissions) {
|
2022-07-18 09:12:42 +00:00
|
|
|
int rc;
|
2022-07-25 02:40:32 +00:00
|
|
|
const char *dir;
|
|
|
|
const char *last;
|
|
|
|
const char *next;
|
|
|
|
struct {
|
|
|
|
char lbuf[PATH_MAX];
|
|
|
|
char buf1[PATH_MAX];
|
|
|
|
char buf2[PATH_MAX];
|
|
|
|
char buf3[PATH_MAX];
|
|
|
|
char buf4[PATH_MAX];
|
|
|
|
} b;
|
|
|
|
CheckLargeStackAllocation(&b, sizeof(b));
|
2022-07-20 04:18:33 +00:00
|
|
|
|
2022-07-18 09:11:06 +00:00
|
|
|
if (!State.fd && (rc = unveil_init()) == -1) return rc;
|
2022-07-18 15:26:40 +00:00
|
|
|
if ((path && !permissions) || (!path && permissions)) return einval();
|
2022-07-18 09:11:06 +00:00
|
|
|
if (!path && !permissions) return unveil_final();
|
2022-07-18 09:12:42 +00:00
|
|
|
struct landlock_path_beneath_attr pb = {0};
|
|
|
|
for (const char *c = permissions; *c != '\0'; c++) {
|
|
|
|
switch (*c) {
|
2022-07-20 04:18:33 +00:00
|
|
|
case 'r':
|
|
|
|
pb.allowed_access |= UNVEIL_READ;
|
|
|
|
break;
|
|
|
|
case 'w':
|
|
|
|
pb.allowed_access |= UNVEIL_WRITE;
|
|
|
|
break;
|
|
|
|
case 'x':
|
|
|
|
pb.allowed_access |= UNVEIL_EXEC;
|
|
|
|
break;
|
|
|
|
case 'c':
|
|
|
|
pb.allowed_access |= UNVEIL_CREATE;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return einval();
|
2022-07-18 09:12:42 +00:00
|
|
|
}
|
|
|
|
}
|
2022-07-18 15:26:40 +00:00
|
|
|
pb.allowed_access &= State.fs_mask;
|
2022-07-20 04:18:33 +00:00
|
|
|
|
|
|
|
// landlock exposes all metadata, so we only technically need to add
|
|
|
|
// realpath(path) to the ruleset. however a corner case exists where
|
|
|
|
// it isn't valid, e.g. /dev/stdin -> /proc/2834/fd/pipe:[51032], so
|
|
|
|
// we'll need to work around this, by adding the path which is valid
|
|
|
|
if (strlen(path) + 1 > PATH_MAX) return enametoolong();
|
|
|
|
last = path;
|
|
|
|
next = path;
|
|
|
|
for (int i = 0;; ++i) {
|
|
|
|
if (i == 64) {
|
|
|
|
// give up
|
|
|
|
return eloop();
|
|
|
|
}
|
|
|
|
int err = errno;
|
2022-07-25 02:40:32 +00:00
|
|
|
if ((rc = sys_readlinkat(AT_FDCWD, next, b.lbuf, PATH_MAX)) != -1) {
|
2022-07-20 04:18:33 +00:00
|
|
|
if (rc < PATH_MAX) {
|
|
|
|
// we need to nul-terminate
|
2022-07-25 02:40:32 +00:00
|
|
|
b.lbuf[rc] = 0;
|
2022-07-20 04:18:33 +00:00
|
|
|
// last = next
|
2022-07-25 02:40:32 +00:00
|
|
|
strcpy(b.buf1, next);
|
|
|
|
last = b.buf1;
|
2022-07-20 04:18:33 +00:00
|
|
|
// next = join(dirname(next), link)
|
2022-07-25 02:40:32 +00:00
|
|
|
strcpy(b.buf2, next);
|
|
|
|
dir = dirname(b.buf2);
|
2023-07-06 16:07:42 +00:00
|
|
|
if ((next = JoinPaths(b.buf3, PATH_MAX, dir, b.lbuf))) {
|
2022-07-20 04:18:33 +00:00
|
|
|
// next now points to either: buf3, buf2, lbuf, rodata
|
2022-07-25 02:40:32 +00:00
|
|
|
strcpy(b.buf4, next);
|
|
|
|
next = b.buf4;
|
2022-07-20 04:18:33 +00:00
|
|
|
} else {
|
|
|
|
return enametoolong();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// symbolic link data was too long
|
|
|
|
return enametoolong();
|
|
|
|
}
|
|
|
|
} else if (errno == EINVAL) {
|
|
|
|
// next wasn't a symbolic link
|
|
|
|
errno = err;
|
|
|
|
path = next;
|
|
|
|
break;
|
|
|
|
} else if (i && (errno == ENOENT || errno == ENOTDIR)) {
|
|
|
|
// next is a broken symlink, use last
|
|
|
|
errno = err;
|
|
|
|
path = last;
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
// readlink failed for some other reason
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// now we can open the path
|
2022-11-06 02:49:41 +00:00
|
|
|
BLOCK_CANCELLATIONS;
|
2023-06-03 15:12:13 +00:00
|
|
|
rc = sys_openat(AT_FDCWD, path, O_PATH | O_NOFOLLOW | O_CLOEXEC, 0);
|
2022-11-06 02:49:41 +00:00
|
|
|
ALLOW_CANCELLATIONS;
|
2022-07-20 04:18:33 +00:00
|
|
|
if (rc == -1) return rc;
|
|
|
|
|
2022-07-18 09:12:42 +00:00
|
|
|
pb.parent_fd = rc;
|
|
|
|
struct stat st;
|
2022-07-18 14:23:15 +00:00
|
|
|
if ((rc = sys_fstat(pb.parent_fd, &st)) == -1) {
|
|
|
|
return err_close(rc, pb.parent_fd);
|
|
|
|
}
|
2022-07-20 04:18:33 +00:00
|
|
|
if (!S_ISDIR(st.st_mode)) {
|
|
|
|
pb.allowed_access &= FILE_BITS;
|
|
|
|
}
|
2022-07-18 14:23:15 +00:00
|
|
|
if ((rc = landlock_add_rule(State.fd, LANDLOCK_RULE_PATH_BENEATH, &pb, 0))) {
|
2022-07-18 09:12:42 +00:00
|
|
|
return err_close(rc, pb.parent_fd);
|
2022-07-18 14:23:15 +00:00
|
|
|
}
|
2022-07-18 09:11:06 +00:00
|
|
|
sys_close(pb.parent_fd);
|
2022-07-18 09:12:42 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2022-07-18 09:11:06 +00:00
|
|
|
/**
|
2022-08-11 07:15:29 +00:00
|
|
|
* Makes files accessible, e.g.
|
2022-07-18 15:26:40 +00:00
|
|
|
*
|
2022-07-20 04:18:33 +00:00
|
|
|
* unveil(".", "r"); // current directory + children are visible
|
|
|
|
* unveil("/etc", "r"); // make /etc readable too
|
|
|
|
* unveil(0, 0); // commit and lock policy
|
2022-07-19 09:11:04 +00:00
|
|
|
*
|
2022-07-24 12:54:26 +00:00
|
|
|
* Unveiling restricts a view of the filesystem to a set of allowed
|
|
|
|
* paths with specific privileges.
|
2022-07-19 09:11:04 +00:00
|
|
|
*
|
|
|
|
* Once you start using unveil(), the entire file system is considered
|
|
|
|
* hidden. You then specify, by repeatedly calling unveil(), which paths
|
|
|
|
* should become unhidden. When you're finished, you call `unveil(0,0)`
|
2022-07-24 12:54:26 +00:00
|
|
|
* which commits your policy.
|
2022-07-19 09:11:04 +00:00
|
|
|
*
|
2023-06-03 15:12:13 +00:00
|
|
|
* This function requires OpenBSD or Linux 5.13+ (2022+). If the kernel
|
|
|
|
* support isn't available (or we're in an emulator like Qemu or Blink)
|
|
|
|
* then zero is returned and nothing happens (instead of raising ENOSYS)
|
|
|
|
* because the files are still unveiled. Use `unveil("", 0)` to feature
|
|
|
|
* check the host system, which is defined as a no-op that'll fail if
|
|
|
|
* the host system doesn't have the necessary features that allow
|
|
|
|
* unveil() impose bona-fide security restrictions. Otherwise, if
|
|
|
|
* everything is good, a return value `>=0` is returned, where `0` means
|
|
|
|
* OpenBSD, and `>=1` means Linux with Landlock LSM, in which case the
|
|
|
|
* return code shall be the maximum supported Landlock ABI version.
|
2022-08-11 07:15:29 +00:00
|
|
|
*
|
2022-07-19 09:11:04 +00:00
|
|
|
* There are some differences between unveil() on Linux versus OpenBSD.
|
|
|
|
*
|
|
|
|
* 1. Build your policy and lock it in one go. On OpenBSD, policies take
|
|
|
|
* effect immediately and may evolve as you continue to call unveil()
|
|
|
|
* but only in a more restrictive direction. On Linux, nothing will
|
|
|
|
* happen until you call `unveil(0,0)` which commits and locks.
|
|
|
|
*
|
|
|
|
* 2. Try not to overlap directory trees. On OpenBSD, if directory trees
|
|
|
|
* overlap, then the most restrictive policy will be used for a given
|
|
|
|
* file. On Linux overlapping may result in a less restrictive policy
|
|
|
|
* and possibly even undefined behavior.
|
|
|
|
*
|
|
|
|
* 3. OpenBSD and Linux disagree on error codes. On OpenBSD, accessing
|
|
|
|
* paths outside of the allowed set raises ENOENT, and accessing ones
|
|
|
|
* with incorrect permissions raises EACCES. On Linux, both these
|
|
|
|
* cases raise EACCES.
|
2022-07-18 15:26:40 +00:00
|
|
|
*
|
2022-07-19 09:11:04 +00:00
|
|
|
* 4. Unlike OpenBSD, Linux does nothing to conceal the existence of
|
|
|
|
* paths. Even with an unveil() policy in place, it's still possible
|
|
|
|
* to access the metadata of all files using functions like stat()
|
|
|
|
* and open(O_PATH), provided you know the path. A sandboxed process
|
|
|
|
* can always, for example, determine how many bytes of data are in
|
|
|
|
* /etc/passwd, even if the file isn't readable. But it's still not
|
|
|
|
* possible to use opendir() and go fishing for paths which weren't
|
|
|
|
* previously known.
|
2022-07-18 15:26:40 +00:00
|
|
|
*
|
2023-06-03 15:12:13 +00:00
|
|
|
* 5. Use ftruncate() rather than truncate() if you wish for portability
|
|
|
|
* to Linux kernels versions released before February 2022. One issue
|
|
|
|
* Landlock hadn't addressed as of ABI version 2 was restrictions
|
|
|
|
* over truncate() and setxattr() which could permit certain kinds of
|
2023-04-17 23:17:02 +00:00
|
|
|
* 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. Linux 6.2 has improved this situation with Landlock
|
|
|
|
* ABI v3, which added the ability to control truncation operations -
|
2023-06-03 15:12:13 +00:00
|
|
|
* this means the SECCOMP BPF filter will only disable truncate() on
|
|
|
|
* Linux 6.1 or older.
|
2022-07-24 12:54:26 +00:00
|
|
|
*
|
|
|
|
* 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.
|
2022-07-20 22:13:39 +00:00
|
|
|
*
|
2022-07-19 09:11:04 +00:00
|
|
|
* This system call is supported natively on OpenBSD and polyfilled on
|
|
|
|
* Linux using the Landlock LSM[1].
|
2022-07-18 15:26:40 +00:00
|
|
|
*
|
2022-07-19 09:11:04 +00:00
|
|
|
* @param path is the file or directory to unveil
|
|
|
|
* @param permissions is a string consisting of zero or more of the
|
|
|
|
* following characters:
|
2022-07-18 15:26:40 +00:00
|
|
|
*
|
2022-07-19 09:11:04 +00:00
|
|
|
* - 'r' makes `path` available for read-only path operations,
|
|
|
|
* corresponding to the pledge promise "rpath".
|
2022-07-18 15:26:40 +00:00
|
|
|
*
|
2022-07-19 09:11:04 +00:00
|
|
|
* - `w` makes `path` available for write operations, corresponding
|
|
|
|
* to the pledge promise "wpath".
|
2022-07-18 15:26:40 +00:00
|
|
|
*
|
2022-07-19 09:11:04 +00:00
|
|
|
* - `x` makes `path` available for execute operations,
|
|
|
|
* corresponding to the pledge promises "exec" and "execnative".
|
2022-07-18 15:26:40 +00:00
|
|
|
*
|
2022-07-19 09:11:04 +00:00
|
|
|
* - `c` allows `path` to be created and removed, corresponding to
|
|
|
|
* the pledge promise "cpath".
|
2022-07-18 15:26:40 +00:00
|
|
|
*
|
2023-06-03 15:12:13 +00:00
|
|
|
* @return 0 on success, or -1 w/ errno; note: if `unveil("",0)` is used
|
|
|
|
* to perform a feature check, then on Linux a value greater than 0
|
|
|
|
* shall be returned which is the supported Landlock ABI version
|
|
|
|
* @raise EPERM if unveil() is called after locking
|
2022-07-18 15:26:40 +00:00
|
|
|
* @raise EINVAL if one argument is set and the other is not
|
|
|
|
* @raise EINVAL if an invalid character in `permissions` was found
|
2023-06-03 15:12:13 +00:00
|
|
|
* @raise ENOSYS if `unveil("",0)` was used and security isn't possible
|
|
|
|
* @raise EOPNOTSUPP if `unveil("",0)` was used and Landlock LSM is disabled
|
2023-04-17 23:17:02 +00:00
|
|
|
* @note on Linux this function requires Linux Kernel 5.13+ and version 6.2+
|
|
|
|
* to properly support truncation operations
|
2022-07-19 09:11:04 +00:00
|
|
|
* @see [1] https://docs.kernel.org/userspace-api/landlock.html
|
2022-07-21 16:16:38 +00:00
|
|
|
* @threadsafe
|
2022-07-18 09:11:06 +00:00
|
|
|
*/
|
2022-07-18 09:12:42 +00:00
|
|
|
int unveil(const char *path, const char *permissions) {
|
2022-08-10 19:56:45 +00:00
|
|
|
int e, rc;
|
|
|
|
e = errno;
|
2023-06-03 15:12:13 +00:00
|
|
|
if (path && !*path) {
|
|
|
|
// OpenBSD will always fail on both unveil("",0) and unveil("",""),
|
|
|
|
// since an empty `path` is invalid and `permissions` is mandatory.
|
|
|
|
// Cosmopolitan Libc uses it as a feature check convention, to test
|
|
|
|
// if the host environment enables unveil() to impose true security
|
|
|
|
// restrictions because the default behavior is to silently succeed
|
|
|
|
// so that programs will err on the side of working if distributed.
|
2023-06-16 06:22:49 +00:00
|
|
|
if (permissions) return einval();
|
2023-06-03 15:12:13 +00:00
|
|
|
if (IsOpenbsd()) return 0;
|
|
|
|
if (landlock_abi_version != -1) {
|
2023-07-26 20:54:49 +00:00
|
|
|
unassert(landlock_abi_version >= 1);
|
2023-06-03 15:12:13 +00:00
|
|
|
return landlock_abi_version;
|
|
|
|
} else {
|
2023-07-26 20:54:49 +00:00
|
|
|
unassert(landlock_abi_errno);
|
2023-06-03 15:12:13 +00:00
|
|
|
errno = landlock_abi_errno;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else if (!IsTiny() && IsGenuineBlink()) {
|
|
|
|
rc = 0; // blink doesn't support landlock; avoid noisy log warnings
|
2022-11-11 05:52:47 +00:00
|
|
|
} else if (IsLinux()) {
|
2022-07-18 09:12:42 +00:00
|
|
|
rc = sys_unveil_linux(path, permissions);
|
|
|
|
} else {
|
|
|
|
rc = sys_unveil(path, permissions);
|
|
|
|
}
|
2022-08-10 19:56:45 +00:00
|
|
|
if (rc == -1 && errno == ENOSYS) {
|
|
|
|
errno = e;
|
|
|
|
rc = 0;
|
|
|
|
}
|
2022-07-18 09:12:42 +00:00
|
|
|
STRACE("unveil(%#s, %#s) → %d% m", path, permissions, rc);
|
|
|
|
return rc;
|
|
|
|
}
|