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.
@@ -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,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) 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
+
70
100
  static unsigned long long parse_ull(const char *value, const char *name) {
71
- if (!value || value[0] == '\0' || value[0] == '-') die_msg(name);
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') die_msg(name);
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) die_msg("TCP port must be between 0 and 65535");
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)) return 0;
90
- if (abi < 0) die("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
+ }
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 = 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;
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
- return known_fs_rights_for_abi(abi) & ~LANDLOCK_ACCESS_FS_EXECUTE;
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
- LANDLOCK_ACCESS_FS_WRITE_FILE |
129
- LANDLOCK_ACCESS_FS_READ_FILE |
130
- LANDLOCK_ACCESS_FS_TRUNCATE |
131
- LANDLOCK_ACCESS_FS_IOCTL_DEV;
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) die("open(path rule)");
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) die("fstat(path rule)");
140
- if (!S_ISDIR(st.st_mode)) rights &= file_path_rights();
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) die_msg("TCP port must be between 0 and 65535");
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) die("landlock_add_rule(net_port)");
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, 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;
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
- if (!need_fs && !need_net) return;
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) die_msg("Linux Landlock is unavailable");
173
- if (need_net && abi < 4) die_msg("Landlock network rules require ABI v4+");
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 fs_handled = allow_all_known ? known_fs_rights_for_abi(abi) : 0;
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) fs_handled |= read_rights();
178
- if (execute_paths->len) fs_handled |= execute_rights();
179
- if (write_paths->len) fs_handled |= write_rights(abi);
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) net_handled |= LANDLOCK_ACCESS_NET_BIND_TCP;
183
- if (connect_ports->len) net_handled |= LANDLOCK_ACCESS_NET_CONNECT_TCP;
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 = net_handled ? offsetof(struct rb_landlock_ruleset_attr, scoped) : offsetof(struct rb_landlock_ruleset_attr, handled_access_net);
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) die("landlock_create_ruleset");
306
+ if (fd < 0) {
307
+ die("landlock_create_ruleset");
308
+ }
193
309
 
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);
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) die("prctl(PR_SET_NO_NEW_PRIVS)");
201
- if (ll_restrict_self(fd, 0) < 0) die("landlock_restrict_self");
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) die("strdup");
345
+ if (!copy) {
346
+ die("strdup");
347
+ }
208
348
  char *eq = strchr(copy, '=');
209
- if (!eq) die_msg("rlimit must be name=value");
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) resource = RLIMIT_CPU;
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) resource = RLIMIT_AS;
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) resource = RLIMIT_FSIZE;
219
- else if (strcmp(copy, "open_files") == 0) resource = RLIMIT_NOFILE;
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) resource = RLIMIT_NPROC;
370
+ else if (strcmp(copy, "processes") == 0) {
371
+ resource = RLIMIT_NPROC;
372
+ }
222
373
  #endif
223
- else die_msg("unknown rlimit");
374
+ else {
375
+ die_msg("unknown rlimit");
376
+ }
224
377
 
225
378
  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");
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 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
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
- #ifndef SECCOMP_RET_KILL_PROCESS
286
- #define SECCOMP_RET_KILL_PROCESS 0x80000000U
287
- #endif
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 void apply_seccomp_deny_network(void) {
290
- size_t count = sizeof(deny_syscalls) / sizeof(deny_syscalls[0]);
291
- if (count == 0) return;
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
- 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);
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
- struct sock_fprog prog;
314
- prog.len = (unsigned short)pc;
315
- prog.filter = filter;
473
+ free(copy);
474
+ if (!count) {
475
+ die_msg("path rights must not be empty");
476
+ }
477
+ return rights;
478
+ }
316
479
 
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)");
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
- free(filter);
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) die_msg("missing option argument");
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}, env_vars = {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 unsetenv_others = 0, seccomp_deny_network = 0, allow_all_known = 0;
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) { 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");
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
  }