Skip to content

Commit

Permalink
initial landlock impl
Browse files Browse the repository at this point in the history
  • Loading branch information
Riolku committed Sep 17, 2021
1 parent 8072c5e commit 7c5316f
Show file tree
Hide file tree
Showing 11 changed files with 275 additions and 10 deletions.
3 changes: 3 additions & 0 deletions dmoj/cptbox/_cptbox.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ PTBOX_SPAWN_FAIL_NO_NEW_PRIVS: int
PTBOX_SPAWN_FAIL_SECCOMP: int
PTBOX_SPAWN_FAIL_TRACEME: int
PTBOX_SPAWN_FAIL_EXECVE: int
PTBOX_SPAWN_FAIL_LANDLOCK: int

def has_landlock() -> bool: ...

AT_FDCWD: int
bsd_get_proc_cwd: Callable[[int], str]
Expand Down
37 changes: 35 additions & 2 deletions dmoj/cptbox/_cptbox.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ __all__ = ['Process', 'Debugger', 'bsd_get_proc_cwd', 'bsd_get_proc_fdno', 'MAX_
'PTBOX_ABI_X86', 'PTBOX_ABI_X64', 'PTBOX_ABI_X32', 'PTBOX_ABI_ARM', 'PTBOX_ABI_ARM64',
'PTBOX_ABI_FREEBSD_X64', 'PTBOX_ABI_INVALID', 'PTBOX_ABI_COUNT',
'PTBOX_SPAWN_FAIL_NO_NEW_PRIVS', 'PTBOX_SPAWN_FAIL_SECCOMP', 'PTBOX_SPAWN_FAIL_TRACEME',
'PTBOX_SPAWN_FAIL_EXECVE']
'PTBOX_SPAWN_FAIL_EXECVE', 'PTBOX_SPAWN_FAIL_LANDLOCK']


cdef extern from 'ptbox.h' nogil:
Expand Down Expand Up @@ -117,7 +117,14 @@ cdef extern from 'helper.h' nogil:
int stderr_
int abi_for_seccomp
int *seccomp_handlers

char **read_exact_files
char **read_exact_dirs
char **read_recursive_dirs
char **write_exact_files
char **write_exact_dirs
char **write_recursive_dirs

int has_landlock_check()
void cptbox_closefrom(int lowfd)
int cptbox_child_run(child_config *)
char *_bsd_get_proc_cwd "bsd_get_proc_cwd"(pid_t pid)
Expand All @@ -128,6 +135,7 @@ cdef extern from 'helper.h' nogil:
PTBOX_SPAWN_FAIL_SECCOMP
PTBOX_SPAWN_FAIL_TRACEME
PTBOX_SPAWN_FAIL_EXECVE
PTBOX_SPAWN_FAIL_LANDLOCK

int _memory_fd_create "memory_fd_create"()
int _memory_fd_seal "memory_fd_seal"(int fd)
Expand Down Expand Up @@ -218,6 +226,12 @@ def memory_fd_seal(int fd):
if result == -1:
PyErr_SetFromErrno(OSError)

def has_landlock():
cdef int code = has_landlock_check();
if code == -1:
PyErr_SetFromErrno(OSError)
return <bool>code

cdef class Process


Expand Down Expand Up @@ -475,6 +489,12 @@ cdef class Process:
config.argv = NULL
config.envp = NULL
config.seccomp_handlers = NULL
config.read_exact_files = NULL
config.read_exact_dirs = NULL
config.read_recursive_dirs = NULL
config.write_exact_files = NULL
config.write_exact_dirs = NULL
config.write_recursive_dirs = NULL

try:
config.address_space = self._child_address
Expand Down Expand Up @@ -502,12 +522,25 @@ cdef class Process:
for i in range(MAX_SYSCALL):
config.seccomp_handlers[i] = handlers[i]

