landlock 0.1.0 → 0.2
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 +42 -0
- data/README.md +141 -9
- data/benchmark/landlock_overhead.rb +212 -0
- data/ext/landlock/bin/safe_exec_helper.c +369 -0
- data/ext/landlock/extconf.rb +30 -0
- data/ext/landlock/landlock.c +25 -153
- data/ext/landlock/landlock_native.h +167 -0
- data/lib/landlock/safe_exec.rb +522 -0
- data/lib/landlock/version.rb +1 -1
- data/lib/landlock.rb +103 -34
- metadata +6 -1
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
#include "../landlock_native.h"
|
|
2
|
+
|
|
3
|
+
#include <stdio.h>
|
|
4
|
+
#include <stdlib.h>
|
|
5
|
+
#include <string.h>
|
|
6
|
+
#include <limits.h>
|
|
7
|
+
#include <sys/resource.h>
|
|
8
|
+
#include <sys/stat.h>
|
|
9
|
+
|
|
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
|
+
typedef struct {
|
|
27
|
+
char **items;
|
|
28
|
+
size_t len;
|
|
29
|
+
size_t cap;
|
|
30
|
+
} string_list;
|
|
31
|
+
|
|
32
|
+
typedef struct {
|
|
33
|
+
unsigned long long *items;
|
|
34
|
+
size_t len;
|
|
35
|
+
size_t cap;
|
|
36
|
+
} ull_list;
|
|
37
|
+
|
|
38
|
+
static void die(const char *message) {
|
|
39
|
+
perror(message);
|
|
40
|
+
_exit(126);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static void die_msg(const char *message) {
|
|
44
|
+
fprintf(stderr, "landlock-safe-exec: %s\n", message);
|
|
45
|
+
_exit(126);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static void string_list_push(string_list *list, char *value) {
|
|
49
|
+
if (list->len == list->cap) {
|
|
50
|
+
size_t cap = list->cap ? list->cap * 2 : 8;
|
|
51
|
+
char **items = realloc(list->items, cap * sizeof(char *));
|
|
52
|
+
if (!items) die("realloc");
|
|
53
|
+
list->items = items;
|
|
54
|
+
list->cap = cap;
|
|
55
|
+
}
|
|
56
|
+
list->items[list->len++] = value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static void ull_list_push(ull_list *list, unsigned long long value) {
|
|
60
|
+
if (list->len == list->cap) {
|
|
61
|
+
size_t cap = list->cap ? list->cap * 2 : 8;
|
|
62
|
+
unsigned long long *items = realloc(list->items, cap * sizeof(unsigned long long));
|
|
63
|
+
if (!items) die("realloc");
|
|
64
|
+
list->items = items;
|
|
65
|
+
list->cap = cap;
|
|
66
|
+
}
|
|
67
|
+
list->items[list->len++] = value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static unsigned long long parse_ull(const char *value, const char *name) {
|
|
71
|
+
if (!value || value[0] == '\0' || value[0] == '-') die_msg(name);
|
|
72
|
+
|
|
73
|
+
errno = 0;
|
|
74
|
+
char *end = NULL;
|
|
75
|
+
unsigned long long parsed = strtoull(value, &end, 10);
|
|
76
|
+
if (errno == ERANGE || !end || *end != '\0') die_msg(name);
|
|
77
|
+
|
|
78
|
+
return parsed;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static unsigned long long parse_port(const char *value) {
|
|
82
|
+
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");
|
|
84
|
+
return port;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static int abi_version(void) {
|
|
88
|
+
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)");
|
|
91
|
+
return (int)abi;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
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;
|
|
111
|
+
return rights;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static uint64_t read_rights(void) {
|
|
115
|
+
return LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
static uint64_t execute_rights(void) {
|
|
119
|
+
return LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
static uint64_t write_rights(int abi) {
|
|
123
|
+
return known_fs_rights_for_abi(abi) & ~LANDLOCK_ACCESS_FS_EXECUTE;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
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;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
static void add_path_rule(int fd, const char *path, uint64_t rights) {
|
|
135
|
+
int parent_fd = open(path, O_PATH | O_CLOEXEC);
|
|
136
|
+
if (parent_fd < 0) die("open(path rule)");
|
|
137
|
+
|
|
138
|
+
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();
|
|
141
|
+
|
|
142
|
+
struct rb_landlock_path_beneath_attr rule;
|
|
143
|
+
memset(&rule, 0, sizeof(rule));
|
|
144
|
+
rule.allowed_access = rights;
|
|
145
|
+
rule.parent_fd = parent_fd;
|
|
146
|
+
|
|
147
|
+
long ret = ll_add_rule(fd, LANDLOCK_RULE_PATH_BENEATH, &rule, 0);
|
|
148
|
+
int saved_errno = errno;
|
|
149
|
+
close(parent_fd);
|
|
150
|
+
if (ret < 0) {
|
|
151
|
+
errno = saved_errno;
|
|
152
|
+
die("landlock_add_rule(path_beneath)");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
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");
|
|
158
|
+
struct rb_landlock_net_port_attr rule;
|
|
159
|
+
memset(&rule, 0, sizeof(rule));
|
|
160
|
+
rule.allowed_access = rights;
|
|
161
|
+
rule.port = port;
|
|
162
|
+
if (ll_add_rule(fd, LANDLOCK_RULE_NET_PORT, &rule, 0) < 0) die("landlock_add_rule(net_port)");
|
|
163
|
+
}
|
|
164
|
+
|
|
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;
|
|
170
|
+
|
|
171
|
+
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+");
|
|
174
|
+
|
|
175
|
+
uint64_t fs_handled = allow_all_known ? known_fs_rights_for_abi(abi) : 0;
|
|
176
|
+
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);
|
|
180
|
+
}
|
|
181
|
+
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;
|
|
184
|
+
|
|
185
|
+
struct rb_landlock_ruleset_attr attr;
|
|
186
|
+
memset(&attr, 0, sizeof(attr));
|
|
187
|
+
attr.handled_access_fs = fs_handled;
|
|
188
|
+
attr.handled_access_net = net_handled;
|
|
189
|
+
|
|
190
|
+
size_t attr_size = net_handled ? offsetof(struct rb_landlock_ruleset_attr, scoped) : offsetof(struct rb_landlock_ruleset_attr, handled_access_net);
|
|
191
|
+
int fd = (int)ll_create_ruleset(&attr, attr_size, 0);
|
|
192
|
+
if (fd < 0) die("landlock_create_ruleset");
|
|
193
|
+
|
|
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);
|
|
199
|
+
|
|
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");
|
|
202
|
+
close(fd);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
static void apply_rlimit(const char *spec) {
|
|
206
|
+
char *copy = strdup(spec);
|
|
207
|
+
if (!copy) die("strdup");
|
|
208
|
+
char *eq = strchr(copy, '=');
|
|
209
|
+
if (!eq) die_msg("rlimit must be name=value");
|
|
210
|
+
*eq = '\0';
|
|
211
|
+
unsigned long long value = parse_ull(eq + 1, "rlimit value must be a non-negative integer");
|
|
212
|
+
int resource = -1;
|
|
213
|
+
|
|
214
|
+
if (strcmp(copy, "cpu_seconds") == 0) resource = RLIMIT_CPU;
|
|
215
|
+
#ifdef RLIMIT_AS
|
|
216
|
+
else if (strcmp(copy, "memory_bytes") == 0) resource = RLIMIT_AS;
|
|
217
|
+
#endif
|
|
218
|
+
else if (strcmp(copy, "file_size_bytes") == 0) resource = RLIMIT_FSIZE;
|
|
219
|
+
else if (strcmp(copy, "open_files") == 0) resource = RLIMIT_NOFILE;
|
|
220
|
+
#ifdef RLIMIT_NPROC
|
|
221
|
+
else if (strcmp(copy, "processes") == 0) resource = RLIMIT_NPROC;
|
|
222
|
+
#endif
|
|
223
|
+
else die_msg("unknown rlimit");
|
|
224
|
+
|
|
225
|
+
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");
|
|
229
|
+
free(copy);
|
|
230
|
+
}
|
|
231
|
+
|
|
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
|
|
284
|
+
|
|
285
|
+
#ifndef SECCOMP_RET_KILL_PROCESS
|
|
286
|
+
#define SECCOMP_RET_KILL_PROCESS 0x80000000U
|
|
287
|
+
#endif
|
|
288
|
+
|
|
289
|
+
static void apply_seccomp_deny_network(void) {
|
|
290
|
+
size_t count = sizeof(deny_syscalls) / sizeof(deny_syscalls[0]);
|
|
291
|
+
if (count == 0) return;
|
|
292
|
+
|
|
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);
|
|
310
|
+
}
|
|
311
|
+
filter[pc++] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW);
|
|
312
|
+
|
|
313
|
+
struct sock_fprog prog;
|
|
314
|
+
prog.len = (unsigned short)pc;
|
|
315
|
+
prog.filter = filter;
|
|
316
|
+
|
|
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)");
|
|
323
|
+
}
|
|
324
|
+
free(filter);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
static char *require_arg(int argc, char **argv, int *i) {
|
|
328
|
+
if (*i + 1 >= argc) die_msg("missing option argument");
|
|
329
|
+
(*i)++;
|
|
330
|
+
return argv[*i];
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
int main(int argc, char **argv) {
|
|
334
|
+
string_list read_paths = {0}, write_paths = {0}, execute_paths = {0}, env_vars = {0};
|
|
335
|
+
ull_list connect_ports = {0}, bind_ports = {0};
|
|
336
|
+
int unsetenv_others = 0, seccomp_deny_network = 0, allow_all_known = 0;
|
|
337
|
+
char *chdir_path = NULL;
|
|
338
|
+
int command_index = -1;
|
|
339
|
+
|
|
340
|
+
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");
|
|
369
|
+
}
|
data/ext/landlock/extconf.rb
CHANGED
|
@@ -5,8 +5,38 @@ require "mkmf"
|
|
|
5
5
|
abort "missing ruby headers" unless have_header("ruby.h")
|
|
6
6
|
|
|
7
7
|
have_header("linux/landlock.h")
|
|
8
|
+
have_header("linux/seccomp.h")
|
|
9
|
+
have_header("linux/filter.h")
|
|
8
10
|
have_header("sys/prctl.h")
|
|
9
11
|
have_header("sys/syscall.h")
|
|
12
|
+
have_header("sys/resource.h")
|
|
10
13
|
have_header("fcntl.h")
|
|
11
14
|
|
|
12
15
|
create_makefile("landlock/landlock")
|
|
16
|
+
|
|
17
|
+
if RUBY_PLATFORM.include?("linux")
|
|
18
|
+
helper = "landlock-safe-exec"
|
|
19
|
+
helper_src = "$(srcdir)/bin/safe_exec_helper.c"
|
|
20
|
+
helper_dest = "$(RUBYARCHDIR)/#{helper}"
|
|
21
|
+
|
|
22
|
+
File.open("Makefile", "a") do |makefile|
|
|
23
|
+
makefile.puts <<~MAKE
|
|
24
|
+
|
|
25
|
+
all: #{helper}
|
|
26
|
+
|
|
27
|
+
#{helper}: #{helper_src}
|
|
28
|
+
\t$(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) #{helper_src} -o #{helper} $(LIBS)
|
|
29
|
+
|
|
30
|
+
install: install-#{helper}
|
|
31
|
+
|
|
32
|
+
install-#{helper}: #{helper}
|
|
33
|
+
\t$(MAKEDIRS) $(RUBYARCHDIR)
|
|
34
|
+
\t$(INSTALL_PROG) #{helper} #{helper_dest}
|
|
35
|
+
|
|
36
|
+
clean-local::
|
|
37
|
+
\t$(Q)$(RM) #{helper}
|
|
38
|
+
|
|
39
|
+
clean: clean-local
|
|
40
|
+
MAKE
|
|
41
|
+
end
|
|
42
|
+
end
|
data/ext/landlock/landlock.c
CHANGED
|
@@ -1,157 +1,12 @@
|
|
|
1
1
|
#include "ruby.h"
|
|
2
|
+
#include "landlock_native.h"
|
|
2
3
|
|
|
3
|
-
#include <errno.h>
|
|
4
|
-
#include <fcntl.h>
|
|
5
|
-
#include <stdint.h>
|
|
6
|
-
#include <stddef.h>
|
|
7
4
|
#include <string.h>
|
|
8
|
-
#include <unistd.h>
|
|
9
|
-
#include <sys/prctl.h>
|
|
10
|
-
#include <sys/syscall.h>
|
|
11
|
-
|
|
12
|
-
#ifdef HAVE_LINUX_LANDLOCK_H
|
|
13
|
-
#include <linux/landlock.h>
|
|
14
|
-
#endif
|
|
15
|
-
|
|
16
|
-
#ifndef SYS_landlock_create_ruleset
|
|
17
|
-
# if defined(__x86_64__)
|
|
18
|
-
# define SYS_landlock_create_ruleset 444
|
|
19
|
-
# define SYS_landlock_add_rule 445
|
|
20
|
-
# define SYS_landlock_restrict_self 446
|
|
21
|
-
# elif defined(__aarch64__)
|
|
22
|
-
# define SYS_landlock_create_ruleset 444
|
|
23
|
-
# define SYS_landlock_add_rule 445
|
|
24
|
-
# define SYS_landlock_restrict_self 446
|
|
25
|
-
# elif defined(__i386__)
|
|
26
|
-
# define SYS_landlock_create_ruleset 451
|
|
27
|
-
# define SYS_landlock_add_rule 452
|
|
28
|
-
# define SYS_landlock_restrict_self 453
|
|
29
|
-
# endif
|
|
30
|
-
#endif
|
|
31
|
-
|
|
32
|
-
#ifndef LANDLOCK_CREATE_RULESET_VERSION
|
|
33
|
-
#define LANDLOCK_CREATE_RULESET_VERSION (1U << 0)
|
|
34
|
-
#endif
|
|
35
|
-
|
|
36
|
-
#ifndef LANDLOCK_RULE_PATH_BENEATH
|
|
37
|
-
#define LANDLOCK_RULE_PATH_BENEATH 1
|
|
38
|
-
#endif
|
|
39
|
-
|
|
40
|
-
#ifndef LANDLOCK_RULE_NET_PORT
|
|
41
|
-
#define LANDLOCK_RULE_NET_PORT 2
|
|
42
|
-
#endif
|
|
43
|
-
|
|
44
|
-
#ifndef LANDLOCK_ACCESS_FS_EXECUTE
|
|
45
|
-
#define LANDLOCK_ACCESS_FS_EXECUTE (1ULL << 0)
|
|
46
|
-
#endif
|
|
47
|
-
#ifndef LANDLOCK_ACCESS_FS_WRITE_FILE
|
|
48
|
-
#define LANDLOCK_ACCESS_FS_WRITE_FILE (1ULL << 1)
|
|
49
|
-
#endif
|
|
50
|
-
#ifndef LANDLOCK_ACCESS_FS_READ_FILE
|
|
51
|
-
#define LANDLOCK_ACCESS_FS_READ_FILE (1ULL << 2)
|
|
52
|
-
#endif
|
|
53
|
-
#ifndef LANDLOCK_ACCESS_FS_READ_DIR
|
|
54
|
-
#define LANDLOCK_ACCESS_FS_READ_DIR (1ULL << 3)
|
|
55
|
-
#endif
|
|
56
|
-
#ifndef LANDLOCK_ACCESS_FS_REMOVE_DIR
|
|
57
|
-
#define LANDLOCK_ACCESS_FS_REMOVE_DIR (1ULL << 4)
|
|
58
|
-
#endif
|
|
59
|
-
#ifndef LANDLOCK_ACCESS_FS_REMOVE_FILE
|
|
60
|
-
#define LANDLOCK_ACCESS_FS_REMOVE_FILE (1ULL << 5)
|
|
61
|
-
#endif
|
|
62
|
-
#ifndef LANDLOCK_ACCESS_FS_MAKE_CHAR
|
|
63
|
-
#define LANDLOCK_ACCESS_FS_MAKE_CHAR (1ULL << 6)
|
|
64
|
-
#endif
|
|
65
|
-
#ifndef LANDLOCK_ACCESS_FS_MAKE_DIR
|
|
66
|
-
#define LANDLOCK_ACCESS_FS_MAKE_DIR (1ULL << 7)
|
|
67
|
-
#endif
|
|
68
|
-
#ifndef LANDLOCK_ACCESS_FS_MAKE_REG
|
|
69
|
-
#define LANDLOCK_ACCESS_FS_MAKE_REG (1ULL << 8)
|
|
70
|
-
#endif
|
|
71
|
-
#ifndef LANDLOCK_ACCESS_FS_MAKE_SOCK
|
|
72
|
-
#define LANDLOCK_ACCESS_FS_MAKE_SOCK (1ULL << 9)
|
|
73
|
-
#endif
|
|
74
|
-
#ifndef LANDLOCK_ACCESS_FS_MAKE_FIFO
|
|
75
|
-
#define LANDLOCK_ACCESS_FS_MAKE_FIFO (1ULL << 10)
|
|
76
|
-
#endif
|
|
77
|
-
#ifndef LANDLOCK_ACCESS_FS_MAKE_BLOCK
|
|
78
|
-
#define LANDLOCK_ACCESS_FS_MAKE_BLOCK (1ULL << 11)
|
|
79
|
-
#endif
|
|
80
|
-
#ifndef LANDLOCK_ACCESS_FS_MAKE_SYM
|
|
81
|
-
#define LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 12)
|
|
82
|
-
#endif
|
|
83
|
-
#ifndef LANDLOCK_ACCESS_FS_REFER
|
|
84
|
-
#define LANDLOCK_ACCESS_FS_REFER (1ULL << 13)
|
|
85
|
-
#endif
|
|
86
|
-
#ifndef LANDLOCK_ACCESS_FS_TRUNCATE
|
|
87
|
-
#define LANDLOCK_ACCESS_FS_TRUNCATE (1ULL << 14)
|
|
88
|
-
#endif
|
|
89
|
-
#ifndef LANDLOCK_ACCESS_FS_IOCTL_DEV
|
|
90
|
-
#define LANDLOCK_ACCESS_FS_IOCTL_DEV (1ULL << 15)
|
|
91
|
-
#endif
|
|
92
|
-
|
|
93
|
-
#ifndef LANDLOCK_ACCESS_NET_BIND_TCP
|
|
94
|
-
#define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0)
|
|
95
|
-
#endif
|
|
96
|
-
#ifndef LANDLOCK_ACCESS_NET_CONNECT_TCP
|
|
97
|
-
#define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1)
|
|
98
|
-
#endif
|
|
99
|
-
|
|
100
|
-
#ifndef O_PATH
|
|
101
|
-
#define O_PATH 010000000
|
|
102
|
-
#endif
|
|
103
|
-
|
|
104
|
-
#ifndef O_CLOEXEC
|
|
105
|
-
#define O_CLOEXEC 02000000
|
|
106
|
-
#endif
|
|
107
|
-
|
|
108
|
-
struct rb_landlock_ruleset_attr {
|
|
109
|
-
uint64_t handled_access_fs;
|
|
110
|
-
uint64_t handled_access_net;
|
|
111
|
-
uint64_t scoped;
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
struct rb_landlock_path_beneath_attr {
|
|
115
|
-
uint64_t allowed_access;
|
|
116
|
-
int32_t parent_fd;
|
|
117
|
-
} __attribute__((packed));
|
|
118
|
-
|
|
119
|
-
struct rb_landlock_net_port_attr {
|
|
120
|
-
uint64_t allowed_access;
|
|
121
|
-
uint64_t port;
|
|
122
|
-
};
|
|
123
5
|
|
|
124
6
|
static VALUE mLandlock;
|
|
125
7
|
static VALUE eLandlockError;
|
|
126
8
|
static VALUE eSyscallError;
|
|
127
9
|
|
|
128
|
-
static long ll_create_ruleset(const void *attr, size_t size, uint32_t flags) {
|
|
129
|
-
#ifdef SYS_landlock_create_ruleset
|
|
130
|
-
return syscall(SYS_landlock_create_ruleset, attr, size, flags);
|
|
131
|
-
#else
|
|
132
|
-
errno = ENOSYS;
|
|
133
|
-
return -1;
|
|
134
|
-
#endif
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
static long ll_add_rule(int ruleset_fd, int rule_type, const void *rule_attr, uint32_t flags) {
|
|
138
|
-
#ifdef SYS_landlock_add_rule
|
|
139
|
-
return syscall(SYS_landlock_add_rule, ruleset_fd, rule_type, rule_attr, flags);
|
|
140
|
-
#else
|
|
141
|
-
errno = ENOSYS;
|
|
142
|
-
return -1;
|
|
143
|
-
#endif
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
static long ll_restrict_self(int ruleset_fd, uint32_t flags) {
|
|
147
|
-
#ifdef SYS_landlock_restrict_self
|
|
148
|
-
return syscall(SYS_landlock_restrict_self, ruleset_fd, flags);
|
|
149
|
-
#else
|
|
150
|
-
errno = ENOSYS;
|
|
151
|
-
return -1;
|
|
152
|
-
#endif
|
|
153
|
-
}
|
|
154
|
-
|
|
155
10
|
static void raise_syscall_error(const char *syscall_name) {
|
|
156
11
|
int saved_errno = errno;
|
|
157
12
|
VALUE err = rb_funcall(eSyscallError, rb_intern("new"), 3,
|
|
@@ -170,16 +25,24 @@ static VALUE rb_ll_abi_version(VALUE self) {
|
|
|
170
25
|
return LONG2NUM(abi);
|
|
171
26
|
}
|
|
172
27
|
|
|
173
|
-
static VALUE rb_ll_create_ruleset(
|
|
28
|
+
static VALUE rb_ll_create_ruleset(int argc, VALUE *argv, VALUE self) {
|
|
29
|
+
VALUE fs_bits, net_bits, scoped_bits;
|
|
30
|
+
rb_scan_args(argc, argv, "21", &fs_bits, &net_bits, &scoped_bits);
|
|
31
|
+
|
|
174
32
|
struct rb_landlock_ruleset_attr attr;
|
|
175
33
|
uint64_t handled_access_net = NUM2ULL(net_bits);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
34
|
+
uint64_t scoped = NIL_P(scoped_bits) ? 0 : NUM2ULL(scoped_bits);
|
|
35
|
+
size_t attr_size = offsetof(struct rb_landlock_ruleset_attr, handled_access_net);
|
|
36
|
+
if (scoped != 0) {
|
|
37
|
+
attr_size = sizeof(struct rb_landlock_ruleset_attr);
|
|
38
|
+
} else if (handled_access_net != 0) {
|
|
39
|
+
attr_size = offsetof(struct rb_landlock_ruleset_attr, scoped);
|
|
40
|
+
}
|
|
179
41
|
|
|
180
42
|
memset(&attr, 0, sizeof(attr));
|
|
181
43
|
attr.handled_access_fs = NUM2ULL(fs_bits);
|
|
182
44
|
attr.handled_access_net = handled_access_net;
|
|
45
|
+
attr.scoped = scoped;
|
|
183
46
|
|
|
184
47
|
long fd = ll_create_ruleset(&attr, attr_size, 0);
|
|
185
48
|
if (fd < 0) raise_syscall_error("landlock_create_ruleset");
|
|
@@ -187,6 +50,8 @@ static VALUE rb_ll_create_ruleset(VALUE self, VALUE fs_bits, VALUE net_bits) {
|
|
|
187
50
|
}
|
|
188
51
|
|
|
189
52
|
static VALUE rb_ll_add_path_rule(VALUE self, VALUE ruleset_fd, VALUE path, VALUE access_bits) {
|
|
53
|
+
int ruleset = NUM2INT(ruleset_fd);
|
|
54
|
+
uint64_t allowed_access = NUM2ULL(access_bits);
|
|
190
55
|
Check_Type(path, T_STRING);
|
|
191
56
|
const char *cpath = StringValueCStr(path);
|
|
192
57
|
int parent_fd = open(cpath, O_PATH | O_CLOEXEC);
|
|
@@ -194,10 +59,10 @@ static VALUE rb_ll_add_path_rule(VALUE self, VALUE ruleset_fd, VALUE path, VALUE
|
|
|
194
59
|
|
|
195
60
|
struct rb_landlock_path_beneath_attr rule;
|
|
196
61
|
memset(&rule, 0, sizeof(rule));
|
|
197
|
-
rule.allowed_access =
|
|
62
|
+
rule.allowed_access = allowed_access;
|
|
198
63
|
rule.parent_fd = parent_fd;
|
|
199
64
|
|
|
200
|
-
long ret = ll_add_rule(
|
|
65
|
+
long ret = ll_add_rule(ruleset, LANDLOCK_RULE_PATH_BENEATH, &rule, 0);
|
|
201
66
|
int saved_errno = errno;
|
|
202
67
|
close(parent_fd);
|
|
203
68
|
if (ret < 0) {
|
|
@@ -222,6 +87,7 @@ static VALUE rb_ll_add_net_rule(VALUE self, VALUE ruleset_fd, VALUE port, VALUE
|
|
|
222
87
|
}
|
|
223
88
|
|
|
224
89
|
static VALUE rb_ll_restrict_self(VALUE self, VALUE ruleset_fd) {
|
|
90
|
+
#ifdef __linux__
|
|
225
91
|
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) {
|
|
226
92
|
raise_syscall_error("prctl(PR_SET_NO_NEW_PRIVS)");
|
|
227
93
|
}
|
|
@@ -229,6 +95,10 @@ static VALUE rb_ll_restrict_self(VALUE self, VALUE ruleset_fd) {
|
|
|
229
95
|
long ret = ll_restrict_self(NUM2INT(ruleset_fd), 0);
|
|
230
96
|
if (ret < 0) raise_syscall_error("landlock_restrict_self");
|
|
231
97
|
return Qtrue;
|
|
98
|
+
#else
|
|
99
|
+
errno = ENOSYS;
|
|
100
|
+
raise_syscall_error("landlock_restrict_self");
|
|
101
|
+
#endif
|
|
232
102
|
}
|
|
233
103
|
|
|
234
104
|
static VALUE rb_ll_close_fd(VALUE self, VALUE fd_value) {
|
|
@@ -253,7 +123,7 @@ void Init_landlock(void) {
|
|
|
253
123
|
}
|
|
254
124
|
|
|
255
125
|
rb_define_singleton_method(mLandlock, "abi_version", rb_ll_abi_version, 0);
|
|
256
|
-
rb_define_singleton_method(mLandlock, "_create_ruleset", rb_ll_create_ruleset,
|
|
126
|
+
rb_define_singleton_method(mLandlock, "_create_ruleset", rb_ll_create_ruleset, -1);
|
|
257
127
|
rb_define_singleton_method(mLandlock, "_add_path_rule", rb_ll_add_path_rule, 3);
|
|
258
128
|
rb_define_singleton_method(mLandlock, "_add_net_rule", rb_ll_add_net_rule, 3);
|
|
259
129
|
rb_define_singleton_method(mLandlock, "_restrict_self", rb_ll_restrict_self, 1);
|
|
@@ -277,4 +147,6 @@ void Init_landlock(void) {
|
|
|
277
147
|
rb_define_const(mLandlock, "ACCESS_FS_IOCTL_DEV", ULL2NUM(LANDLOCK_ACCESS_FS_IOCTL_DEV));
|
|
278
148
|
rb_define_const(mLandlock, "ACCESS_NET_BIND_TCP", ULL2NUM(LANDLOCK_ACCESS_NET_BIND_TCP));
|
|
279
149
|
rb_define_const(mLandlock, "ACCESS_NET_CONNECT_TCP", ULL2NUM(LANDLOCK_ACCESS_NET_CONNECT_TCP));
|
|
150
|
+
rb_define_const(mLandlock, "SCOPE_ABSTRACT_UNIX_SOCKET", ULL2NUM(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET));
|
|
151
|
+
rb_define_const(mLandlock, "SCOPE_SIGNAL", ULL2NUM(LANDLOCK_SCOPE_SIGNAL));
|
|
280
152
|
}
|