Logo

index : 0x2lib

Library extension for Jai

  • summary
  • about
  • tree
  • log
  • branches
<< path: root/public/0x2lib.git/html/0x2_Landlock.jai blob: 3c7cfb4218cdfbb16900cee3c93fc9c16cf178fe [raw] [clear marker]

        
0
1
2
3/** CAVE: Rulesets support only till ABI v7 !
4
5 Source
6 /usr/include/linux/landlock.h
7
8 Guide
9 https://docs.kernel.org/userspace-api/landlock.html
10
11 Example in C
12 https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/samples/landlock/sandboxer.c
13
14
15 Very short example
16
17 rules := landlock_all_rules();
18 ok, ll_fd := landlock_create_ruleset(*rules);
19 assert(ok, "Create Ruleset");
20
21 ok = landlock_lock_privileges();
22 assert(ok, "Lock Privileges");
23
24 ok = landlock_restrict_self(ll_fd);
25 assert(ok, "Restrict Self");
26*/
27
28
29
30LL_Ruleset_Create :: enum_flags u64 {
31 NULL :: 0;
32 VERSION :: 1;
33
34 // CAVE: https://docs.kernel.org/userspace-api/landlock.html#landlock-errata
35 ERRATA;
36}
37
38LL_Restrict_Self :: enum_flags u64 {
39 LOG_SAME_EXEC_OFF :: 1;
40 LOG_NEW_EXEC_ON;
41 LOG_SUBDOMAINS_OFF;
42}
43
44landlock_rule_type :: enum #specified {
45 PATH_BENEATH :: 1;
46 NET_PORT :: 2;
47}
48
49/** Supports till ABI v7 */
50LL_Filesystem :: enum_flags u64 {
51 EXECUTE :: 1;
52 WRITE_FILE;
53 READ_FILE;
54 READ_DIR;
55 REMOVE_DIR;
56 REMOVE_FILE;
57 MAKE_CHAR;
58 MAKE_DIR;
59 MAKE_REG;
60 MAKE_SOCK;
61 MAKE_FIFO;
62 MAKE_BLOCK;
63 MAKE_SYM;
64 REFER; /** ABI v2+ */
65 TRUNCATE; /** ABI v3+ */
66 IOCTL_DEV; /** ABI v5+ */
67}
68
69LL_Network :: enum_flags u64 {
70 BIND_TCP :: 1;
71 CONNECT_TCP;
72}
73
74LL_Scoped :: enum_flags u64 {
75 ABSTRACT_UNIX_SOCKET :: 1;
76 SIGNAL;
77}
78
79
80landlock_ruleset_attr :: struct {
81 handled_access_fs: LL_Filesystem;
82 handled_access_net: LL_Network;
83 scoped: LL_Scoped;
84}
85
86landlock_path_beneath_attr :: struct {
87 allowed_access: LL_Filesystem;
88 parent_fd: s32;
89} #no_padding
90
91landlock_net_port_attr :: struct {
92 allowed_access: LL_Network;
93 port: u64;
94}
95
96
97/** https://man7.org/linux/man-pages/man2/landlock_create_ruleset.2.html
98
99 CAVE: Flags must be 0 if attr is used.
100 Otherwise flags can be set to: .VERSION
101
102 Quote from above man page:
103
104 If attr is NULL and size is 0, then the returned value is
105 the highest supported Landlock ABI version (starting at 1).
106 This version can be used for a best-effort security
107 approach, which is encouraged when user space is not pinned
108 to a specific kernel version. [...]
109
110*/
111landlock_create_ruleset :: (
112 ruleset: *landlock_ruleset_attr,
113 flags: LL_Ruleset_Create = .NULL,
114 loc := #caller_location
115)
116 -> (ok: bool, fd: int)
117{
118 assert(CPU == .X64 && OS == .LINUX, "Sorry, only support for x64 Linux!");
119
120 size := ifx ruleset == null then 0 else size_of(landlock_ruleset_attr);
121 fd := syscall(
122 SYS_LANDLOCK_CREATE_RULESET,
123 ruleset,
124 size,
125 cast(u64, flags)
126 );
127
128 if fd < 0 {
129 print_last_error(ERR_LL_CREATE_RULESET, loc);
130 return false, -1;
131 }
132
133 return true, fd;
134}
135
136
137/** https://man7.org/linux/man-pages/man2/landlock_restrict_self.2.html */
138landlock_restrict_self :: (ruleset_fd: int) -> ok: bool {
139 /** According to above man page, flags MUST be ZERO */
140 ok := syscall(SYS_LANDLOCK_RESTRICT_SELF, ruleset_fd, 0);
141 if ok < 0 {
142 print_last_error(ERR_LL_RESTRICT);
143 return false;
144 }
145 return true;
146}
147
148landlock_add_rule :: (
149 ruleset_fd: int,
150 rule_attr: *landlock_net_port_attr
151)
152 -> ok: bool
153{
154 return add_rule(ruleset_fd, .NET_PORT, rule_attr);
155}
156
157landlock_add_rule :: (
158 ruleset_fd: int,
159 rule_attr: *landlock_path_beneath_attr
160)
161 -> ok: bool
162{
163 return add_rule(ruleset_fd, .PATH_BENEATH, rule_attr);
164}
165
166
167/**
168 * The following procedures are non standard and added by me
169 */
170
171
172/** According to the man pages you must lock privileges
173 before applying any landlock rules.
174
175 https://man7.org/linux/man-pages/man7/landlock.7.html
176
177
178 'man prctl' on the return value:
179
180 On success, a nonnegative value is returned.
181 On error, -1 is returned, and errno is set to indicate the error.
182
183*/
184landlock_lock_privileges :: () -> ok: bool {
185 ok := prctl(PR_SET_NO_NEW_PRIVS, 1);
186 if ok < 0 {
187 print_last_error(ERR_PRCTL);
188 return false;
189 }
190 return true;
191}
192
193landlock_is_version_equal_or_higher :: (pinned_version: int, $print_version := false) -> bool {
194 ok, system_version := landlock_create_ruleset(null, .VERSION);
195 if !ok {
196 log_error("Could not determine version");
197 return false;
198 }
199
200 #if print_version then log("Landlock System Version: %", system_version);
201 return system_version >= pinned_version;
202}
203
204/** Returns _all_ available rules, essentially locking everything.
205
206 This might be useful if you want more a whitelist, rather than
207 a blacklist. Remove flags like this:
208
209 my_ruleset.handled_access_fs &= ~TRUNCATE;
210
211 Or if you want to remove items based on the supported ABI version.
212*/
213landlock_all_rules :: () -> landlock_ruleset_attr {
214 #insert -> string {
215 ll_fs := type_info(LL_Filesystem);
216 ll_net := type_info(LL_Network);
217 ll_scoped := type_info(LL_Scoped);
218
219 tmp: [..]string;
220
221 sb: String_Builder;
222 append(*sb, "return .{ handled_access_fs = ");
223
224 for ll_fs.names array_add(*tmp, tprint(".%", it));
225 fs_values := join(..tmp, " | ");
226
227 append(*sb, fs_values);
228 append(*sb, ", handled_access_net = ");
229
230 array_reset(*tmp);
231
232 for ll_net.names array_add(*tmp, tprint(".%", it));
233 net_values := join(..tmp, " | ");
234
235 append(*sb, net_values);
236 append(*sb, ", scoped = ");
237
238 array_reset(*tmp);
239
240 for ll_scoped.names array_add(*tmp, tprint(".%", it));
241 scoped_values := join(..tmp, " | ");
242
243 append(*sb, scoped_values);
244 append(*sb, ", };");
245
246 code := builder_to_string(*sb);
247 return code;
248 };
249}
250
251
252#scope_file
253
254
255#import "POSIX";
256
257using,only(
258 String_Builder, builder_to_string, append,
259 array_reset, array_add,
260 tprint, log, log_error,
261 assert
262) Basic :: #import "Basic";
263
264using,only(join) String :: #import "String";
265
266libc :: #library,system "libc";
267
268
269/** This assertion is important, since syscall numbers might shift when using
270 a different arch!
271*/
272#assert(CPU == .X64 && OS == .LINUX) "Only x64 Linux support!";
273
274
275/** 2026-04-23
276
277 Glibc didn't implented landlock functions yet, so we have to wrap
278 the syscalls ourselves.
279
280 Jai's Linux-faced stdlib does not have the syscall numbers for
281 the landlock functions, so we stole them from the Android lib.
282 Which are identical with x64 Linux anyway.
283
284*/
285
286SYS_LANDLOCK_CREATE_RULESET :: 444;
287SYS_LANDLOCK_ADD_RULE :: 445;
288SYS_LANDLOCK_RESTRICT_SELF :: 446;
289
290/** /usr/include/linux/prctl.h */
291PR_SET_NO_NEW_PRIVS :: 38;
292
293
294Error_Kind :: enum {
295 LANDLOCK;
296 PRCTL;
297}
298
299
300ERR_LL_CREATE_RULESET :: #code {
301 if err == {
302 case EOPNOTSUPP;
303 log_error("Landlock is supported by the kernel but disabled at boot time.");
304 case EINVAL;
305 log_error("Unknown flags, or unknown access, or too small size.");
306 case E2BIG;
307 log_error("size is too big.");
308 case EFAULT;
309 log_error("attr was not a valid address.");
310 case ENOMSG;
311 log_error("Empty accesses (i.e., attr did not specify any access rights to restrict).");
312 case; log_error("Uncovered error: %", err);
313 }
314}
315
316ERR_LL_ADD_RULE :: #code {
317 if err == {
318 case EAFNOSUPPORT;
319 log_error("rule_type is set correctly, but TCP is not supported by the kernel.");
320 case EOPNOTSUPP;
321 log_error("Landlock is supported, but this feature is disabled at boot time by the kernel.");
322 case EINVAL;
323 log_error("Flags is not 0 OR inalivid port number supplied.");
324 case ENOMSG;
325 log_error("Empty accesses (i.e., rule_attr.allowed_access is 0).");
326 case EBADF;
327 log_error("No valid file descriptor supplied in `ruleset_fd` or `rule_attr`.");
328 case EBADFD;
329 log_error("No valid file descriptor in `ruleset_fd`.");
330 case EPERM;
331 log_error("`ruleset_fd` has no write access to the underlying ruleset.");
332 case EFAULT;
333 log_error("`rule_attr` was not a valid address.");
334 }
335}
336
337ERR_LL_RESTRICT :: #code {
338 if err == {
339 case EOPNOTSUPP;
340 log_error("Landlock is supported by the kernel but disabled at boot time.");
341 case EINVAL;
342 log_error("flags is not 0.");
343 case EBADF;
344 log_error("ruleset_fd is not a file descriptor for the current thread.");
345 case EBADFD;
346 log_error("ruleset_fd is not a ruleset file descriptor.");
347 case EPERM;
348 log_error("ruleset_fd has no read access to the underlying ruleset.\nOr the calling thread is not running with no_new_privs,\nor it doesn't have the CAP_SYS_ADMIN in its user namespace.");
349 case E2BIG;
350 log_error("The maximum number of composed rulesets is reached for the calling thread. This limit is currently 64.");
351 }
352}
353
354ERR_PRCTL :: #code {
355 if err == {
356 case EINVAL;
357 log_error("OP value is not recognized/supported");
358 }
359}
360
361
362prctl :: (op: int, a2 := 0, a3 := 0, a4 := 0, a5 := 0) -> int #foreign libc;
363
364/** https://man7.org/linux/man-pages/man2/landlock_add_rule.2.html */
365add_rule :: (
366 ruleset_fd: int,
367 rule_type: landlock_rule_type,
368 rule_attr: *void
369)
370 -> ok: bool
371{
372 /** According to above man page, flags MUST be ZERO */
373 ok := syscall(SYS_LANDLOCK_ADD_RULE, ruleset_fd, rule_type, rule_attr, 0);
374 if ok < 0 {
375 print_last_error(ERR_LL_ADD_RULE);
376 return false;
377 }
378 return true;
379}
380
381print_last_error :: ($code: Code, loc := #caller_location) {
382 err := errno();
383 if err == 0 return;
384
385 log_error("-- Error --------------------");
386 log_error("%", loc);
387
388 #insert,scope() code;
389
390 log_error("------------------------------");
391}
392
393
394
395/*
396------------------------------------------------------------------------------
397This software is available under 2 licenses -- choose whichever you prefer.
398------------------------------------------------------------------------------
399ALTERNATIVE A - MIT License
400Copyright (c) 2026 Adam Blazeowsky
401Permission is hereby granted, free of charge, to any person obtaining a copy of
402this software and associated documentation files (the "Software"), to deal in
403the Software without restriction, including without limitation the rights to
404use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
405of the Software, and to permit persons to whom the Software is furnished to do
406so, subject to the following conditions:
407The above copyright notice and this permission notice shall be included in all
408copies or substantial portions of the Software.
409THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
410IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
411FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
412AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
413LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
414OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
415SOFTWARE.
416------------------------------------------------------------------------------
417ALTERNATIVE B - Public Domain (www.unlicense.org)
418This is free and unencumbered software released into the public domain.
419Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
420software, either in source code form or as a compiled binary, for any purpose,
421commercial or non-commercial, and by any means.
422In jurisdictions that recognize copyright laws, the author or authors of this
423software dedicate any and all copyright interest in the software to the public
424domain. We make this dedication for the benefit of the public at large and to
425the detriment of our heirs and successors. We intend this dedication to be an
426overt act of relinquishment in perpetuity of all present and future rights to
427this software under copyright law.
428THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
429IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
430FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
431AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
432ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
433WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
434------------------------------------------------------------------------------
435*/
436
437
438
Copyright 2026  E766CB298A6D1E64 | Git-Thing heavily inspired by cgit