landlock 0.1.1 → 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 +40 -0
- data/README.md +77 -6
- data/benchmark/landlock_overhead.rb +9 -30
- data/ext/landlock/bin/safe_exec_helper.c +602 -0
- data/ext/landlock/extconf.rb +33 -0
- data/ext/landlock/landlock.c +40 -174
- data/ext/landlock/landlock_native.h +168 -0
- 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 -245
- metadata +53 -9
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
#include "../landlock_native.h"
|
|
2
|
+
#include "../seccomp_deny_network.h"
|
|
3
|
+
|
|
4
|
+
#include <stdio.h>
|
|
5
|
+
#include <stdlib.h>
|
|
6
|
+
#include <string.h>
|
|
7
|
+
#include <limits.h>
|
|
8
|
+
#include <ctype.h>
|
|
9
|
+
#include <dirent.h>
|
|
10
|
+
#include <sys/resource.h>
|
|
11
|
+
#include <sys/stat.h>
|
|
12
|
+
|
|
13
|
+
typedef struct {
|
|
14
|
+
char **items;
|
|
15
|
+
size_t len;
|
|
16
|
+
size_t cap;
|
|
17
|
+
} string_list;
|
|
18
|
+
|
|
19
|
+
typedef struct {
|
|
20
|
+
unsigned long long *items;
|
|
21
|
+
size_t len;
|
|
22
|
+
size_t cap;
|
|
23
|
+
} ull_list;
|
|
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
|
+
static void die(const char *message) {
|
|
39
|
+
perror(message);
|
|
40
|
+
_exit(126);
|
|
41
|
+
}
|
|
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
|
+
|
|
49
|
+
static void die_msg(const char *message) {
|
|
50
|
+
fprintf(stderr, "landlock-safe-exec: %s\n", message);
|
|
51
|
+
_exit(126);
|
|
52
|
+
}
|
|
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
|
+
|
|
59
|
+
static void string_list_push(string_list *list, char *value) {
|
|
60
|
+
if (list->len == list->cap) {
|
|
61
|
+
size_t cap = list->cap ? list->cap * 2 : 8;
|
|
62
|
+
char **items = realloc(list->items, cap * sizeof(char *));
|
|
63
|
+
if (!items) {
|
|
64
|
+
die("realloc");
|
|
65
|
+
}
|
|
66
|
+
list->items = items;
|
|
67
|
+
list->cap = cap;
|
|
68
|
+
}
|
|
69
|
+
list->items[list->len++] = value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static void ull_list_push(ull_list *list, unsigned long long value) {
|
|
73
|
+
if (list->len == list->cap) {
|
|
74
|
+
size_t cap = list->cap ? list->cap * 2 : 8;
|
|
75
|
+
unsigned long long *items = realloc(list->items, cap * sizeof(unsigned long long));
|
|
76
|
+
if (!items) {
|
|
77
|
+
die("realloc");
|
|
78
|
+
}
|
|
79
|
+
list->items = items;
|
|
80
|
+
list->cap = cap;
|
|
81
|
+
}
|
|
82
|
+
list->items[list->len++] = value;
|
|
83
|
+
}
|
|
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
|
+
static unsigned long long parse_ull(const char *value, const char *name) {
|
|
101
|
+
if (!value || value[0] == '\0' || value[0] == '-' || value[0] == '+' ||
|
|
102
|
+
isspace((unsigned char)value[0])) {
|
|
103
|
+
die_msg(name);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
errno = 0;
|
|
107
|
+
char *end = NULL;
|
|
108
|
+
unsigned long long parsed = strtoull(value, &end, 10);
|
|
109
|
+
if (errno == ERANGE || !end || *end != '\0') {
|
|
110
|
+
die_msg(name);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return parsed;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
static unsigned long long parse_port(const char *value) {
|
|
117
|
+
unsigned long long port = parse_ull(value, "TCP port must be an integer between 0 and 65535");
|
|
118
|
+
if (port > 65535ULL) {
|
|
119
|
+
die_msg("TCP port must be between 0 and 65535");
|
|
120
|
+
}
|
|
121
|
+
return port;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
static int abi_version(void) {
|
|
125
|
+
long abi = ll_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
|
|
126
|
+
if (abi < 0 && (errno == ENOSYS || errno == EOPNOTSUPP)) {
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
if (abi < 0) {
|
|
130
|
+
die("landlock_create_ruleset(version)");
|
|
131
|
+
}
|
|
132
|
+
return (int)abi;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
static uint64_t known_fs_rights_for_abi(int abi) {
|
|
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
|
+
}
|
|
151
|
+
return rights;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static uint64_t read_rights(void) {
|
|
155
|
+
return LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
static uint64_t execute_rights(void) {
|
|
159
|
+
return LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
static uint64_t write_rights(int abi) {
|
|
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;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
static uint64_t file_path_rights(void) {
|
|
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;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
static void add_path_rule(int fd, const char *path, uint64_t rights) {
|
|
195
|
+
int parent_fd = open(path, O_PATH | O_CLOEXEC);
|
|
196
|
+
if (parent_fd < 0) {
|
|
197
|
+
die_path("open(path rule)", path);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
struct stat st;
|
|
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
|
+
}
|
|
211
|
+
|
|
212
|
+
struct rb_landlock_path_beneath_attr rule;
|
|
213
|
+
memset(&rule, 0, sizeof(rule));
|
|
214
|
+
rule.allowed_access = rights;
|
|
215
|
+
rule.parent_fd = parent_fd;
|
|
216
|
+
|
|
217
|
+
long ret = ll_add_rule(fd, LANDLOCK_RULE_PATH_BENEATH, &rule, 0);
|
|
218
|
+
int saved_errno = errno;
|
|
219
|
+
close(parent_fd);
|
|
220
|
+
if (ret < 0) {
|
|
221
|
+
errno = saved_errno;
|
|
222
|
+
die("landlock_add_rule(path_beneath)");
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
static void add_net_rule(int fd, unsigned long long port, uint64_t rights) {
|
|
227
|
+
if (port > 65535ULL) {
|
|
228
|
+
die_msg("TCP port must be between 0 and 65535");
|
|
229
|
+
}
|
|
230
|
+
struct rb_landlock_net_port_attr rule;
|
|
231
|
+
memset(&rule, 0, sizeof(rule));
|
|
232
|
+
rule.allowed_access = rights;
|
|
233
|
+
rule.port = port;
|
|
234
|
+
if (ll_add_rule(fd, LANDLOCK_RULE_NET_PORT, &rule, 0) < 0) {
|
|
235
|
+
die("landlock_add_rule(net_port)");
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
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;
|
|
245
|
+
int need_net = connect_ports->len || bind_ports->len;
|
|
246
|
+
int need_scope = scoped != 0;
|
|
247
|
+
if (!need_fs && !need_net && !need_scope) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
int abi = abi_version();
|
|
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
|
+
}
|
|
261
|
+
|
|
262
|
+
uint64_t known_fs_rights = known_fs_rights_for_abi(abi);
|
|
263
|
+
uint64_t fs_handled = allow_all_known ? known_fs_rights : 0;
|
|
264
|
+
if (!allow_all_known) {
|
|
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
|
+
}
|
|
282
|
+
}
|
|
283
|
+
uint64_t net_handled = 0;
|
|
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
|
+
}
|
|
294
|
+
|
|
295
|
+
struct rb_landlock_ruleset_attr attr;
|
|
296
|
+
memset(&attr, 0, sizeof(attr));
|
|
297
|
+
attr.handled_access_fs = fs_handled;
|
|
298
|
+
attr.handled_access_net = net_handled;
|
|
299
|
+
attr.scoped = scoped;
|
|
300
|
+
|
|
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));
|
|
305
|
+
int fd = (int)ll_create_ruleset(&attr, attr_size, 0);
|
|
306
|
+
if (fd < 0) {
|
|
307
|
+
die("landlock_create_ruleset");
|
|
308
|
+
}
|
|
309
|
+
|
|
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
|
+
}
|
|
333
|
+
|
|
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
|
+
}
|
|
340
|
+
close(fd);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
static void apply_rlimit(const char *spec) {
|
|
344
|
+
char *copy = strdup(spec);
|
|
345
|
+
if (!copy) {
|
|
346
|
+
die("strdup");
|
|
347
|
+
}
|
|
348
|
+
char *eq = strchr(copy, '=');
|
|
349
|
+
if (!eq) {
|
|
350
|
+
die_msg("rlimit must be name=value");
|
|
351
|
+
}
|
|
352
|
+
*eq = '\0';
|
|
353
|
+
unsigned long long value = parse_ull(eq + 1, "rlimit value must be a non-negative integer");
|
|
354
|
+
int resource = -1;
|
|
355
|
+
|
|
356
|
+
if (strcmp(copy, "cpu_seconds") == 0) {
|
|
357
|
+
resource = RLIMIT_CPU;
|
|
358
|
+
}
|
|
359
|
+
#ifdef RLIMIT_AS
|
|
360
|
+
else if (strcmp(copy, "memory_bytes") == 0) {
|
|
361
|
+
resource = RLIMIT_AS;
|
|
362
|
+
}
|
|
363
|
+
#endif
|
|
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
|
+
}
|
|
369
|
+
#ifdef RLIMIT_NPROC
|
|
370
|
+
else if (strcmp(copy, "processes") == 0) {
|
|
371
|
+
resource = RLIMIT_NPROC;
|
|
372
|
+
}
|
|
373
|
+
#endif
|
|
374
|
+
else {
|
|
375
|
+
die_msg("unknown rlimit");
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
struct rlimit limit;
|
|
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
|
+
}
|
|
388
|
+
free(copy);
|
|
389
|
+
}
|
|
390
|
+
|
|
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
|
+
}
|
|
397
|
+
|
|
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
|
+
}
|
|
450
|
+
|
|
451
|
+
static uint64_t parse_fs_rights(const char *spec) {
|
|
452
|
+
char *copy = strdup(spec);
|
|
453
|
+
if (!copy) {
|
|
454
|
+
die("strdup");
|
|
455
|
+
}
|
|
456
|
+
|
|
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
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
free(copy);
|
|
474
|
+
if (!count) {
|
|
475
|
+
die_msg("path rights must not be empty");
|
|
476
|
+
}
|
|
477
|
+
return rights;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
static uint64_t scope_name(const char *name) {
|
|
481
|
+
if (strcmp(name, "abstract_unix_socket") == 0) {
|
|
482
|
+
return LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
|
|
483
|
+
}
|
|
484
|
+
if (strcmp(name, "signal") == 0) {
|
|
485
|
+
return LANDLOCK_SCOPE_SIGNAL;
|
|
486
|
+
}
|
|
487
|
+
die_msg("unknown Landlock scope");
|
|
488
|
+
return 0;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
static char *require_arg(int argc, char **argv, int *i) {
|
|
492
|
+
if (*i + 1 >= argc) {
|
|
493
|
+
die_msg("missing option argument");
|
|
494
|
+
}
|
|
495
|
+
(*i)++;
|
|
496
|
+
return argv[*i];
|
|
497
|
+
}
|
|
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
|
+
|
|
531
|
+
int main(int argc, char **argv) {
|
|
532
|
+
string_list read_paths = {0}, write_paths = {0}, execute_paths = {0}, rlimit_specs = {0};
|
|
533
|
+
path_rule_list path_rules = {0};
|
|
534
|
+
ull_list connect_ports = {0}, bind_ports = {0};
|
|
535
|
+
int seccomp_deny_network = 0, allow_all_known = 0, close_others = 1;
|
|
536
|
+
uint64_t scoped = 0;
|
|
537
|
+
char *chdir_path = NULL;
|
|
538
|
+
char **command_argv = NULL;
|
|
539
|
+
int command_index = -1;
|
|
540
|
+
|
|
541
|
+
for (int i = 1; i < argc; i++) {
|
|
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);
|
|
602
|
+
}
|
data/ext/landlock/extconf.rb
CHANGED
|
@@ -2,11 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
require "mkmf"
|
|
4
4
|
|
|
5
|
+
append_cppflags("-D_GNU_SOURCE")
|
|
6
|
+
|
|
5
7
|
abort "missing ruby headers" unless have_header("ruby.h")
|
|
6
8
|
|
|
7
9
|
have_header("linux/landlock.h")
|
|
10
|
+
have_header("linux/seccomp.h")
|
|
11
|
+
have_header("linux/filter.h")
|
|
8
12
|
have_header("sys/prctl.h")
|
|
9
13
|
have_header("sys/syscall.h")
|
|
14
|
+
have_header("sys/resource.h")
|
|
10
15
|
have_header("fcntl.h")
|
|
11
16
|
|
|
12
17
|
create_makefile("landlock/landlock")
|
|
18
|
+
|
|
19
|
+
if RUBY_PLATFORM.include?("linux")
|
|
20
|
+
helper = "landlock-safe-exec"
|
|
21
|
+
helper_src = "$(srcdir)/bin/safe_exec_helper.c"
|
|
22
|
+
helper_headers = "$(srcdir)/landlock_native.h $(srcdir)/seccomp_deny_network.h"
|
|
23
|
+
helper_dest = "$(RUBYARCHDIR)/#{helper}"
|
|
24
|
+
|
|
25
|
+
File.open("Makefile", "a") do |makefile|
|
|
26
|
+
makefile.puts <<~MAKE
|
|
27
|
+
|
|
28
|
+
all: #{helper}
|
|
29
|
+
|
|
30
|
+
#{helper}: #{helper_src}
|
|
31
|
+
\t$(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) #{helper_src} -o #{helper}
|
|
32
|
+
|
|
33
|
+
install: install-#{helper}
|
|
34
|
+
|
|
35
|
+
install-#{helper}: #{helper}
|
|
36
|
+
\t$(MAKEDIRS) $(RUBYARCHDIR)
|
|
37
|
+
\t$(INSTALL_PROG) #{helper} #{helper_dest}
|
|
38
|
+
|
|
39
|
+
clean-local::
|
|
40
|
+
\t$(Q)$(RM) #{helper}
|
|
41
|
+
|
|
42
|
+
clean: clean-local
|
|
43
|
+
MAKE
|
|
44
|
+
end
|
|
45
|
+
end
|