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.
@@ -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) die("realloc");
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) die("realloc");
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] == '-') die_msg(name);
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') die_msg(name);
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) die_msg("TCP port must be between 0 and 65535");
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)) return 0;
90
- if (abi < 0) die("landlock_create_ruleset(version)");
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 = LANDLOCK_ACCESS_FS_EXECUTE |
96
- LANDLOCK_ACCESS_FS_WRITE_FILE |
97
- LANDLOCK_ACCESS_FS_READ_FILE |
98
- LANDLOCK_ACCESS_FS_READ_DIR |
99
- LANDLOCK_ACCESS_FS_REMOVE_DIR |
100
- LANDLOCK_ACCESS_FS_REMOVE_FILE |
101
- LANDLOCK_ACCESS_FS_MAKE_CHAR |
102
- LANDLOCK_ACCESS_FS_MAKE_DIR |
103
- LANDLOCK_ACCESS_FS_MAKE_REG |
104
- LANDLOCK_ACCESS_FS_MAKE_SOCK |
105
- LANDLOCK_ACCESS_FS_MAKE_FIFO |
106
- LANDLOCK_ACCESS_FS_MAKE_BLOCK |
107
- LANDLOCK_ACCESS_FS_MAKE_SYM;
108
- if (abi >= 2) rights |= LANDLOCK_ACCESS_FS_REFER;
109
- if (abi >= 3) rights |= LANDLOCK_ACCESS_FS_TRUNCATE;
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
- return known_fs_rights_for_abi(abi) & ~LANDLOCK_ACCESS_FS_EXECUTE;
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
- LANDLOCK_ACCESS_FS_WRITE_FILE |
129
- LANDLOCK_ACCESS_FS_READ_FILE |
130
- LANDLOCK_ACCESS_FS_TRUNCATE |
131
- LANDLOCK_ACCESS_FS_IOCTL_DEV;
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) die("open(path rule)");
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) die("fstat(path rule)");
140
- if (!S_ISDIR(st.st_mode)) rights &= file_path_rights();
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) die_msg("TCP port must be between 0 and 65535");
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) die("landlock_add_rule(net_port)");
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, string_list *execute_paths,
166
- ull_list *connect_ports, ull_list *bind_ports, int allow_all_known) {
167
- int need_fs = read_paths->len || write_paths->len || execute_paths->len || allow_all_known;
168
- int need_net = connect_ports->len || bind_ports->len;
169
- if (!need_fs && !need_net) return;
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) die_msg("Linux Landlock is unavailable");
173
- if (need_net && abi < 4) die_msg("Landlock network rules require ABI v4+");
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 fs_handled = allow_all_known ? known_fs_rights_for_abi(abi) : 0;
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 (read_paths->len) fs_handled |= read_rights();
178
- if (execute_paths->len) fs_handled |= execute_rights();
179
- if (write_paths->len) fs_handled |= write_rights(abi);
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 (bind_ports->len) net_handled |= LANDLOCK_ACCESS_NET_BIND_TCP;
183
- if (connect_ports->len) net_handled |= LANDLOCK_ACCESS_NET_CONNECT_TCP;
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 = net_handled ? offsetof(struct rb_landlock_ruleset_attr, scoped) : offsetof(struct rb_landlock_ruleset_attr, handled_access_net);
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) die("landlock_create_ruleset");
323
+ if (fd < 0) {
324
+ die("landlock_create_ruleset");
325
+ }
193
326
 
194
- for (size_t i = 0; i < read_paths->len; i++) add_path_rule(fd, read_paths->items[i], read_rights());
195
- for (size_t i = 0; i < execute_paths->len; i++) add_path_rule(fd, execute_paths->items[i], execute_rights());
196
- for (size_t i = 0; i < write_paths->len; i++) add_path_rule(fd, write_paths->items[i], write_rights(abi));
197
- for (size_t i = 0; i < connect_ports->len; i++) add_net_rule(fd, connect_ports->items[i], LANDLOCK_ACCESS_NET_CONNECT_TCP);
198
- for (size_t i = 0; i < bind_ports->len; i++) add_net_rule(fd, bind_ports->items[i], LANDLOCK_ACCESS_NET_BIND_TCP);
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) die("prctl(PR_SET_NO_NEW_PRIVS)");
201
- if (ll_restrict_self(fd, 0) < 0) die("landlock_restrict_self");
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) die("strdup");
362
+ if (!copy) {
363
+ die("strdup");
364
+ }
208
365
  char *eq = strchr(copy, '=');
