/** CAVE: Rulesets support only till ABI v7 ! Source /usr/include/linux/landlock.h Guide https://docs.kernel.org/userspace-api/landlock.html Example in C https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/samples/landlock/sandboxer.c Very short example rules := landlock_all_rules(); ok, ll_fd := landlock_create_ruleset(*rules); assert(ok, "Create Ruleset"); ok = landlock_lock_privileges(); assert(ok, "Lock Privileges"); ok = landlock_restrict_self(ll_fd); assert(ok, "Restrict Self"); */ LL_Ruleset_Create :: enum_flags u64 { NULL :: 0; VERSION :: 1; // CAVE: https://docs.kernel.org/userspace-api/landlock.html#landlock-errata ERRATA; } LL_Restrict_Self :: enum_flags u64 { LOG_SAME_EXEC_OFF :: 1; LOG_NEW_EXEC_ON; LOG_SUBDOMAINS_OFF; } landlock_rule_type :: enum #specified { PATH_BENEATH :: 1; NET_PORT :: 2; } /** Supports till ABI v7 */ LL_Filesystem :: enum_flags u64 { EXECUTE :: 1; WRITE_FILE; READ_FILE; READ_DIR; REMOVE_DIR; REMOVE_FILE; MAKE_CHAR; MAKE_DIR; MAKE_REG; MAKE_SOCK; MAKE_FIFO; MAKE_BLOCK; MAKE_SYM; REFER; /** ABI v2+ */ TRUNCATE; /** ABI v3+ */ IOCTL_DEV; /** ABI v5+ */ } LL_Network :: enum_flags u64 { BIND_TCP :: 1; CONNECT_TCP; } LL_Scoped :: enum_flags u64 { ABSTRACT_UNIX_SOCKET :: 1; SIGNAL; } landlock_ruleset_attr :: struct { handled_access_fs: LL_Filesystem; handled_access_net: LL_Network; scoped: LL_Scoped; } landlock_path_beneath_attr :: struct { allowed_access: LL_Filesystem; parent_fd: s32; } #no_padding landlock_net_port_attr :: struct { allowed_access: LL_Network; port: u64; } /** https://man7.org/linux/man-pages/man2/landlock_create_ruleset.2.html CAVE: Flags must be 0 if attr is used. Otherwise flags can be set to: .VERSION Quote from above man page: If attr is NULL and size is 0, then the returned value is the highest supported Landlock ABI version (starting at 1). This version can be used for a best-effort security approach, which is encouraged when user space is not pinned to a specific kernel version. [...] */ landlock_create_ruleset :: ( ruleset: *landlock_ruleset_attr, flags: LL_Ruleset_Create = .NULL, loc := #caller_location ) -> (ok: bool, fd: int) { assert(CPU == .X64 && OS == .LINUX, "Sorry, only support for x64 Linux!"); size := ifx ruleset == null then 0 else size_of(landlock_ruleset_attr); fd := syscall( SYS_LANDLOCK_CREATE_RULESET, ruleset, size, cast(u64, flags) ); if fd < 0 { print_last_error(ERR_LL_CREATE_RULESET, loc); return false, -1; } return true, fd; } /** https://man7.org/linux/man-pages/man2/landlock_restrict_self.2.html */ landlock_restrict_self :: (ruleset_fd: int) -> ok: bool { /** According to above man page, flags MUST be ZERO */ ok := syscall(SYS_LANDLOCK_RESTRICT_SELF, ruleset_fd, 0); if ok < 0 { print_last_error(ERR_LL_RESTRICT); return false; } return true; } landlock_add_rule :: ( ruleset_fd: int, rule_attr: *landlock_net_port_attr ) -> ok: bool { return add_rule(ruleset_fd, .NET_PORT, rule_attr); } landlock_add_rule :: ( ruleset_fd: int, rule_attr: *landlock_path_beneath_attr ) -> ok: bool { return add_rule(ruleset_fd, .PATH_BENEATH, rule_attr); } /** * The following procedures are non standard and added by me */ /** According to the man pages you must lock privileges before applying any landlock rules. https://man7.org/linux/man-pages/man7/landlock.7.html 'man prctl' on the return value: On success, a nonnegative value is returned. On error, -1 is returned, and errno is set to indicate the error. */ landlock_lock_privileges :: () -> ok: bool { ok := prctl(PR_SET_NO_NEW_PRIVS, 1); if ok < 0 { print_last_error(ERR_PRCTL); return false; } return true; } landlock_is_version_equal_or_higher :: (pinned_version: int, $print_version := false) -> bool { ok, system_version := landlock_create_ruleset(null, .VERSION); if !ok { log_error("Could not determine version"); return false; } #if print_version then log("Landlock System Version: %", system_version); return system_version >= pinned_version; } /** Returns _all_ available rules, essentially locking everything. This might be useful if you want more a whitelist, rather than a blacklist. Remove flags like this: my_ruleset.handled_access_fs &= ~TRUNCATE; Or if you want to remove items based on the supported ABI version. */ landlock_all_rules :: () -> landlock_ruleset_attr { #insert -> string { ll_fs := type_info(LL_Filesystem); ll_net := type_info(LL_Network); ll_scoped := type_info(LL_Scoped); tmp: [..]string; sb: String_Builder; append(*sb, "return .{ handled_access_fs = "); for ll_fs.names array_add(*tmp, tprint(".%", it)); fs_values := join(..tmp, " | "); append(*sb, fs_values); append(*sb, ", handled_access_net = "); array_reset(*tmp); for ll_net.names array_add(*tmp, tprint(".%", it)); net_values := join(..tmp, " | "); append(*sb, net_values); append(*sb, ", scoped = "); array_reset(*tmp); for ll_scoped.names array_add(*tmp, tprint(".%", it)); scoped_values := join(..tmp, " | "); append(*sb, scoped_values); append(*sb, ", };"); code := builder_to_string(*sb); return code; }; } #scope_file #import "POSIX"; using,only( String_Builder, builder_to_string, append, array_reset, array_add, tprint, log, log_error, assert ) Basic :: #import "Basic"; using,only(join) String :: #import "String"; libc :: #library,system "libc"; /** This assertion is important, since syscall numbers might shift when using a different arch! */ #assert(CPU == .X64 && OS == .LINUX) "Only x64 Linux support!"; /** 2026-04-23 Glibc didn't implented landlock functions yet, so we have to wrap the syscalls ourselves. Jai's Linux-faced stdlib does not have the syscall numbers for the landlock functions, so we stole them from the Android lib. Which are identical with x64 Linux anyway. */ SYS_LANDLOCK_CREATE_RULESET :: 444; SYS_LANDLOCK_ADD_RULE :: 445; SYS_LANDLOCK_RESTRICT_SELF :: 446; /** /usr/include/linux/prctl.h */ PR_SET_NO_NEW_PRIVS :: 38; Error_Kind :: enum { LANDLOCK; PRCTL; } ERR_LL_CREATE_RULESET :: #code { if err == { case EOPNOTSUPP; log_error("Landlock is supported by the kernel but disabled at boot time."); case EINVAL; log_error("Unknown flags, or unknown access, or too small size."); case E2BIG; log_error("size is too big."); case EFAULT; log_error("attr was not a valid address."); case ENOMSG; log_error("Empty accesses (i.e., attr did not specify any access rights to restrict)."); case; log_error("Uncovered error: %", err); } } ERR_LL_ADD_RULE :: #code { if err == { case EAFNOSUPPORT; log_error("rule_type is set correctly, but TCP is not supported by the kernel."); case EOPNOTSUPP; log_error("Landlock is supported, but this feature is disabled at boot time by the kernel."); case EINVAL; log_error("Flags is not 0 OR inalivid port number supplied."); case ENOMSG; log_error("Empty accesses (i.e., rule_attr.allowed_access is 0)."); case EBADF; log_error("No valid file descriptor supplied in `ruleset_fd` or `rule_attr`."); case EBADFD; log_error("No valid file descriptor in `ruleset_fd`."); case EPERM; log_error("`ruleset_fd` has no write access to the underlying ruleset."); case EFAULT; log_error("`rule_attr` was not a valid address."); } } ERR_LL_RESTRICT :: #code { if err == { case EOPNOTSUPP; log_error("Landlock is supported by the kernel but disabled at boot time."); case EINVAL; log_error("flags is not 0."); case EBADF; log_error("ruleset_fd is not a file descriptor for the current thread."); case EBADFD; log_error("ruleset_fd is not a ruleset file descriptor."); case EPERM; 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."); case E2BIG; log_error("The maximum number of composed rulesets is reached for the calling thread. This limit is currently 64."); } } ERR_PRCTL :: #code { if err == { case EINVAL; log_error("OP value is not recognized/supported"); } } prctl :: (op: int, a2 := 0, a3 := 0, a4 := 0, a5 := 0) -> int #foreign libc; /** https://man7.org/linux/man-pages/man2/landlock_add_rule.2.html */ add_rule :: ( ruleset_fd: int, rule_type: landlock_rule_type, rule_attr: *void ) -> ok: bool { /** According to above man page, flags MUST be ZERO */ ok := syscall(SYS_LANDLOCK_ADD_RULE, ruleset_fd, rule_type, rule_attr, 0); if ok < 0 { print_last_error(ERR_LL_ADD_RULE); return false; } return true; } print_last_error :: ($code: Code, loc := #caller_location) { err := errno(); if err == 0 return; log_error("-- Error --------------------"); log_error("%", loc); #insert,scope() code; log_error("------------------------------"); } /* ------------------------------------------------------------------------------ This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------ ALTERNATIVE A - MIT License Copyright (c) 2026 Adam Blazeowsky Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ ALTERNATIVE B - Public Domain (www.unlicense.org) This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ */