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