Skip to content

Commit 1bdd49c

Browse files
committed
initial landlock impl
1 parent 8072c5e commit 1bdd49c

11 files changed

+272
-11
lines changed

dmoj/cptbox/_cptbox.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ PTBOX_SPAWN_FAIL_NO_NEW_PRIVS: int
9090
PTBOX_SPAWN_FAIL_SECCOMP: int
9191
PTBOX_SPAWN_FAIL_TRACEME: int
9292
PTBOX_SPAWN_FAIL_EXECVE: int
93+
PTBOX_SPAWN_FAIL_LANDLOCK: int
94+
95+
def has_landlock() -> bool: ...
9396

9497
AT_FDCWD: int
9598
bsd_get_proc_cwd: Callable[[int], str]

dmoj/cptbox/_cptbox.pyx

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ __all__ = ['Process', 'Debugger', 'bsd_get_proc_cwd', 'bsd_get_proc_fdno', 'MAX_
1313
'PTBOX_ABI_X86', 'PTBOX_ABI_X64', 'PTBOX_ABI_X32', 'PTBOX_ABI_ARM', 'PTBOX_ABI_ARM64',
1414
'PTBOX_ABI_FREEBSD_X64', 'PTBOX_ABI_INVALID', 'PTBOX_ABI_COUNT',
1515
'PTBOX_SPAWN_FAIL_NO_NEW_PRIVS', 'PTBOX_SPAWN_FAIL_SECCOMP', 'PTBOX_SPAWN_FAIL_TRACEME',
16-
'PTBOX_SPAWN_FAIL_EXECVE']
16+
'PTBOX_SPAWN_FAIL_EXECVE', 'PTBOX_SPAWN_FAIL_LANDLOCK']
1717

1818

1919
cdef extern from 'ptbox.h' nogil:
@@ -117,7 +117,14 @@ cdef extern from 'helper.h' nogil:
117117
int stderr_
118118
int abi_for_seccomp
119119
int *seccomp_handlers
120-
120+
char **read_exact_files
121+
char **read_exact_dirs
122+
char **read_recursive_dirs
123+
char **write_exact_files
124+
char **write_exact_dirs
125+
char **write_recursive_dirs
126+
127+
int has_landlock_check()
121128
void cptbox_closefrom(int lowfd)
122129
int cptbox_child_run(child_config *)
123130
char *_bsd_get_proc_cwd "bsd_get_proc_cwd"(pid_t pid)
@@ -128,6 +135,7 @@ cdef extern from 'helper.h' nogil:
128135
PTBOX_SPAWN_FAIL_SECCOMP
129136
PTBOX_SPAWN_FAIL_TRACEME
130137
PTBOX_SPAWN_FAIL_EXECVE
138+
PTBOX_SPAWN_FAIL_LANDLOCK
131139

132140
int _memory_fd_create "memory_fd_create"()
133141
int _memory_fd_seal "memory_fd_seal"(int fd)
@@ -218,6 +226,12 @@ def memory_fd_seal(int fd):
218226
if result == -1:
219227
PyErr_SetFromErrno(OSError)
220228

229+
def has_landlock():
230+
cdef int code = has_landlock_check();
231+
if code == -1:
232+
PyErr_SetFromErrno(OSError)
233+
return <bool>code
234+
221235
cdef class Process
222236

223237

@@ -475,6 +489,12 @@ cdef class Process:
475489
config.argv = NULL
476490
config.envp = NULL
477491
config.seccomp_handlers = NULL
492+
config.read_exact_files = NULL
493+
config.read_exact_dirs = NULL
494+
config.read_recursive_dirs = NULL
495+
config.write_exact_files = NULL
496+
config.write_exact_dirs = NULL
497+
config.write_recursive_dirs = NULL
478498

479499
try:
480500
config.address_space = self._child_address
@@ -502,12 +522,25 @@ cdef class Process:
502522
for i in range(MAX_SYSCALL):
503523
config.seccomp_handlers[i] = handlers[i]
504524

525+
config.read_exact_files = alloc_byte_array(self.read_exact_files)
526+
config.read_exact_dirs = alloc_byte_array(self.read_exact_dirs)
527+
config.read_recursive_dirs = alloc_byte_array(self.read_recursive_dirs)
528+
config.write_exact_files = alloc_byte_array(self.write_exact_files)
529+
config.write_exact_dirs = alloc_byte_array(self.write_exact_dirs)
530+
config.write_recursive_dirs = alloc_byte_array(self.write_recursive_dirs)
531+
505532
if self.process.spawn(pt_child, &config):
506533
raise RuntimeError('failed to spawn child')
507534
finally:
508535
free(config.argv)
509536
free(config.envp)
510537
free(config.seccomp_handlers)
538+
free(config.read_exact_files)
539+
free(config.read_exact_dirs)
540+
free(config.read_recursive_dirs)
541+
free(config.write_exact_files)
542+
free(config.write_exact_dirs)
543+
free(config.write_recursive_dirs)
511544

512545
cpdef _monitor(self):
513546
cdef int exitcode

dmoj/cptbox/helper.cpp

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "ptbox.h"
22
#include "helper.h"
3+
#include "landlock_helpers.h"
34

45
#include <dirent.h>
56
#include <errno.h>
@@ -31,6 +32,7 @@
3132
# define FD_DIR "/proc/self/fd"
3233
#endif
3334

35+
3436
inline void setrlimit2(int resource, rlim_t cur, rlim_t max) {
3537
rlimit limit;
3638
limit.rlim_cur = cur;
@@ -72,14 +74,70 @@ int cptbox_child_run(const struct child_config *config) {
7274

7375
kill(getpid(), SIGSTOP);
7476

75-
#if !PTBOX_FREEBSD
77+
#ifndef __FreeBSD__
78+
// landlock setup
79+
// if at any point landlock fails, we log and resume normal seccomp-trapping
80+
int ruleset_fd, rc;
81+
struct landlock_ruleset_attr ruleset_attr = {
82+
.handled_access_fs =
83+
LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_WRITE_FILE |
84+
LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR |
85+
LANDLOCK_ACCESS_FS_REMOVE_DIR | LANDLOCK_ACCESS_FS_REMOVE_FILE |
86+
LANDLOCK_ACCESS_FS_MAKE_CHAR | LANDLOCK_ACCESS_FS_MAKE_DIR |
87+
LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_MAKE_SOCK |
88+
LANDLOCK_ACCESS_FS_MAKE_FIFO | LANDLOCK_ACCESS_FS_MAKE_BLOCK |
89+
LANDLOCK_ACCESS_FS_MAKE_SYM,
90+
};
91+
92+
ruleset_fd =
93+
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
94+
if (ruleset_fd < 0) {
95+
if (errno != ENOSYS && errno != EOPNOTSUPP) {
96+
perror("Failed to create a ruleset");
97+
return PTBOX_SPAWN_FAIL_LANDLOCK;
98+
} else
99+
goto seccomp_setup;
100+
}
101+
102+
#define READ_EXACT_FILE_RULE \
103+
LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_FILE
104+
#define READ_EXACT_DIR_RULE LANDLOCK_ACCESS_FS_READ_DIR
105+
#define READ_RECURSIVE_DIR_RULE READ_EXACT_FILE_RULE | READ_EXACT_DIR_RULE
106+
#define WRITE_EXACT_FILE_RULE LANDLOCK_ACCESS_FS_WRITE_FILE
107+
#define WRITE_EXACT_DIR_RULE LANDLOCK_ACCESS_FS_READ_DIR
108+
#define WRITE_RECURSIVE_DIR_RULE WRITE_EXACT_FILE_RULE | WRITE_EXACT_DIR_RULE
109+
110+
if (landlock_add_rules(ruleset_fd, config->read_exact_files,
111+
READ_EXACT_FILE_RULE) ||
112+
landlock_add_rules(ruleset_fd, config->read_exact_dirs,
113+
READ_EXACT_DIR_RULE) ||
114+
landlock_add_rules(ruleset_fd, config->read_recursive_dirs,
115+
READ_RECURSIVE_DIR_RULE) ||
116+
landlock_add_rules(ruleset_fd, config->write_exact_files,
117+
WRITE_EXACT_FILE_RULE) ||
118+
landlock_add_rules(ruleset_fd, config->write_exact_dirs,
119+
WRITE_EXACT_DIR_RULE) ||
120+
landlock_add_rules(ruleset_fd, config->write_recursive_dirs,
121+
WRITE_RECURSIVE_DIR_RULE)) {
122+
// landlock_add_rules logs errors
123+
close(ruleset_fd);
124+
return PTBOX_SPAWN_FAIL_LANDLOCK;
125+
}
126+
127+
rc = landlock_restrict_self(ruleset_fd, 0);
128+
close(ruleset_fd);
129+
if (rc) {
130+
perror("Failed to enforce ruleset");
131+
return PTBOX_SPAWN_FAIL_LANDLOCK;
132+
}
133+
134+
seccomp_setup:
76135
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_TRACE(0));
77136
if (!ctx) {
78137
fprintf(stderr, "Failed to initialize seccomp context!");
79-
goto seccomp_fail;
138+
return PTBOX_SPAWN_FAIL_SECCOMP;
80139
}
81140

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

109167
if ((rc = seccomp_load(ctx))) {
110168
fprintf(stderr, "seccomp_load: %s\n", strerror(-rc));
111-
goto seccomp_fail;
169+
return PTBOX_SPAWN_FAIL_SECCOMP;
112170
}
113171

114172
seccomp_release(ctx);
@@ -141,9 +199,21 @@ int cptbox_child_run(const struct child_config *config) {
141199
execve(config->file, config->argv, config->envp);
142200
perror("execve");
143201
return PTBOX_SPAWN_FAIL_EXECVE;
202+
}
144203

145-
seccomp_fail:
146-
return PTBOX_SPAWN_FAIL_SECCOMP;
204+
int has_landlock_check() {
205+
int landlock_version = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
206+
if(landlock_version < 0) {
207+
if(errno == ENOSYS || errno == EOPNOTSUPP) {
208+
return 0;
209+
}
210+
else {
211+
return -1;
212+
}
213+
}
214+
else {
215+
return 1;
216+
}
147217
}
148218

149219
// From python's _posixsubprocess

dmoj/cptbox/helper.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define PTBOX_SPAWN_FAIL_SECCOMP 203
77
#define PTBOX_SPAWN_FAIL_TRACEME 204
88
#define PTBOX_SPAWN_FAIL_EXECVE 205
9+
#define PTBOX_SPAWN_FAIL_LANDLOCK 206
910

1011
struct child_config {
1112
unsigned long memory;
@@ -22,8 +23,16 @@ struct child_config {
2223
int stdout_;
2324
int stderr_;
2425
int *seccomp_handlers;
26+
char** read_exact_files;
27+
char** read_exact_dirs;
28+
char** read_recursive_dirs;
29+
char** write_exact_files;
30+
char** write_exact_dirs;
31+
char** write_recursive_dirs;
2532
};
2633

34+
int has_landlock_check();
35+
2736
void cptbox_closefrom(int lowfd);
2837
int cptbox_child_run(const struct child_config *config);
2938

dmoj/cptbox/isolate.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44
from typing import Optional, Tuple
55

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

47+
if has_landlock():
48+
self.update({sys_openat: ALLOW, sys_open: ALLOW})
49+
else:
50+
self.update(
51+
{
52+
sys_openat: self.check_file_access_at('openat', is_open=True),
53+
sys_open: self.check_file_access('open', 0, is_open=True),
54+
}
55+
)
56+
4757
self.update(
4858
{
4959
# Deny with report
50-
sys_openat: self.check_file_access_at('openat', is_open=True),
51-
sys_open: self.check_file_access('open', 0, is_open=True),
5260
sys_faccessat: self.check_file_access_at('faccessat'),
5361
sys_access: self.check_file_access('access', 0),
5462
sys_readlink: self.check_file_access('readlink', 0),

dmoj/cptbox/landlock_header.h

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
2+
/*
3+
* Landlock - User space API
4+
*
5+
* Copyright © 2017-2020 Mickaël Salaün <[email protected]>
6+
* Copyright © 2018-2020 ANSSI
7+
*/
8+
#if defined(_LINUX_LANDLOCK_H)
9+
#elif __has_include(<linux/landlock.h>)
10+
#include <linux/landlock.h>
11+
#else
12+
typedef unsigned long long __u64;
13+
typedef __signed__ int __s32;
14+
typedef unsigned int __u32;
15+
16+
struct landlock_ruleset_attr {
17+
__u64 handled_access_fs;
18+
};
19+
#define LANDLOCK_CREATE_RULESET_VERSION (1U << 0)
20+
enum landlock_rule_type {
21+
LANDLOCK_RULE_PATH_BENEATH = 1,
22+
};
23+
struct landlock_path_beneath_attr {
24+
__u64 allowed_access;
25+
__s32 parent_fd;
26+
} __attribute__((packed));
27+
#define LANDLOCK_ACCESS_FS_EXECUTE (1ULL << 0)
28+
#define LANDLOCK_ACCESS_FS_WRITE_FILE (1ULL << 1)
29+
#define LANDLOCK_ACCESS_FS_READ_FILE (1ULL << 2)
30+
#define LANDLOCK_ACCESS_FS_READ_DIR (1ULL << 3)
31+
#define LANDLOCK_ACCESS_FS_REMOVE_DIR (1ULL << 4)
32+
#define LANDLOCK_ACCESS_FS_REMOVE_FILE (1ULL << 5)
33+
#define LANDLOCK_ACCESS_FS_MAKE_CHAR (1ULL << 6)
34+
#define LANDLOCK_ACCESS_FS_MAKE_DIR (1ULL << 7)
35+
#define LANDLOCK_ACCESS_FS_MAKE_REG (1ULL << 8)
36+
#define LANDLOCK_ACCESS_FS_MAKE_SOCK (1ULL << 9)
37+
#define LANDLOCK_ACCESS_FS_MAKE_FIFO (1ULL << 10)
38+
#define LANDLOCK_ACCESS_FS_MAKE_BLOCK (1ULL << 11)
39+
#define LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 12)
40+
#endif /* _LINUX_LANDLOCK_H */
41+
42+
#include <sys/syscall.h>
43+
#ifndef __NR_landlock_create_ruleset
44+
#define __NR_landlock_create_ruleset 444
45+
#endif
46+
#ifndef __NR_landlock_add_rule
47+
#define __NR_landlock_add_rule 445
48+
#endif
49+
#ifndef __NR_landlock_restrict_self
50+
#define __NR_landlock_restrict_self 446
51+
#endif
52+
53+
#include <cstddef>
54+
#include <unistd.h>
55+
#ifndef landlock_create_ruleset
56+
static inline int
57+
landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,
58+
const size_t size, const __u32 flags) {
59+
return syscall(__NR_landlock_create_ruleset, attr, size, flags);
60+
}
61+
#endif
62+
#ifndef landlock_add_rule
63+
static inline int landlock_add_rule(const int ruleset_fd,
64+
const enum landlock_rule_type rule_type,
65+
const void *const rule_attr,
66+
const __u32 flags) {
67+
return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
68+
flags);
69+
}
70+
#endif
71+
#ifndef landlock_restrict_self
72+
static inline int landlock_restrict_self(const int ruleset_fd,
73+
const __u32 flags) {
74+
return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
75+
}
76+
#endif

dmoj/cptbox/landlock_helpers.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#ifndef __FreeBSD__
2+
3+
#include <errno.h>
4+
#include <fcntl.h>
5+
#include <stdio.h>
6+
#include <string.h>
7+
8+
#include "landlock_helpers.h"
9+
10+
int landlock_add_rules(const int ruleset_fd, const char *const *const paths,
11+
__u64 access_rule) {
12+
struct landlock_path_beneath_attr path_beneath = {
13+
.allowed_access = access_rule,
14+
.parent_fd = -1,
15+
};
16+
for (const char *const *pathptr = paths; *pathptr; pathptr++) {
17+
path_beneath.parent_fd = open(*pathptr, O_PATH | O_CLOEXEC);
18+
if (path_beneath.parent_fd < 0) {
19+
if (errno == ENOENT)
20+
goto close_fd; // missing files are ignored
21+
fprintf(stderr, "Failed to open path '%s' for rule: %s\n", *pathptr,
22+
strerror(errno));
23+
return -1;
24+
}
25+
if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
26+
&path_beneath, 0)) {
27+
fprintf(stderr, "Failed to add rule '%s' to ruleset: %s\n",
28+
*pathptr, strerror(errno));
29+
return -1;
30+
}
31+
close_fd:
32+
close(path_beneath.parent_fd);
33+
}
34+
return 0;
35+
}
36+
37+
#endif

dmoj/cptbox/landlock_helpers.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#include "landlock_header.h"
2+
3+
int landlock_add_rules(const int ruleset_fd, const char *const *const paths,
4+
__u64 access_rule);

0 commit comments

Comments
 (0)