Skip to content

Commit e2644de

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

11 files changed

+273
-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: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
# include <sys/sysctl.h>
2121
# include <libprocstat.h>
2222
#else
23+
# include "landlock_helpers.h"
24+
# include "landlock_header.h"
25+
2326
// No ASLR on FreeBSD... not as of 11.0, anyway
2427
# include <sys/personality.h>
2528
# include <sys/prctl.h>
@@ -31,6 +34,7 @@
3134
# define FD_DIR "/proc/self/fd"
3235
#endif
3336

37+
3438
inline void setrlimit2(int resource, rlim_t cur, rlim_t max) {
3539
rlimit limit;
3640
limit.rlim_cur = cur;
@@ -72,14 +76,70 @@ int cptbox_child_run(const struct child_config *config) {
7276

7377
kill(getpid(), SIGSTOP);
7478

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

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

109169
if ((rc = seccomp_load(ctx))) {
110170
fprintf(stderr, "seccomp_load: %s\n", strerror(-rc));
111-
goto seccomp_fail;
171+
return PTBOX_SPAWN_FAIL_SECCOMP;
112172
}
113173

114174
seccomp_release(ctx);
@@ -141,9 +201,21 @@ int cptbox_child_run(const struct child_config *config) {
141201
execve(config->file, config->argv, config->envp);
142202
perror("execve");
143203
return PTBOX_SPAWN_FAIL_EXECVE;
204+
}
144205

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

149221
// 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: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import errno
12
import logging
23
import os
34
import sys
45
from typing import Optional, Tuple
56

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

48+
if has_landlock():
49+
self.update({sys_openat: ALLOW, sys_open: ALLOW})
50+
else:
51+
self.update(
52+
{
53+
sys_openat: self.check_file_access_at('openat', is_open=True),
54+
sys_open: self.check_file_access('open', 0, is_open=True),
55+
}
56+
)
57+
4758
self.update(
4859
{
4960
# 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),
5261
sys_faccessat: self.check_file_access_at('faccessat'),
5362
sys_access: self.check_file_access('access', 0),
5463
sys_readlink: self.check_file_access('readlink', 0),

dmoj/cptbox/landlock_header.h

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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+
#include <linux/types.h>
13+
struct landlock_ruleset_attr {
14+
__u64 handled_access_fs;
15+
};
16+
#define LANDLOCK_CREATE_RULESET_VERSION (1U << 0)
17+
enum landlock_rule_type {
18+
LANDLOCK_RULE_PATH_BENEATH = 1,
19+
};
20+
struct landlock_path_beneath_attr {
21+
__u64 allowed_access;
22+
__s32 parent_fd;
23+
} __attribute__((packed));
24+
#define LANDLOCK_ACCESS_FS_EXECUTE (1ULL << 0)
25+
#define LANDLOCK_ACCESS_FS_WRITE_FILE (1ULL << 1)
26+
#define LANDLOCK_ACCESS_FS_READ_FILE (1ULL << 2)
27+
#define LANDLOCK_ACCESS_FS_READ_DIR (1ULL << 3)
28+
#define LANDLOCK_ACCESS_FS_REMOVE_DIR (1ULL << 4)
29+
#define LANDLOCK_ACCESS_FS_REMOVE_FILE (1ULL << 5)
30+
#define LANDLOCK_ACCESS_FS_MAKE_CHAR (1ULL << 6)
31+
#define LANDLOCK_ACCESS_FS_MAKE_DIR (1ULL << 7)
32+
#define LANDLOCK_ACCESS_FS_MAKE_REG (1ULL << 8)
33+
#define LANDLOCK_ACCESS_FS_MAKE_SOCK (1ULL << 9)
34+
#define LANDLOCK_ACCESS_FS_MAKE_FIFO (1ULL << 10)
35+
#define LANDLOCK_ACCESS_FS_MAKE_BLOCK (1ULL << 11)
36+
#define LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 12)
37+
#endif /* _LINUX_LANDLOCK_H */
38+
39+
#include <sys/syscall.h>
40+
#ifndef __NR_landlock_create_ruleset
41+
#define __NR_landlock_create_ruleset 444
42+
#endif
43+
#ifndef __NR_landlock_add_rule
44+
#define __NR_landlock_add_rule 445
45+
#endif
46+
#ifndef __NR_landlock_restrict_self
47+
#define __NR_landlock_restrict_self 446
48+
#endif
49+
50+
#include <cstddef>
51+
#include <unistd.h>
52+
#ifndef landlock_create_ruleset
53+
static inline int
54+
landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,
55+
const size_t size, const __u32 flags) {
56+
return syscall(__NR_landlock_create_ruleset, attr, size, flags);
57+
}
58+
#endif
59+
#ifndef landlock_add_rule
60+
static inline int landlock_add_rule(const int ruleset_fd,
61+
const enum landlock_rule_type rule_type,
62+
const void *const rule_attr,
63+
const __u32 flags) {
64+
return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
65+
flags);
66+
}
67+
#endif
68+
#ifndef landlock_restrict_self
69+
static inline int landlock_restrict_self(const int ruleset_fd,
70+
const __u32 flags) {
71+
return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
72+
}
73+
#endif

dmoj/cptbox/landlock_helpers.cpp

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