<<
path:
root/public/blog.git/html/src/search/liblandlock.jai
blob: dc578ed7227eae952dcca6058933625f5a63e2c8
[raw]
[clear marker]
3/** CAVE: Rulesets support only till ABI v7 !
6 /usr/include/linux/landlock.h
9 https://docs.kernel.org/userspace-api/landlock.html
12 https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/samples/landlock/sandboxer.c
17 rules := landlock_all_rules();
18 ok, ll_fd := landlock_create_ruleset(*rules);
19 assert(ok, "Create Ruleset");
21 ok = landlock_lock_privileges();
22 assert(ok, "Lock Privileges");
24 ok = landlock_restrict_self(ll_fd);
25 assert(ok, "Restrict Self");
30LL_Ruleset_Create :: enum_flags u64 {
34 // CAVE: https://docs.kernel.org/userspace-api/landlock.html#landlock-errata
38LL_Restrict_Self :: enum_flags u64 {
39 LOG_SAME_EXEC_OFF :: 1;
44landlock_rule_type :: enum #specified {
49/** Supports till ABI v7 */
50LL_Filesystem :: enum_flags u64 {
65 TRUNCATE; /** ABI v3+ */
66 IOCTL_DEV; /** ABI v5+ */
69LL_Network :: enum_flags u64 {
74LL_Scoped :: enum_flags u64 {
75 ABSTRACT_UNIX_SOCKET :: 1;
80landlock_ruleset_attr :: struct {
81 handled_access_fs: LL_Filesystem;
82 handled_access_net: LL_Network;
86landlock_path_beneath_attr :: struct {
87 allowed_access: LL_Filesystem;
91landlock_net_port_attr :: struct {
92 allowed_access: LL_Network;
97/** https://man7.org/linux/man-pages/man2/landlock_create_ruleset.2.html
99 CAVE: Flags must be 0 if attr is used.
100 Otherwise flags can be set to: .VERSION
102 Quote from above man page:
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. [...]
111landlock_create_ruleset :: (
112 ruleset: *landlock_ruleset_attr,
113 flags: LL_Ruleset_Create = .NULL,
114 loc := #caller_location
116 -> (ok: bool, fd: int)
118 assert(CPU == .X64 && OS == .LINUX, "Sorry, only support for x64 Linux!");
120 size := ifx ruleset == null then 0 else size_of(landlock_ruleset_attr);
122 SYS_LANDLOCK_CREATE_RULESET,
129 print_last_error(ERR_LL_CREATE_RULESET, loc);
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);
142 print_last_error(ERR_LL_RESTRICT);
148landlock_add_rule :: inline (
150 rule_attr: *landlock_net_port_attr
154 return add_rule(ruleset_fd, .NET_PORT, rule_attr);
157landlock_add_rule :: inline (
159 rule_attr: *landlock_path_beneath_attr
163 return add_rule(ruleset_fd, .PATH_BENEATH, rule_attr);
168 * The following procedures are non standard and added by me
172/** According to the man pages you must lock privileges
173 before applying any landlock rules.
175 https://man7.org/linux/man-pages/man7/landlock.7.html
178 'man prctl' on the return value:
180 On success, a nonnegative value is returned.
181 On error, -1 is returned, and errno is set to indicate the error.
184landlock_lock_privileges :: () -> ok: bool {
185 ok := prctl(PR_SET_NO_NEW_PRIVS, 1);
187 print_last_error(ERR_PRCTL);
193landlock_is_version_equal_or_higher :: (pinned_version: int, $print_version := false) -> bool {
194 ok, system_version := landlock_create_ruleset(null, .VERSION);
196 log_error("Could not determine version");
200 #if print_version then log("Landlock System Version: %", system_version);
201 return system_version >= pinned_version;
204/** Returns _all_ available rules, essentially locking everything.
206 This might be useful if you want more a whitelist, rather than
207 a blacklist. Remove flags like this:
209 my_ruleset.handled_access_fs &= ~TRUNCATE;
211 Or if you want to remove items based on the supported ABI version.
213landlock_all_rules :: () -> landlock_ruleset_attr {
215 ll_fs := type_info(LL_Filesystem);
216 ll_net := type_info(LL_Network);
217 ll_scoped := type_info(LL_Scoped);
222 append(*sb, "return .{ handled_access_fs = ");
224 for ll_fs.names array_add(*temp, tprint(".%", it));
225 fs_values := join(..temp, " | ");
227 append(*sb, fs_values);
228 append(*sb, ", handled_access_net = ");
232 for ll_net.names array_add(*temp, tprint(".%", it));
233 net_values := join(..temp, " | ");
235 append(*sb, net_values);
236 append(*sb, ", scoped = ");
240 for ll_scoped.names array_add(*temp, tprint(".%", it));
241 scoped_values := join(..temp, " | ");
243 append(*sb, scoped_values);
246 code := builder_to_string(*sb);
256libc :: #library,system "libc";
259/** This assertion is important, since syscall numbers might shift when using
262#assert(CPU == .X64 && OS == .LINUX) "Only x64 Linux support!";
267 Glibc didn't implented landlock functions yet, so we have to wrap
268 the syscalls ourselves.
270 Jai's Linux-faced stdlib does not have the syscall numbers for
271 the landlock functions, so we stole them from the Android lib.
272 Which are identical with x64 Linux anyway.
276SYS_LANDLOCK_CREATE_RULESET :: 444;
277SYS_LANDLOCK_ADD_RULE :: 445;
278SYS_LANDLOCK_RESTRICT_SELF :: 446;
280/** /usr/include/linux/prctl.h */
281PR_SET_NO_NEW_PRIVS :: 38;
290ERR_LL_CREATE_RULESET :: #code {
293 log_error("Landlock is supported by the kernel but disabled at boot time.");
295 log_error("Unknown flags, or unknown access, or too small size.");
297 log_error("size is too big.");
299 log_error("attr was not a valid address.");
301 log_error("Empty accesses (i.e., attr did not specify any access rights to restrict).");
302 case; log_error("Uncovered error: %", err);
306ERR_LL_ADD_RULE :: #code {
309 log_error("rule_type is set correctly, but TCP is not supported by the kernel.");
311 log_error("Landlock is supported, but this feature is disabled at boot time by the kernel.");
313 log_error("Flags is not 0 OR inalivid port number supplied.");
315 log_error("Empty accesses (i.e., rule_attr.allowed_access is 0).");
317 log_error("No valid file descriptor supplied in `ruleset_fd` or `rule_attr`.");
319 log_error("No valid file descriptor in `ruleset_fd`.");
321 log_error("`ruleset_fd` has no write access to the underlying ruleset.");
323 log_error("`rule_attr` was not a valid address.");
327ERR_LL_RESTRICT :: #code {
330 log_error("Landlock is supported by the kernel but disabled at boot time.");
332 log_error("flags is not 0.");
334 log_error("ruleset_fd is not a file descriptor for the current thread.");
336 log_error("ruleset_fd is not a ruleset file descriptor.");
338 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.");
340 log_error("The maximum number of composed rulesets is reached for the calling thread. This limit is currently 64.");
347 log_error("OP value is not recognized/supported");
352prctl :: (op: int, a2 := 0, a3 := 0, a4 := 0, a5 := 0) -> int #foreign libc;
354/** https://man7.org/linux/man-pages/man2/landlock_add_rule.2.html */
357 rule_type: landlock_rule_type,
362 /** According to above man page, flags MUST be ZERO */
363 ok := syscall(SYS_LANDLOCK_ADD_RULE, ruleset_fd, rule_type, rule_attr, 0);
365 print_last_error(ERR_LL_ADD_RULE);
371print_last_error :: ($code: Code, loc := #caller_location) {
375 log_error("-- Error --------------------");
378 #insert,scope() code;
380 log_error("------------------------------");