landlock 0.2 → 0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +40 -1
- data/README.md +44 -43
- data/benchmark/landlock_overhead.rb +9 -30
- data/ext/landlock/bin/safe_exec_helper.c +453 -200
- data/ext/landlock/extconf.rb +4 -1
- data/ext/landlock/landlock.c +34 -10
- data/ext/landlock/landlock_native.h +30 -29
- data/ext/landlock/seccomp_deny_network.h +176 -0
- data/lib/landlock/env.rb +31 -0
- data/lib/landlock/errors.rb +32 -0
- data/lib/landlock/execution.rb +238 -0
- data/lib/landlock/native.rb +38 -0
- data/lib/landlock/policy.rb +164 -0
- data/lib/landlock/process_io.rb +249 -0
- data/lib/landlock/result.rb +43 -0
- data/lib/landlock/rights.rb +48 -0
- data/lib/landlock/rlimits.rb +40 -0
- data/lib/landlock/runner/fork.rb +171 -0
- data/lib/landlock/runner/native.rb +235 -0
- data/lib/landlock/runner.rb +28 -0
- data/lib/landlock/validation.rb +59 -0
- data/lib/landlock/version.rb +1 -1
- data/lib/landlock.rb +25 -246
- metadata +52 -11
- data/lib/landlock/safe_exec.rb +0 -522
|
@@ -1,28 +1,15 @@
|
|
|
1
1
|
#include "../landlock_native.h"
|
|
2
|
+
#include "../seccomp_deny_network.h"
|
|
2
3
|
|
|
3
4
|
#include <stdio.h>
|
|
4
5
|
#include <stdlib.h>
|
|
5
6
|
#include <string.h>
|
|
6
7
|
#include <limits.h>
|
|
8
|
+
#include <ctype.h>
|
|
9
|
+
#include <dirent.h>
|
|
7
10
|
#include <sys/resource.h>
|
|
8
11
|
#include <sys/stat.h>
|
|
9
12
|
|
|
10
|
-
#ifdef __linux__
|
|
11
|
-
#include <linux/audit.h>
|
|
12
|
-
#include <linux/filter.h>
|
|
13
|
-
#include <linux/seccomp.h>
|
|
14
|
-
#endif
|
|
15
|
-
|
|
16
|
-
#ifndef SECCOMP_RET_ALLOW
|
|
17
|
-
#define SECCOMP_RET_ALLOW 0x7fff0000U
|
|
18
|
-
#endif
|
|
19
|
-
#ifndef SECCOMP_RET_ERRNO
|
|
20
|
-
#define SECCOMP_RET_ERRNO 0x00050000U
|
|
21
|
-
#endif
|
|
22
|
-
#ifndef SECCOMP_SET_MODE_FILTER
|
|
23
|
-
#define SECCOMP_SET_MODE_FILTER 1
|
|
24
|
-
#endif
|
|
25
|
-
|
|
26
13
|
typedef struct {
|
|
27
14
|
char **items;
|
|
28
15
|
size_t len;
|
|
@@ -35,21 +22,47 @@ typedef struct {
|
|
|
35
22
|
size_t cap;
|
|
36
23
|
} ull_list;
|
|
37
24
|
|
|
25
|
+
typedef struct {
|
|
26
|
+
char *path;
|
|
27
|
+
uint64_t rights;
|
|
28
|
+
} path_rule;
|
|
29
|
+
|
|
30
|
+
typedef struct {
|
|
31
|
+
path_rule *items;
|
|
32
|
+
size_t len;
|
|
33
|
+
size_t cap;
|
|
34
|
+
} path_rule_list;
|
|
35
|
+
|
|
36
|
+
#define MAX_CSV_RIGHTS 64U
|
|
37
|
+
|
|
38
38
|
static void die(const char *message) {
|
|
39
39
|
perror(message);
|
|
40
40
|
_exit(126);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
static void die_path(const char *message, const char *path) {
|
|
44
|
+
int saved_errno = errno;
|
|
45
|
+
fprintf(stderr, "landlock-safe-exec: %s %s: %s\n", message, path, strerror(saved_errno));
|
|
46
|
+
_exit(126);
|
|
47
|
+
}
|
|
48
|
+
|
|
43
49
|
static void die_msg(const char *message) {
|
|
44
50
|
fprintf(stderr, "landlock-safe-exec: %s\n", message);
|
|
45
51
|
_exit(126);
|
|
46
52
|
}
|
|
47
53
|
|
|
54
|
+
static void die_no_effective_path_rights(const char *path) {
|
|
55
|
+
fprintf(stderr, "landlock-safe-exec: path rule has no effective rights: %s\n", path);
|
|
56
|
+
_exit(126);
|
|
57
|
+
}
|
|
58
|
+
|
|
48
59
|
static void string_list_push(string_list *list, char *value) {
|
|
49
60
|
if (list->len == list->cap) {
|
|
50
61
|
size_t cap = list->cap ? list->cap * 2 : 8;
|
|
51
62
|
char **items = realloc(list->items, cap * sizeof(char *));
|
|
52
|
-
if (!items)
|
|
63
|
+
if (!items) {
|
|
64
|
+
die("realloc");
|
|
65
|
+
}
|
|
53
66
|
list->items = items;
|
|
54
67
|
list->cap = cap;
|
|
55
68
|
}
|
|
@@ -60,54 +73,97 @@ static void ull_list_push(ull_list *list, unsigned long long value) {
|
|
|
60
73
|
if (list->len == list->cap) {
|
|
61
74
|
size_t cap = list->cap ? list->cap * 2 : 8;
|
|
62
75
|
unsigned long long *items = realloc(list->items, cap * sizeof(unsigned long long));
|
|
63
|
-
if (!items)
|
|
76
|
+
if (!items) {
|
|
77
|
+
die("realloc");
|
|
78
|
+
}
|
|
64
79
|
list->items = items;
|
|
65
80
|
list->cap = cap;
|
|
66
81
|
}
|
|
67
82
|
list->items[list->len++] = value;
|
|
68
83
|
}
|
|
69
84
|
|
|
85
|
+
static void path_rule_list_push(path_rule_list *list, char *path, uint64_t rights) {
|
|
86
|
+
if (list->len == list->cap) {
|
|
87
|
+
size_t cap = list->cap ? list->cap * 2 : 8;
|
|
88
|
+
path_rule *items = realloc(list->items, cap * sizeof(path_rule));
|
|
89
|
+
if (!items) {
|
|
90
|
+
die("realloc");
|
|
91
|
+
}
|
|
92
|
+
list->items = items;
|
|
93
|
+
list->cap = cap;
|
|
94
|
+
}
|
|
95
|
+
list->items[list->len].path = path;
|
|
96
|
+
list->items[list->len].rights = rights;
|
|
97
|
+
list->len++;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* An empty value marks the class handled while granting nothing; a real path
|
|
101
|
+
or port is never the empty string, so the two cannot collide. */
|
|
102
|
+
static void push_unless_empty(string_list *list, char *value, int *handled) {
|
|
103
|
+
*handled = 1;
|
|
104
|
+
if (value[0] != '\0') {
|
|
105
|
+
string_list_push(list, value);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
70
109
|
static unsigned long long parse_ull(const char *value, const char *name) {
|
|
71
|
-
if (!value || value[0] == '\0' || value[0] == '-'
|
|
110
|
+
if (!value || value[0] == '\0' || value[0] == '-' || value[0] == '+' ||
|
|
111
|
+
isspace((unsigned char)value[0])) {
|
|
112
|
+
die_msg(name);
|
|
113
|
+
}
|
|
72
114
|
|
|
73
115
|
errno = 0;
|
|
74
116
|
char *end = NULL;
|
|
75
117
|
unsigned long long parsed = strtoull(value, &end, 10);
|
|
76
|
-
if (errno == ERANGE || !end || *end != '\0')
|
|
118
|
+
if (errno == ERANGE || !end || *end != '\0') {
|
|
119
|
+
die_msg(name);
|
|
120
|
+
}
|
|
77
121
|
|
|
78
122
|
return parsed;
|
|
79
123
|
}
|
|
80
124
|
|
|
81
125
|
static unsigned long long parse_port(const char *value) {
|
|
82
126
|
unsigned long long port = parse_ull(value, "TCP port must be an integer between 0 and 65535");
|
|
83
|
-
if (port > 65535ULL)
|
|
127
|
+
if (port > 65535ULL) {
|
|
128
|
+
die_msg("TCP port must be between 0 and 65535");
|
|
129
|
+
}
|
|
84
130
|
return port;
|
|
85
131
|
}
|
|
86
132
|
|
|
133
|
+
static void push_port_unless_empty(ull_list *list, const char *value, int *handled) {
|
|
134
|
+
*handled = 1;
|
|
135
|
+
if (value[0] != '\0') {
|
|
136
|
+
ull_list_push(list, parse_port(value));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
87
140
|
static int abi_version(void) {
|
|
88
141
|
long abi = ll_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
|
|
89
|
-
if (abi < 0 && (errno == ENOSYS || errno == EOPNOTSUPP))
|
|
90
|
-
|
|
142
|
+
if (abi < 0 && (errno == ENOSYS || errno == EOPNOTSUPP)) {
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
if (abi < 0) {
|
|
146
|
+
die("landlock_create_ruleset(version)");
|
|
147
|
+
}
|
|
91
148
|
return (int)abi;
|
|
92
149
|
}
|
|
93
150
|
|
|
94
151
|
static uint64_t known_fs_rights_for_abi(int abi) {
|
|
95
|
-
uint64_t rights =
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (abi >= 5) rights |= LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
|
152
|
+
uint64_t rights =
|
|
153
|
+
LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_READ_FILE |
|
|
154
|
+
LANDLOCK_ACCESS_FS_READ_DIR | LANDLOCK_ACCESS_FS_REMOVE_DIR | LANDLOCK_ACCESS_FS_REMOVE_FILE |
|
|
155
|
+
LANDLOCK_ACCESS_FS_MAKE_CHAR | LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG |
|
|
156
|
+
LANDLOCK_ACCESS_FS_MAKE_SOCK | LANDLOCK_ACCESS_FS_MAKE_FIFO | LANDLOCK_ACCESS_FS_MAKE_BLOCK |
|
|
157
|
+
LANDLOCK_ACCESS_FS_MAKE_SYM;
|
|
158
|
+
if (abi >= 2) {
|
|
159
|
+
rights |= LANDLOCK_ACCESS_FS_REFER;
|
|
160
|
+
}
|
|
161
|
+
if (abi >= 3) {
|
|
162
|
+
rights |= LANDLOCK_ACCESS_FS_TRUNCATE;
|
|
163
|
+
}
|
|
164
|
+
if (abi >= 5) {
|
|
165
|
+
rights |= LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
|
166
|
+
}
|
|
111
167
|
return rights;
|
|
112
168
|
}
|
|
113
169
|
|
|
@@ -120,24 +176,54 @@ static uint64_t execute_rights(void) {
|
|
|
120
176
|
}
|
|
121
177
|
|
|
122
178
|
static uint64_t write_rights(int abi) {
|
|
123
|
-
|
|
179
|
+
uint64_t rights = LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_READ_FILE |
|
|
180
|
+
LANDLOCK_ACCESS_FS_READ_DIR | LANDLOCK_ACCESS_FS_REMOVE_DIR |
|
|
181
|
+
LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_MAKE_CHAR |
|
|
182
|
+
LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG |
|
|
183
|
+
LANDLOCK_ACCESS_FS_MAKE_SOCK | LANDLOCK_ACCESS_FS_MAKE_FIFO |
|
|
184
|
+
LANDLOCK_ACCESS_FS_MAKE_BLOCK | LANDLOCK_ACCESS_FS_MAKE_SYM;
|
|
185
|
+
if (abi >= 2) {
|
|
186
|
+
rights |= LANDLOCK_ACCESS_FS_REFER;
|
|
187
|
+
}
|
|
188
|
+
if (abi >= 3) {
|
|
189
|
+
rights |= LANDLOCK_ACCESS_FS_TRUNCATE;
|
|
190
|
+
}
|
|
191
|
+
return rights;
|
|
124
192
|
}
|
|
125
193
|
|
|
126
194
|
static uint64_t file_path_rights(void) {
|
|
127
|
-
return LANDLOCK_ACCESS_FS_EXECUTE |
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
195
|
+
return LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_READ_FILE |
|
|
196
|
+
LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
static uint64_t effective_path_rights(const char *path, uint64_t rights) {
|
|
200
|
+
struct stat st;
|
|
201
|
+
if (stat(path, &st) != 0) {
|
|
202
|
+
die_path("stat(path rule)", path);
|
|
203
|
+
}
|
|
204
|
+
if (!S_ISDIR(st.st_mode)) {
|
|
205
|
+
rights &= file_path_rights();
|
|
206
|
+
}
|
|
207
|
+
return rights;
|
|
132
208
|
}
|
|
133
209
|
|
|
134
210
|
static void add_path_rule(int fd, const char *path, uint64_t rights) {
|
|
135
211
|
int parent_fd = open(path, O_PATH | O_CLOEXEC);
|
|
136
|
-
if (parent_fd < 0)
|
|
212
|
+
if (parent_fd < 0) {
|
|
213
|
+
die_path("open(path rule)", path);
|
|
214
|
+
}
|
|
137
215
|
|
|
138
216
|
struct stat st;
|
|
139
|
-
if (fstat(parent_fd, &st) != 0)
|
|
140
|
-
|
|
217
|
+
if (fstat(parent_fd, &st) != 0) {
|
|
218
|
+
die_path("fstat(path rule)", path);
|
|
219
|
+
}
|
|
220
|
+
if (!S_ISDIR(st.st_mode)) {
|
|
221
|
+
rights &= file_path_rights();
|
|
222
|
+
}
|
|
223
|
+
if (!rights) {
|
|
224
|
+
close(parent_fd);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
141
227
|
|
|
142
228
|
struct rb_landlock_path_beneath_attr rule;
|
|
143
229
|
memset(&rule, 0, sizeof(rule));
|
|
@@ -154,216 +240,383 @@ static void add_path_rule(int fd, const char *path, uint64_t rights) {
|
|
|
154
240
|
}
|
|
155
241
|
|
|
156
242
|
static void add_net_rule(int fd, unsigned long long port, uint64_t rights) {
|
|
157
|
-
if (port > 65535ULL)
|
|
243
|
+
if (port > 65535ULL) {
|
|
244
|
+
die_msg("TCP port must be between 0 and 65535");
|
|
245
|
+
}
|
|
158
246
|
struct rb_landlock_net_port_attr rule;
|
|
159
247
|
memset(&rule, 0, sizeof(rule));
|
|
160
248
|
rule.allowed_access = rights;
|
|
161
249
|
rule.port = port;
|
|
162
|
-
if (ll_add_rule(fd, LANDLOCK_RULE_NET_PORT, &rule, 0) < 0)
|
|
250
|
+
if (ll_add_rule(fd, LANDLOCK_RULE_NET_PORT, &rule, 0) < 0) {
|
|
251
|
+
die("landlock_add_rule(net_port)");
|
|
252
|
+
}
|
|
163
253
|
}
|
|
164
254
|
|
|
165
|
-
static void apply_landlock(string_list *read_paths, string_list *write_paths,
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
255
|
+
static void apply_landlock(string_list *read_paths, string_list *write_paths,
|
|
256
|
+
string_list *execute_paths, path_rule_list *path_rules,
|
|
257
|
+
ull_list *connect_ports, ull_list *bind_ports, uint64_t scoped,
|
|
258
|
+
int allow_all_known, int handled_read, int handled_write,
|
|
259
|
+
int handled_execute, int handled_connect, int handled_bind) {
|
|
260
|
+
int need_fs = handled_read || handled_write || handled_execute || path_rules->len ||
|
|
261
|
+
allow_all_known;
|
|
262
|
+
int need_net = handled_connect || handled_bind;
|
|
263
|
+
int need_scope = scoped != 0;
|
|
264
|
+
if (!need_fs && !need_net && !need_scope) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
170
267
|
|
|
171
268
|
int abi = abi_version();
|
|
172
|
-
if (abi <= 0)
|
|
173
|
-
|
|
269
|
+
if (abi <= 0) {
|
|
270
|
+
die_msg("Linux Landlock is unavailable");
|
|
271
|
+
}
|
|
272
|
+
if (need_net && abi < 4) {
|
|
273
|
+
die_msg("Landlock network rules require ABI v4+");
|
|
274
|
+
}
|
|
275
|
+
if (need_scope && abi < 6) {
|
|
276
|
+
die_msg("Landlock scopes require ABI v6+");
|
|
277
|
+
}
|
|
174
278
|
|
|
175
|
-
uint64_t
|
|
279
|
+
uint64_t known_fs_rights = known_fs_rights_for_abi(abi);
|
|
280
|
+
uint64_t fs_handled = allow_all_known ? known_fs_rights : 0;
|
|
176
281
|
if (!allow_all_known) {
|
|
177
|
-
if (
|
|
178
|
-
|
|
179
|
-
|
|
282
|
+
if (handled_read) {
|
|
283
|
+
fs_handled |= read_rights();
|
|
284
|
+
}
|
|
285
|
+
if (handled_execute) {
|
|
286
|
+
fs_handled |= execute_rights();
|
|
287
|
+
}
|
|
288
|
+
if (handled_write) {
|
|
289
|
+
fs_handled |= write_rights(abi);
|
|
290
|
+
}
|
|
291
|
+
for (size_t i = 0; i < path_rules->len; i++) {
|
|
292
|
+
uint64_t rights = effective_path_rights(path_rules->items[i].path,
|
|
293
|
+
path_rules->items[i].rights & known_fs_rights);
|
|
294
|
+
if (!rights) {
|
|
295
|
+
die_no_effective_path_rights(path_rules->items[i].path);
|
|
296
|
+
}
|
|
297
|
+
fs_handled |= rights;
|
|
298
|
+
}
|
|
180
299
|
}
|
|
181
300
|
uint64_t net_handled = 0;
|
|
182
|
-
if (
|
|
183
|
-
|
|
301
|
+
if (handled_bind) {
|
|
302
|
+
net_handled |= LANDLOCK_ACCESS_NET_BIND_TCP;
|
|
303
|
+
}
|
|
304
|
+
if (handled_connect) {
|
|
305
|
+
net_handled |= LANDLOCK_ACCESS_NET_CONNECT_TCP;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!fs_handled && !net_handled && !scoped) {
|
|
309
|
+
die_msg("empty Landlock policy: provide filesystem paths, TCP ports, or scopes");
|
|
310
|
+
}
|
|
184
311
|
|
|
185
312
|
struct rb_landlock_ruleset_attr attr;
|
|
186
313
|
memset(&attr, 0, sizeof(attr));
|
|
187
314
|
attr.handled_access_fs = fs_handled;
|
|
188
315
|
attr.handled_access_net = net_handled;
|
|
316
|
+
attr.scoped = scoped;
|
|
189
317
|
|
|
190
|
-
size_t attr_size =
|
|
318
|
+
size_t attr_size =
|
|
319
|
+
scoped ? sizeof(struct rb_landlock_ruleset_attr)
|
|
320
|
+
: (net_handled ? offsetof(struct rb_landlock_ruleset_attr, scoped)
|
|
321
|
+
: offsetof(struct rb_landlock_ruleset_attr, handled_access_net));
|
|
191
322
|
int fd = (int)ll_create_ruleset(&attr, attr_size, 0);
|
|
192
|
-
if (fd < 0)
|
|
323
|
+
if (fd < 0) {
|
|
324
|
+
die("landlock_create_ruleset");
|
|
325
|
+
}
|
|
193
326
|
|
|
194
|
-
for (size_t i = 0; i < read_paths->len; i++)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
for (size_t i = 0; i <
|
|
198
|
-
|
|
327
|
+
for (size_t i = 0; i < read_paths->len; i++) {
|
|
328
|
+
add_path_rule(fd, read_paths->items[i], read_rights());
|
|
329
|
+
}
|
|
330
|
+
for (size_t i = 0; i < execute_paths->len; i++) {
|
|
331
|
+
add_path_rule(fd, execute_paths->items[i], execute_rights());
|
|
332
|
+
}
|
|
333
|
+
for (size_t i = 0; i < write_paths->len; i++) {
|
|
334
|
+
add_path_rule(fd, write_paths->items[i], write_rights(abi));
|
|
335
|
+
}
|
|
336
|
+
for (size_t i = 0; i < path_rules->len; i++) {
|
|
337
|
+
uint64_t rights = effective_path_rights(path_rules->items[i].path,
|
|
338
|
+
path_rules->items[i].rights & known_fs_rights);
|
|
339
|
+
if (!rights) {
|
|
340
|
+
die_no_effective_path_rights(path_rules->items[i].path);
|
|
341
|
+
}
|
|
342
|
+
add_path_rule(fd, path_rules->items[i].path, rights);
|
|
343
|
+
}
|
|
344
|
+
for (size_t i = 0; i < connect_ports->len; i++) {
|
|
345
|
+
add_net_rule(fd, connect_ports->items[i], LANDLOCK_ACCESS_NET_CONNECT_TCP);
|
|
346
|
+
}
|
|
347
|
+
for (size_t i = 0; i < bind_ports->len; i++) {
|
|
348
|
+
add_net_rule(fd, bind_ports->items[i], LANDLOCK_ACCESS_NET_BIND_TCP);
|
|
349
|
+
}
|
|
199
350
|
|
|
200
|
-
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0)
|
|
201
|
-
|
|
351
|
+
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) {
|
|
352
|
+
die("prctl(PR_SET_NO_NEW_PRIVS)");
|
|
353
|
+
}
|
|
354
|
+
if (ll_restrict_self(fd, 0) < 0) {
|
|
355
|
+
die("landlock_restrict_self");
|
|
356
|
+
}
|
|
202
357
|
close(fd);
|
|
203
358
|
}
|
|
204
359
|
|
|
205
360
|
static void apply_rlimit(const char *spec) {
|
|
206
361
|
char *copy = strdup(spec);
|
|
207
|
-
if (!copy)
|
|
362
|
+
if (!copy) {
|
|
363
|
+
die("strdup");
|
|
364
|
+
}
|
|
208
365
|
char *eq = strchr(copy, '=');
|
|
209
|
-
if (!eq)
|
|
366
|
+
if (!eq) {
|
|
367
|
+
die_msg("rlimit must be name=value");
|
|
368
|
+
}
|
|
210
369
|
*eq = '\0';
|
|
211
370
|
unsigned long long value = parse_ull(eq + 1, "rlimit value must be a non-negative integer");
|
|
212
371
|
int resource = -1;
|
|
213
372
|
|
|
214
|
-
if (strcmp(copy, "cpu_seconds") == 0)
|
|
373
|
+
if (strcmp(copy, "cpu_seconds") == 0) {
|
|
374
|
+
resource = RLIMIT_CPU;
|
|
375
|
+
}
|
|
215
376
|
#ifdef RLIMIT_AS
|
|
216
|
-
else if (strcmp(copy, "memory_bytes") == 0)
|
|
377
|
+
else if (strcmp(copy, "memory_bytes") == 0) {
|
|
378
|
+
resource = RLIMIT_AS;
|
|
379
|
+
}
|
|
217
380
|
#endif
|
|
218
|
-
else if (strcmp(copy, "file_size_bytes") == 0)
|
|
219
|
-
|
|
381
|
+
else if (strcmp(copy, "file_size_bytes") == 0) {
|
|
382
|
+
resource = RLIMIT_FSIZE;
|
|
383
|
+
} else if (strcmp(copy, "open_files") == 0) {
|
|
384
|
+
resource = RLIMIT_NOFILE;
|
|
385
|
+
}
|
|
220
386
|
#ifdef RLIMIT_NPROC
|
|
221
|
-
else if (strcmp(copy, "processes") == 0)
|
|
387
|
+
else if (strcmp(copy, "processes") == 0) {
|
|
388
|
+
resource = RLIMIT_NPROC;
|
|
389
|
+
}
|
|
222
390
|
#endif
|
|
223
|
-
else
|
|
391
|
+
else {
|
|
392
|
+
die_msg("unknown rlimit");
|
|
393
|
+
}
|
|
224
394
|
|
|
225
395
|
struct rlimit limit;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
396
|
+
rlim_t rlim_value = (rlim_t)value;
|
|
397
|
+
if ((unsigned long long)rlim_value != value) {
|
|
398
|
+
die_msg("rlimit value is too large for this platform");
|
|
399
|
+
}
|
|
400
|
+
limit.rlim_cur = rlim_value;
|
|
401
|
+
limit.rlim_max = rlim_value;
|
|
402
|
+
if (setrlimit(resource, &limit) != 0) {
|
|
403
|
+
die("setrlimit");
|
|
404
|
+
}
|
|
229
405
|
free(copy);
|
|
230
406
|
}
|
|
231
407
|
|
|
232
|
-
static
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
#endif
|
|
239
|
-
#ifdef __NR_connect
|
|
240
|
-
__NR_connect,
|
|
241
|
-
#endif
|
|
242
|
-
#ifdef __NR_bind
|
|
243
|
-
__NR_bind,
|
|
244
|
-
#endif
|
|
245
|
-
#ifdef __NR_listen
|
|
246
|
-
__NR_listen,
|
|
247
|
-
#endif
|
|
248
|
-
#ifdef __NR_accept
|
|
249
|
-
__NR_accept,
|
|
250
|
-
#endif
|
|
251
|
-
#ifdef __NR_accept4
|
|
252
|
-
__NR_accept4,
|
|
253
|
-
#endif
|
|
254
|
-
#ifdef __NR_sendto
|
|
255
|
-
__NR_sendto,
|
|
256
|
-
#endif
|
|
257
|
-
#ifdef __NR_sendmsg
|
|
258
|
-
__NR_sendmsg,
|
|
259
|
-
#endif
|
|
260
|
-
#ifdef __NR_sendmmsg
|
|
261
|
-
__NR_sendmmsg,
|
|
262
|
-
#endif
|
|
263
|
-
#ifdef __NR_recvfrom
|
|
264
|
-
__NR_recvfrom,
|
|
265
|
-
#endif
|
|
266
|
-
#ifdef __NR_recvmsg
|
|
267
|
-
__NR_recvmsg,
|
|
268
|
-
#endif
|
|
269
|
-
#ifdef __NR_recvmmsg
|
|
270
|
-
__NR_recvmmsg,
|
|
271
|
-
#endif
|
|
272
|
-
#ifdef __NR_socketcall
|
|
273
|
-
__NR_socketcall,
|
|
274
|
-
#endif
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
#if defined(__x86_64__) && defined(AUDIT_ARCH_X86_64)
|
|
278
|
-
#define EXPECTED_AUDIT_ARCH AUDIT_ARCH_X86_64
|
|
279
|
-
#elif defined(__aarch64__) && defined(AUDIT_ARCH_AARCH64)
|
|
280
|
-
#define EXPECTED_AUDIT_ARCH AUDIT_ARCH_AARCH64
|
|
281
|
-
#elif defined(__i386__) && defined(AUDIT_ARCH_I386)
|
|
282
|
-
#define EXPECTED_AUDIT_ARCH AUDIT_ARCH_I386
|
|
283
|
-
#endif
|
|
408
|
+
static void apply_seccomp_deny_network(void) {
|
|
409
|
+
const char *error_message = "seccomp(SECCOMP_SET_MODE_FILTER)";
|
|
410
|
+
if (rb_landlock_seccomp_deny_network(&error_message) != 0) {
|
|
411
|
+
die(error_message);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
284
414
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
415
|
+
static uint64_t fs_right_name(const char *name) {
|
|
416
|
+
if (strcmp(name, "execute") == 0) {
|
|
417
|
+
return LANDLOCK_ACCESS_FS_EXECUTE;
|
|
418
|
+
}
|
|
419
|
+
if (strcmp(name, "write_file") == 0) {
|
|
420
|
+
return LANDLOCK_ACCESS_FS_WRITE_FILE;
|
|
421
|
+
}
|
|
422
|
+
if (strcmp(name, "read_file") == 0) {
|
|
423
|
+
return LANDLOCK_ACCESS_FS_READ_FILE;
|
|
424
|
+
}
|
|
425
|
+
if (strcmp(name, "read_dir") == 0) {
|
|
426
|
+
return LANDLOCK_ACCESS_FS_READ_DIR;
|
|
427
|
+
}
|
|
428
|
+
if (strcmp(name, "remove_dir") == 0) {
|
|
429
|
+
return LANDLOCK_ACCESS_FS_REMOVE_DIR;
|
|
430
|
+
}
|
|
431
|
+
if (strcmp(name, "remove_file") == 0) {
|
|
432
|
+
return LANDLOCK_ACCESS_FS_REMOVE_FILE;
|
|
433
|
+
}
|
|
434
|
+
if (strcmp(name, "make_char") == 0) {
|
|
435
|
+
return LANDLOCK_ACCESS_FS_MAKE_CHAR;
|
|
436
|
+
}
|
|
437
|
+
if (strcmp(name, "make_dir") == 0) {
|
|
438
|
+
return LANDLOCK_ACCESS_FS_MAKE_DIR;
|
|
439
|
+
}
|
|
440
|
+
if (strcmp(name, "make_reg") == 0) {
|
|
441
|
+
return LANDLOCK_ACCESS_FS_MAKE_REG;
|
|
442
|
+
}
|
|
443
|
+
if (strcmp(name, "make_sock") == 0) {
|
|
444
|
+
return LANDLOCK_ACCESS_FS_MAKE_SOCK;
|
|
445
|
+
}
|
|
446
|
+
if (strcmp(name, "make_fifo") == 0) {
|
|
447
|
+
return LANDLOCK_ACCESS_FS_MAKE_FIFO;
|
|
448
|
+
}
|
|
449
|
+
if (strcmp(name, "make_block") == 0) {
|
|
450
|
+
return LANDLOCK_ACCESS_FS_MAKE_BLOCK;
|
|
451
|
+
}
|
|
452
|
+
if (strcmp(name, "make_sym") == 0) {
|
|
453
|
+
return LANDLOCK_ACCESS_FS_MAKE_SYM;
|
|
454
|
+
}
|
|
455
|
+
if (strcmp(name, "refer") == 0) {
|
|
456
|
+
return LANDLOCK_ACCESS_FS_REFER;
|
|
457
|
+
}
|
|
458
|
+
if (strcmp(name, "truncate") == 0) {
|
|
459
|
+
return LANDLOCK_ACCESS_FS_TRUNCATE;
|
|
460
|
+
}
|
|
461
|
+
if (strcmp(name, "ioctl_dev") == 0) {
|
|
462
|
+
return LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
|
463
|
+
}
|
|
464
|
+
die_msg("unknown filesystem right");
|
|
465
|
+
return 0;
|
|
466
|
+
}
|
|
288
467
|
|
|
289
|
-
static
|
|
290
|
-
|
|
291
|
-
if (
|
|
468
|
+
static uint64_t parse_fs_rights(const char *spec) {
|
|
469
|
+
char *copy = strdup(spec);
|
|
470
|
+
if (!copy) {
|
|
471
|
+
die("strdup");
|
|
472
|
+
}
|
|
292
473
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
for (size_t i = 0; i < count; i++) {
|
|
308
|
-
filter[pc++] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (unsigned int)deny_syscalls[i], 0, 1);
|
|
309
|
-
filter[pc++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | EPERM);
|
|
474
|
+
uint64_t rights = 0;
|
|
475
|
+
size_t count = 0;
|
|
476
|
+
char *saveptr = NULL;
|
|
477
|
+
for (char *name = strtok_r(copy, ",", &saveptr); name; name = strtok_r(NULL, ",", &saveptr)) {
|
|
478
|
+
if (name[0] == '\0') {
|
|
479
|
+
free(copy);
|
|
480
|
+
die_msg("empty filesystem right");
|
|
481
|
+
}
|
|
482
|
+
rights |= fs_right_name(name);
|
|
483
|
+
count++;
|
|
484
|
+
if (count > MAX_CSV_RIGHTS) {
|
|
485
|
+
free(copy);
|
|
486
|
+
die_msg("too many filesystem rights");
|
|
487
|
+
}
|
|
310
488
|
}
|
|
311
|
-
filter[pc++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
|
|
312
489
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
490
|
+
free(copy);
|
|
491
|
+
if (!count) {
|
|
492
|
+
die_msg("path rights must not be empty");
|
|
493
|
+
}
|
|
494
|
+
return rights;
|
|
495
|
+
}
|
|
316
496
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
#endif
|
|
321
|
-
{
|
|
322
|
-
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) != 0) die("seccomp(SECCOMP_SET_MODE_FILTER)");
|
|
497
|
+
static uint64_t scope_name(const char *name) {
|
|
498
|
+
if (strcmp(name, "abstract_unix_socket") == 0) {
|
|
499
|
+
return LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
|
|
323
500
|
}
|
|
324
|
-
|
|
501
|
+
if (strcmp(name, "signal") == 0) {
|
|
502
|
+
return LANDLOCK_SCOPE_SIGNAL;
|
|
503
|
+
}
|
|
504
|
+
die_msg("unknown Landlock scope");
|
|
505
|
+
return 0;
|
|
325
506
|
}
|
|
326
507
|
|
|
327
508
|
static char *require_arg(int argc, char **argv, int *i) {
|
|
328
|
-
if (*i + 1 >= argc)
|
|
509
|
+
if (*i + 1 >= argc) {
|
|
510
|
+
die_msg("missing option argument");
|
|
511
|
+
}
|
|
329
512
|
(*i)++;
|
|
330
513
|
return argv[*i];
|
|
331
514
|
}
|
|
332
515
|
|
|
516
|
+
static void close_inherited_fds(void) {
|
|
517
|
+
#ifdef SYS_close_range
|
|
518
|
+
if (syscall(SYS_close_range, 3U, ~0U, 0U) == 0) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
#endif
|
|
522
|
+
|
|
523
|
+
DIR *dir = opendir("/proc/self/fd");
|
|
524
|
+
if (dir) {
|
|
525
|
+
int dir_fd = dirfd(dir);
|
|
526
|
+
struct dirent *entry;
|
|
527
|
+
while ((entry = readdir(dir)) != NULL) {
|
|
528
|
+
char *end = NULL;
|
|
529
|
+
errno = 0;
|
|
530
|
+
long fd = strtol(entry->d_name, &end, 10);
|
|
531
|
+
if (errno == 0 && end && *end == '\0' && fd >= 3 && fd != dir_fd) {
|
|
532
|
+
close((int)fd);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
closedir(dir);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
long max_fd = sysconf(_SC_OPEN_MAX);
|
|
540
|
+
if (max_fd < 0) {
|
|
541
|
+
max_fd = 1024;
|
|
542
|
+
}
|
|
543
|
+
for (long fd = 3; fd < max_fd; fd++) {
|
|
544
|
+
close((int)fd);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
333
548
|
int main(int argc, char **argv) {
|
|
334
|
-
string_list read_paths = {0}, write_paths = {0}, execute_paths = {0},
|
|
549
|
+
string_list read_paths = {0}, write_paths = {0}, execute_paths = {0}, rlimit_specs = {0};
|
|
550
|
+
path_rule_list path_rules = {0};
|
|
335
551
|
ull_list connect_ports = {0}, bind_ports = {0};
|
|
336
|
-
int
|
|
552
|
+
int seccomp_deny_network = 0, allow_all_known = 0, close_others = 1;
|
|
553
|
+
int handled_read = 0, handled_write = 0, handled_execute = 0;
|
|
554
|
+
int handled_connect = 0, handled_bind = 0;
|
|
555
|
+
uint64_t scoped = 0;
|
|
337
556
|
char *chdir_path = NULL;
|
|
557
|
+
char **command_argv = NULL;
|
|
338
558
|
int command_index = -1;
|
|
339
559
|
|
|
340
560
|
for (int i = 1; i < argc; i++) {
|
|
341
|
-
if (strcmp(argv[i], "--") == 0) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
else if (strcmp(argv[i], "--
|
|
348
|
-
|
|
349
|
-
else if (strcmp(argv[i], "--
|
|
350
|
-
|
|
351
|
-
else if (strcmp(argv[i], "--
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
561
|
+
if (strcmp(argv[i], "--") == 0) {
|
|
562
|
+
command_index = i + 1;
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
if (strcmp(argv[i], "--read") == 0) {
|
|
566
|
+
push_unless_empty(&read_paths, require_arg(argc, argv, &i), &handled_read);
|
|
567
|
+
} else if (strcmp(argv[i], "--write") == 0) {
|
|
568
|
+
push_unless_empty(&write_paths, require_arg(argc, argv, &i), &handled_write);
|
|
569
|
+
} else if (strcmp(argv[i], "--execute") == 0) {
|
|
570
|
+
push_unless_empty(&execute_paths, require_arg(argc, argv, &i), &handled_execute);
|
|
571
|
+
} else if (strcmp(argv[i], "--path") == 0) {
|
|
572
|
+
char *path = require_arg(argc, argv, &i);
|
|
573
|
+
char *rights = require_arg(argc, argv, &i);
|
|
574
|
+
path_rule_list_push(&path_rules, path, parse_fs_rights(rights));
|
|
575
|
+
} else if (strcmp(argv[i], "--connect-tcp") == 0) {
|
|
576
|
+
push_port_unless_empty(&connect_ports, require_arg(argc, argv, &i), &handled_connect);
|
|
577
|
+
} else if (strcmp(argv[i], "--bind-tcp") == 0) {
|
|
578
|
+
push_port_unless_empty(&bind_ports, require_arg(argc, argv, &i), &handled_bind);
|
|
579
|
+
} else if (strcmp(argv[i], "--scope") == 0) {
|
|
580
|
+
scoped |= scope_name(require_arg(argc, argv, &i));
|
|
581
|
+
} else if (strcmp(argv[i], "--chdir") == 0) {
|
|
582
|
+
chdir_path = require_arg(argc, argv, &i);
|
|
583
|
+
} else if (strcmp(argv[i], "--rlimit") == 0) {
|
|
584
|
+
string_list_push(&rlimit_specs, require_arg(argc, argv, &i));
|
|
585
|
+
} else if (strcmp(argv[i], "--seccomp-deny-network") == 0) {
|
|
586
|
+
seccomp_deny_network = 1;
|
|
587
|
+
} else if (strcmp(argv[i], "--allow-all-known") == 0) {
|
|
588
|
+
allow_all_known = 1;
|
|
589
|
+
} else if (strcmp(argv[i], "--keep-fds") == 0) {
|
|
590
|
+
close_others = 0;
|
|
591
|
+
} else {
|
|
592
|
+
die_msg("unknown option");
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (command_index < 0 || command_index >= argc) {
|
|
597
|
+
die_msg("missing command after --");
|
|
598
|
+
}
|
|
599
|
+
command_argv = &argv[command_index];
|
|
600
|
+
|
|
601
|
+
if (close_others) {
|
|
602
|
+
close_inherited_fds();
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if (chdir_path && chdir(chdir_path) != 0) {
|
|
606
|
+
die("chdir");
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
apply_landlock(&read_paths, &write_paths, &execute_paths, &path_rules, &connect_ports,
|
|
610
|
+
&bind_ports, scoped, allow_all_known, handled_read, handled_write,
|
|
611
|
+
handled_execute, handled_connect, handled_bind);
|
|
612
|
+
if (seccomp_deny_network) {
|
|
613
|
+
apply_seccomp_deny_network();
|
|
614
|
+
}
|
|
615
|
+
for (size_t i = 0; i < rlimit_specs.len; i++) {
|
|
616
|
+
apply_rlimit(rlimit_specs.items[i]);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
execvp(command_argv[0], command_argv);
|
|
620
|
+
perror("execvp");
|
|
621
|
+
_exit(127);
|
|
369
622
|
}
|