209
- if (!eq) die_msg("rlimit must be name=value");
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) resource = RLIMIT_CPU;
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) resource = RLIMIT_AS;
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) resource = RLIMIT_FSIZE;
219
- else if (strcmp(copy, "open_files") == 0) resource = RLIMIT_NOFILE;
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) resource = RLIMIT_NPROC;
387
+ else if (strcmp(copy, "processes") == 0) {
388
+ resource = RLIMIT_NPROC;
389
+ }
222
390
  #endif
223
- else die_msg("unknown rlimit");
391
+ else {
392
+ die_msg("unknown rlimit");
393
+ }
224
394
 
225
395
  struct rlimit limit;
226
- limit.rlim_cur = (rlim_t)value;
227
- limit.rlim_max = (rlim_t)value;
228
- if (setrlimit(resource, &limit) != 0) die("setrlimit");
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 int deny_syscalls[] = {
233
- #ifdef __NR_socket
234
- __NR_socket,
235
- #endif
236
- #ifdef __NR_socketpair
237
- __NR_socketpair,
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
- #ifndef SECCOMP_RET_KILL_PROCESS
286
- #define SECCOMP_RET_KILL_PROCESS 0x80000000U
287
- #endif
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 void apply_seccomp_deny_network(void) {
290
- size_t count = sizeof(deny_syscalls) / sizeof(deny_syscalls[0]);
291
- if (count == 0) return;
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
- size_t len = 1 + (2 * count) + 1;
294
- #ifdef EXPECTED_AUDIT_ARCH
295
- len += 3;
296
- #endif
297
- struct sock_filter *filter = calloc(len, sizeof(struct sock_filter));
298
- if (!filter) die("calloc");
299
-
300
- size_t pc = 0;
301
- #ifdef EXPECTED_AUDIT_ARCH
302
- filter[pc++] = (struct sock_filter)BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, arch));
303
- filter[pc++] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, EXPECTED_AUDIT_ARCH, 1, 0);
304
- filter[pc++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS);
305
- #endif
306
- filter[pc++] = (struct sock_filter)BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr));
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
- struct sock_fprog prog;
314
- prog.len = (unsigned short)pc;
315
- prog.filter = filter;
490
+ free(copy);
491
+ if (!count) {
492
+ die_msg("path rights must not be empty");
493
+ }
494
+ return rights;
495
+ }
316
496
 
317
- if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) die("prctl(PR_SET_NO_NEW_PRIVS)");
318
- #ifdef SYS_seccomp
319
- if (syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog) != 0)
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
- free(filter);
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) die_msg("missing option argument");
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}, env_vars = {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 unsetenv_others = 0, seccomp_deny_network = 0, allow_all_known = 0;
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) { command_index = i + 1; break; }
342
- if (strcmp(argv[i], "--read") == 0) string_list_push(&read_paths, require_arg(argc, argv, &i));
343
- else if (strcmp(argv[i], "--write") == 0) string_list_push(&write_paths, require_arg(argc, argv, &i));
344
- else if (strcmp(argv[i], "--execute") == 0) string_list_push(&execute_paths, require_arg(argc, argv, &i));
345
- else if (strcmp(argv[i], "--connect-tcp") == 0) ull_list_push(&connect_ports, parse_port(require_arg(argc, argv, &i)));
346
- else if (strcmp(argv[i], "--bind-tcp") == 0) ull_list_push(&bind_ports, parse_port(require_arg(argc, argv, &i)));
347
- else if (strcmp(argv[i], "--chdir") == 0) chdir_path = require_arg(argc, argv, &i);
348
- else if (strcmp(argv[i], "--env") == 0) string_list_push(&env_vars, require_arg(argc, argv, &i));
349
- else if (strcmp(argv[i], "--unsetenv-others") == 0) unsetenv_others = 1;
350
- else if (strcmp(argv[i], "--rlimit") == 0) apply_rlimit(require_arg(argc, argv, &i));
351
- else if (strcmp(argv[i], "--seccomp-deny-network") == 0) seccomp_deny_network = 1;
352
- else if (strcmp(argv[i], "--allow-all-known") == 0) allow_all_known = 1;
353
- else die_msg("unknown option");
354
- }
355
-
356
- if (command_index < 0 || command_index >= argc) die_msg("missing command after --");
357
-
358
- if (chdir_path && chdir(chdir_path) != 0) die("chdir");
359
- if (unsetenv_others && clearenv() != 0) die("clearenv");
360
- for (size_t i = 0; i < env_vars.len; i++) {
361
- if (putenv(env_vars.items[i]) != 0) die("putenv");
362
- }
363
-
364
- apply_landlock(&read_paths, &write_paths, &execute_paths, &connect_ports, &bind_ports, allow_all_known);
365
- if (seccomp_deny_network) apply_seccomp_deny_network();
366
-
367
- execvp(argv[command_index], &argv[command_index]);
368
- die("execvp");
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
  }