config.read_exact_files = alloc_byte_array(self.read_exact_files)
config.read_exact_dirs = alloc_byte_array(self.read_exact_dirs)
config.read_recursive_dirs = alloc_byte_array(self.read_recursive_dirs)
config.write_exact_files = alloc_byte_array(self.write_exact_files)
config.write_exact_dirs = alloc_byte_array(self.write_exact_dirs)
config.write_recursive_dirs = alloc_byte_array(self.write_recursive_dirs)

if self.process.spawn(pt_child, &config):
raise RuntimeError('failed to spawn child')
finally:
free(config.argv)
free(config.envp)
free(config.seccomp_handlers)
free(config.read_exact_files)
free(config.read_exact_dirs)
free(config.read_recursive_dirs)
free(config.write_exact_files)
free(config.write_exact_dirs)
free(config.write_recursive_dirs)

cpdef _monitor(self):
cdef int exitcode
Expand Down
84 changes: 79 additions & 5 deletions dmoj/cptbox/helper.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "ptbox.h"
#include "helper.h"
#include "landlock_helpers.h"

#include <dirent.h>
#include <errno.h>
Expand Down Expand Up @@ -31,6 +32,7 @@
# define FD_DIR "/proc/self/fd"
#endif


inline void setrlimit2(int resource, rlim_t cur, rlim_t max) {
rlimit limit;
limit.rlim_cur = cur;
Expand Down Expand Up @@ -73,13 +75,69 @@ int cptbox_child_run(const struct child_config *config) {
kill(getpid(), SIGSTOP);

#if !PTBOX_FREEBSD
// landlock setup
// if at any point landlock fails, we log and resume normal seccomp-trapping
int ruleset_fd, rc;
struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs =
LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_WRITE_FILE |
LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR |
LANDLOCK_ACCESS_FS_REMOVE_DIR | LANDLOCK_ACCESS_FS_REMOVE_FILE |
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,
};

ruleset_fd =
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
if (ruleset_fd < 0) {
if (errno != ENOSYS && errno != EOPNOTSUPP) {
perror("Failed to create a ruleset");
return PTBOX_SPAWN_FAIL_LANDLOCK;
} else
goto seccomp_setup;
}

#define READ_EXACT_FILE_RULE \
LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_FILE
#define READ_EXACT_DIR_RULE LANDLOCK_ACCESS_FS_READ_DIR
#define READ_RECURSIVE_DIR_RULE READ_EXACT_FILE_RULE | READ_EXACT_DIR_RULE
#define WRITE_EXACT_FILE_RULE LANDLOCK_ACCESS_FS_WRITE_FILE
#define WRITE_EXACT_DIR_RULE LANDLOCK_ACCESS_FS_READ_DIR
#define WRITE_RECURSIVE_DIR_RULE WRITE_EXACT_FILE_RULE | WRITE_EXACT_DIR_RULE

if (landlock_add_rules(ruleset_fd, config->read_exact_files,
READ_EXACT_FILE_RULE) ||
landlock_add_rules(ruleset_fd, config->read_exact_dirs,
READ_EXACT_DIR_RULE) ||
landlock_add_rules(ruleset_fd, config->read_recursive_dirs,
READ_RECURSIVE_DIR_RULE) ||
landlock_add_rules(ruleset_fd, config->write_exact_files,
WRITE_EXACT_FILE_RULE) ||
landlock_add_rules(ruleset_fd, config->write_exact_dirs,
WRITE_EXACT_DIR_RULE) ||
landlock_add_rules(ruleset_fd, config->write_recursive_dirs,
WRITE_RECURSIVE_DIR_RULE)) {
// landlock_add_rules logs errors
close(ruleset_fd);
return PTBOX_SPAWN_FAIL_LANDLOCK;
}

rc = landlock_restrict_self(ruleset_fd, 0);
close(ruleset_fd);
if (rc) {
perror("Failed to enforce ruleset");
return PTBOX_SPAWN_FAIL_LANDLOCK;
}

seccomp_setup:
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_TRACE(0));
if (!ctx) {
fprintf(stderr, "Failed to initialize seccomp context!");
goto seccomp_fail;
return PTBOX_SPAWN_FAIL_SECCOMP;
}

int rc;
// By default, the native architecture is added to the filter already, so we add all the non-native ones.
// This will bloat the filter due to additional architectures, but a few extra compares in the BPF matters
// very little when syscalls are rare and other overhead is expensive.
Expand Down Expand Up @@ -108,7 +166,7 @@ int cptbox_child_run(const struct child_config *config) {

if ((rc = seccomp_load(ctx))) {
fprintf(stderr, "seccomp_load: %s\n", strerror(-rc));
goto seccomp_fail;
return PTBOX_SPAWN_FAIL_SECCOMP;
}

seccomp_release(ctx);
Expand Down Expand Up @@ -141,9 +199,25 @@ int cptbox_child_run(const struct child_config *config) {
execve(config->file, config->argv, config->envp);
perror("execve");
return PTBOX_SPAWN_FAIL_EXECVE;
}

seccomp_fail:
return PTBOX_SPAWN_FAIL_SECCOMP;
int has_landlock_check() {
#if !PTBOX_FREEBSD
int landlock_version = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
if(landlock_version < 0) {
if(errno == ENOSYS || errno == EOPNOTSUPP) {
return 0;
}
else {
return -1;
}
}
else {
return 1;
}
#else
return 0; // FreeBSD does not have landlock
#endif
}

// From python's _posixsubprocess
Expand Down
9 changes: 9 additions & 0 deletions dmoj/cptbox/helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#define PTBOX_SPAWN_FAIL_SECCOMP 203
#define PTBOX_SPAWN_FAIL_TRACEME 204
#define PTBOX_SPAWN_FAIL_EXECVE 205
#define PTBOX_SPAWN_FAIL_LANDLOCK 206

struct child_config {
unsigned long memory;
Expand All @@ -22,8 +23,16 @@ struct child_config {
int stdout_;
int stderr_;
int *seccomp_handlers;
char** read_exact_files;
char** read_exact_dirs;
char** read_recursive_dirs;
char** write_exact_files;
char** write_exact_dirs;
char** write_recursive_dirs;
};

int has_landlock_check();

void cptbox_closefrom(int lowfd);
int cptbox_child_run(const struct child_config *config);

Expand Down
14 changes: 11 additions & 3 deletions dmoj/cptbox/isolate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import sys
from typing import Optional, Tuple

from dmoj.cptbox._cptbox import AT_FDCWD, Debugger, bsd_get_proc_cwd, bsd_get_proc_fdno
from dmoj.cptbox._cptbox import AT_FDCWD, Debugger, bsd_get_proc_cwd, bsd_get_proc_fdno, has_landlock
from dmoj.cptbox.filesystem_policies import FilesystemPolicy
from dmoj.cptbox.handlers import (
ACCESS_EACCES,
Expand Down Expand Up @@ -44,11 +44,19 @@ def __init__(self, read_fs, write_fs=None, writable=(1, 2)):
self._getcwd_pid = lambda pid: os.readlink('/proc/%d/cwd' % pid)
self._getfd_pid = lambda pid, fd: os.readlink('/proc/%d/fd/%d' % (pid, fd))

if has_landlock():
self.update({sys_openat: ALLOW, sys_open: ALLOW})
else:
self.update(
{
sys_openat: self.check_file_access_at('openat', is_open=True),
sys_open: self.check_file_access('open', 0, is_open=True),
}
)

self.update(
{
# Deny with report
sys_openat: self.check_file_access_at('openat', is_open=True),
sys_open: self.check_file_access('open', 0, is_open=True),
sys_faccessat: self.check_file_access_at('faccessat'),
sys_access: self.check_file_access('access', 0),
sys_readlink: self.check_file_access('readlink', 0),
Expand Down
76 changes: 76 additions & 0 deletions dmoj/cptbox/landlock_header.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
* Landlock - User space API
*
* Copyright © 2017-2020 Mickaël Salaün <[email protected]>
* Copyright © 2018-2020 ANSSI
*/
#if defined(_LINUX_LANDLOCK_H)
#elif __has_include(<linux/landlock.h>)
#include <linux/landlock.h>
#else
typedef unsigned long long __u64;
typedef __signed__ int __s32;
typedef unsigned int __u32;

struct landlock_ruleset_attr {
__u64 handled_access_fs;
};
#define LANDLOCK_CREATE_RULESET_VERSION (1U << 0)
enum landlock_rule_type {
LANDLOCK_RULE_PATH_BENEATH = 1,
};
struct landlock_path_beneath_attr {
__u64 allowed_access;
__s32 parent_fd;
} __attribute__((packed));
#define LANDLOCK_ACCESS_FS_EXECUTE (1ULL << 0)
#define LANDLOCK_ACCESS_FS_WRITE_FILE (1ULL << 1)
#define LANDLOCK_ACCESS_FS_READ_FILE (1ULL << 2)
#define LANDLOCK_ACCESS_FS_READ_DIR (1ULL << 3)
#define LANDLOCK_ACCESS_FS_REMOVE_DIR (1ULL << 4)
#define LANDLOCK_ACCESS_FS_REMOVE_FILE (1ULL << 5)
#define LANDLOCK_ACCESS_FS_MAKE_CHAR (1ULL << 6)
#define LANDLOCK_ACCESS_FS_MAKE_DIR (1ULL << 7)
#define LANDLOCK_ACCESS_FS_MAKE_REG (1ULL << 8)
#define LANDLOCK_ACCESS_FS_MAKE_SOCK (1ULL << 9)
#define LANDLOCK_ACCESS_FS_MAKE_FIFO (1ULL << 10)
#define LANDLOCK_ACCESS_FS_MAKE_BLOCK (1ULL << 11)
#define LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 12)
#endif /* _LINUX_LANDLOCK_H */

#include <sys/syscall.h>
#ifndef __NR_landlock_create_ruleset
#define __NR_landlock_create_ruleset 444
#endif
#ifndef __NR_landlock_add_rule
#define __NR_landlock_add_rule 445
#endif
#ifndef __NR_landlock_restrict_self
#define __NR_landlock_restrict_self 446
#endif

#include <cstddef>
#include <unistd.h>
#ifndef landlock_create_ruleset
static inline int
landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,
const size_t size, const __u32 flags) {
return syscall(__NR_landlock_create_ruleset, attr, size, flags);
}
#endif
#ifndef landlock_add_rule
static inline int landlock_add_rule(const int ruleset_fd,
const enum landlock_rule_type rule_type,
const void *const rule_attr,
const __u32 flags) {
return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
flags);
}
#endif
#ifndef landlock_restrict_self
static inline int landlock_restrict_self(const int ruleset_fd,
const __u32 flags) {
return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
}
#endif
37 changes: 37 additions & 0 deletions dmoj/cptbox/landlock_helpers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#ifndef __FreeBSD__

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

#include "landlock_helpers.h"

int landlock_add_rules(const int ruleset_fd, const char *const *const paths,
__u64 access_rule) {
struct landlock_path_beneath_attr path_beneath = {
.allowed_access = access_rule,
.parent_fd = -1,
};
for (const char *const *pathptr = paths; *pathptr; pathptr++) {
path_beneath.parent_fd = open(*pathptr, O_PATH | O_CLOEXEC);
if (path_beneath.parent_fd < 0) {
if (errno == ENOENT)
goto close_fd; // missing files are ignored
fprintf(stderr, "Failed to open path '%s' for rule: %s\n", *pathptr,
strerror(errno));
return -1;
}
if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
&path_beneath, 0)) {
fprintf(stderr, "Failed to add rule '%s' to ruleset: %s\n",
*pathptr, strerror(errno));
return -1;
}
close_fd:
close(path_beneath.parent_fd);
}
return 0;
}

#endif
4 changes: 4 additions & 0 deletions dmoj/cptbox/landlock_helpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#include "landlock_header.h"

int landlock_add_rules(const int ruleset_fd, const char *const *const paths,
__u64 access_rule);
Loading

0 comments on commit 7c5316f

Please sign in to comment.