Author:ptrace Comitter:ptrace Date:2026-08-16 05:44:35 UTC

Added landlock & seccomp

diff --git a/.gitignore b/.gitignore index 7a80058..3536a59 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .build /tests/run_tests /tests/*.txt diff --git a/0x2_Landlock.jai b/0x2_Landlock.jai new file mode 100644 index 0000000..3c7cfb4 --- /dev/null +++ b/0x2_Landlock.jai @@ -0,0 +1,438 @@ /** 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. ------------------------------------------------------------------------------ */ diff --git a/0x2_Seccomp.jai b/0x2_Seccomp.jai new file mode 100644 index 0000000..a0ea185 --- /dev/null +++ b/0x2_Seccomp.jai @@ -0,0 +1,531 @@ /** Seccomp bindings         /usr/include/seccomp.h          -> public APIs         /usr/include/asm/unistd_64.h    -> Syscalls */ #assert(CPU == .X64 && OS == .LINUX) "Only x64 Linux support. Sorry!"; Scmp_Action :: enum #specified {     KILL_PROCESS :: 0x80000000;     KILL_THREAD  :: 0x00000000;     /** KILL omitted because only used for backward compatibility */     TRAP         :: 0x00030000;     NOTIFY       :: 0x7fc00000;     /** libseccomp has macros for providing own error codes.         But in an enum we can't do that, so we're outsourcing it to a         proc.     */     LOG          :: 0x7ffc0000;     ALLOW        :: 0x7fff0000;     USER_NOTIF   :: 0x7fc00000; } // TODO: bindings generator for libseccomp /** Enum from unistd_64.h     There's also the idea of translating the SCMP_SYS macro to Jai and     use a more generic approach with asm/unistd.h as presented in libseccomp.     But Jai only supports X64 OOTB anyway, so it makes     no sense to go the extra mile for a generic approach.     The syscall enum is extracted via a script from unistd_64.h directly.     I'll create a generate.jai for libseccomp, so we do not rely on a Python script     for that.     Note: It is very important to assert the exact arch, because otherwise we'll           filter the wrong syscalls! Comptime AND runtime assertion! */ Scmp_Syscall_x64 :: enum #specified {     READ                    :: 0;     WRITE                   :: 1;     OPEN                    :: 2;     CLOSE                   :: 3;     STAT                    :: 4;     FSTAT                   :: 5;     LSTAT                   :: 6;     POLL                    :: 7;     LSEEK                   :: 8;     MMAP                    :: 9;     MPROTECT                :: 10;     MUNMAP                  :: 11;     BRK                     :: 12;     RT_SIGACTION            :: 13;     RT_SIGPROCMASK          :: 14;     RT_SIGRETURN            :: 15;     IOCTL                   :: 16;     PREAD64                 :: 17;     PWRITE64                :: 18;     READV                   :: 19;     WRITEV                  :: 20;     ACCESS                  :: 21;     PIPE                    :: 22;     SELECT                  :: 23;     SCHED_YIELD             :: 24;     MREMAP                  :: 25;     MSYNC                   :: 26;     MINCORE                 :: 27;     MADVISE                 :: 28;     SHMGET                  :: 29;     SHMAT                   :: 30;     SHMCTL                  :: 31;     DUP                     :: 32;     DUP2                    :: 33;     PAUSE                   :: 34;     NANOSLEEP               :: 35;     GETITIMER               :: 36;     ALARM                   :: 37;     SETITIMER               :: 38;     GETPID                  :: 39;     SENDFILE                :: 40;     SOCKET                  :: 41;     CONNECT                 :: 42;     ACCEPT                  :: 43;     SENDTO                  :: 44;     RECVFROM                :: 45;     SENDMSG                 :: 46;     RECVMSG                 :: 47;     SHUTDOWN                :: 48;     BIND                    :: 49;     LISTEN                  :: 50;     GETSOCKNAME             :: 51;     GETPEERNAME             :: 52;     SOCKETPAIR              :: 53;     SETSOCKOPT              :: 54;     GETSOCKOPT              :: 55;     CLONE                   :: 56;     FORK                    :: 57;     VFORK                   :: 58;     EXECVE                  :: 59;     EXIT                    :: 60;     WAIT4                   :: 61;     KILL                    :: 62;     UNAME                   :: 63;     SEMGET                  :: 64;     SEMOP                   :: 65;     SEMCTL                  :: 66;     SHMDT                   :: 67;     MSGGET                  :: 68;     MSGSND                  :: 69;     MSGRCV                  :: 70;     MSGCTL                  :: 71;     FCNTL                   :: 72;     FLOCK                   :: 73;     FSYNC                   :: 74;     FDATASYNC               :: 75;     TRUNCATE                :: 76;     FTRUNCATE               :: 77;     GETDENTS                :: 78;     GETCWD                  :: 79;     CHDIR                   :: 80;     FCHDIR                  :: 81;     RENAME                  :: 82;     MKDIR                   :: 83;     RMDIR                   :: 84;     CREAT                   :: 85;     LINK                    :: 86;     UNLINK                  :: 87;     SYMLINK                 :: 88;     READLINK                :: 89;     CHMOD                   :: 90;     FCHMOD                  :: 91;     CHOWN                   :: 92;     FCHOWN                  :: 93;     LCHOWN                  :: 94;     UMASK                   :: 95;     GETTIMEOFDAY            :: 96;     GETRLIMIT               :: 97;     GETRUSAGE               :: 98;     SYSINFO                 :: 99;     TIMES                   :: 100;     PTRACE                  :: 101;     GETUID                  :: 102;     SYSLOG                  :: 103;     GETGID                  :: 104;     SETUID                  :: 105;     SETGID                  :: 106;     GETEUID                 :: 107;     GETEGID                 :: 108;     SETPGID                 :: 109;     GETPPID                 :: 110;     GETPGRP                 :: 111;     SETSID                  :: 112;     SETREUID                :: 113;     SETREGID                :: 114;     GETGROUPS               :: 115;     SETGROUPS               :: 116;     SETRESUID               :: 117;     GETRESUID               :: 118;     SETRESGID               :: 119;     GETRESGID               :: 120;     GETPGID                 :: 121;     SETFSUID                :: 122;     SETFSGID                :: 123;     GETSID                  :: 124;     CAPGET                  :: 125;     CAPSET                  :: 126;     RT_SIGPENDING           :: 127;     RT_SIGTIMEDWAIT         :: 128;     RT_SIGQUEUEINFO         :: 129;     RT_SIGSUSPEND           :: 130;     SIGALTSTACK             :: 131;     UTIME                   :: 132;     MKNOD                   :: 133;     USELIB                  :: 134;     PERSONALITY             :: 135;     USTAT                   :: 136;     STATFS                  :: 137;     FSTATFS                 :: 138;     SYSFS                   :: 139;     GETPRIORITY             :: 140;     SETPRIORITY             :: 141;     SCHED_SETPARAM          :: 142;     SCHED_GETPARAM          :: 143;     SCHED_SETSCHEDULER      :: 144;     SCHED_GETSCHEDULER      :: 145;     SCHED_GET_PRIORITY_MAX  :: 146;     SCHED_GET_PRIORITY_MIN  :: 147;     SCHED_RR_GET_INTERVAL   :: 148;     MLOCK                   :: 149;     MUNLOCK                 :: 150;     MLOCKALL                :: 151;     MUNLOCKALL              :: 152;     VHANGUP                 :: 153;     MODIFY_LDT              :: 154;     PIVOT_ROOT              :: 155;     _SYSCTL                 :: 156;     PRCTL                   :: 157;     ARCH_PRCTL              :: 158;     ADJTIMEX                :: 159;     SETRLIMIT               :: 160;     CHROOT                  :: 161;     SYNC                    :: 162;     ACCT                    :: 163;     SETTIMEOFDAY            :: 164;     MOUNT                   :: 165;     UMOUNT2                 :: 166;     SWAPON                  :: 167;     SWAPOFF                 :: 168;     REBOOT                  :: 169;     SETHOSTNAME             :: 170;     SETDOMAINNAME           :: 171;     IOPL                    :: 172;     IOPERM                  :: 173;     CREATE_MODULE           :: 174;     INIT_MODULE             :: 175;     DELETE_MODULE           :: 176;     GET_KERNEL_SYMS         :: 177;     QUERY_MODULE            :: 178;     QUOTACTL                :: 179;     NFSSERVCTL              :: 180;     GETPMSG                 :: 181;     PUTPMSG                 :: 182;     AFS_SYSCALL             :: 183;     TUXCALL                 :: 184;     SECURITY                :: 185;     GETTID                  :: 186;     READAHEAD               :: 187;     SETXATTR                :: 188;     LSETXATTR               :: 189;     FSETXATTR               :: 190;     GETXATTR                :: 191;     LGETXATTR               :: 192;     FGETXATTR               :: 193;     LISTXATTR               :: 194;     LLISTXATTR              :: 195;     FLISTXATTR              :: 196;     REMOVEXATTR             :: 197;     LREMOVEXATTR            :: 198;     FREMOVEXATTR            :: 199;     TKILL                   :: 200;     TIME                    :: 201;     FUTEX                   :: 202;     SCHED_SETAFFINITY       :: 203;     SCHED_GETAFFINITY       :: 204;     SET_THREAD_AREA         :: 205;     IO_SETUP                :: 206;     IO_DESTROY              :: 207;     IO_GETEVENTS            :: 208;     IO_SUBMIT               :: 209;     IO_CANCEL               :: 210;     GET_THREAD_AREA         :: 211;     LOOKUP_DCOOKIE          :: 212;     EPOLL_CREATE            :: 213;     EPOLL_CTL_OLD           :: 214;     EPOLL_WAIT_OLD          :: 215;     REMAP_FILE_PAGES        :: 216;     GETDENTS64              :: 217;     SET_TID_ADDRESS         :: 218;     RESTART_SYSCALL         :: 219;     SEMTIMEDOP              :: 220;     FADVISE64               :: 221;     TIMER_CREATE            :: 222;     TIMER_SETTIME           :: 223;     TIMER_GETTIME           :: 224;     TIMER_GETOVERRUN        :: 225;     TIMER_DELETE            :: 226;     CLOCK_SETTIME           :: 227;     CLOCK_GETTIME           :: 228;     CLOCK_GETRES            :: 229;     CLOCK_NANOSLEEP         :: 230;     EXIT_GROUP              :: 231;     EPOLL_WAIT              :: 232;     EPOLL_CTL               :: 233;     TGKILL                  :: 234;     UTIMES                  :: 235;     VSERVER                 :: 236;     MBIND                   :: 237;     SET_MEMPOLICY           :: 238;     GET_MEMPOLICY           :: 239;     MQ_OPEN                 :: 240;     MQ_UNLINK               :: 241;     MQ_TIMEDSEND            :: 242;     MQ_TIMEDRECEIVE         :: 243;     MQ_NOTIFY               :: 244;     MQ_GETSETATTR           :: 245;     KEXEC_LOAD              :: 246;     WAITID                  :: 247;     ADD_KEY                 :: 248;     REQUEST_KEY             :: 249;     KEYCTL                  :: 250;     IOPRIO_SET              :: 251;     IOPRIO_GET              :: 252;     INOTIFY_INIT            :: 253;     INOTIFY_ADD_WATCH       :: 254;     INOTIFY_RM_WATCH        :: 255;     MIGRATE_PAGES           :: 256;     OPENAT                  :: 257;     MKDIRAT                 :: 258;     MKNODAT                 :: 259;     FCHOWNAT                :: 260;     FUTIMESAT               :: 261;     NEWFSTATAT              :: 262;     UNLINKAT                :: 263;     RENAMEAT                :: 264;     LINKAT                  :: 265;     SYMLINKAT               :: 266;     READLINKAT              :: 267;     FCHMODAT                :: 268;     FACCESSAT               :: 269;     PSELECT6                :: 270;     PPOLL                   :: 271;     UNSHARE                 :: 272;     SET_ROBUST_LIST         :: 273;     GET_ROBUST_LIST         :: 274;     SPLICE                  :: 275;     TEE                     :: 276;     SYNC_FILE_RANGE         :: 277;     VMSPLICE                :: 278;     MOVE_PAGES              :: 279;     UTIMENSAT               :: 280;     EPOLL_PWAIT             :: 281;     SIGNALFD                :: 282;     TIMERFD_CREATE          :: 283;     EVENTFD                 :: 284;     FALLOCATE               :: 285;     TIMERFD_SETTIME         :: 286;     TIMERFD_GETTIME         :: 287;     ACCEPT4                 :: 288;     SIGNALFD4               :: 289;     EVENTFD2                :: 290;     EPOLL_CREATE1           :: 291;     DUP3                    :: 292;     PIPE2                   :: 293;     INOTIFY_INIT1           :: 294;     PREADV                  :: 295;     PWRITEV                 :: 296;     RT_TGSIGQUEUEINFO       :: 297;     PERF_EVENT_OPEN         :: 298;     RECVMMSG                :: 299;     FANOTIFY_INIT           :: 300;     FANOTIFY_MARK           :: 301;     PRLIMIT64               :: 302;     NAME_TO_HANDLE_AT       :: 303;     OPEN_BY_HANDLE_AT       :: 304;     CLOCK_ADJTIME           :: 305;     SYNCFS                  :: 306;     SENDMMSG                :: 307;     SETNS                   :: 308;     GETCPU                  :: 309;     PROCESS_VM_READV        :: 310;     PROCESS_VM_WRITEV       :: 311;     KCMP                    :: 312;     FINIT_MODULE            :: 313;     SCHED_SETATTR           :: 314;     SCHED_GETATTR           :: 315;     RENAMEAT2               :: 316;     SECCOMP                 :: 317;     GETRANDOM               :: 318;     MEMFD_CREATE            :: 319;     KEXEC_FILE_LOAD         :: 320;     BPF                     :: 321;     EXECVEAT                :: 322;     USERFAULTFD             :: 323;     MEMBARRIER              :: 324;     MLOCK2                  :: 325;     COPY_FILE_RANGE         :: 326;     PREADV2                 :: 327;     PWRITEV2                :: 328;     PKEY_MPROTECT           :: 329;     PKEY_ALLOC              :: 330;     PKEY_FREE               :: 331;     STATX                   :: 332;     IO_PGETEVENTS           :: 333;     RSEQ                    :: 334;     URETPROBE               :: 335;     UPROBE                  :: 336;     PIDFD_SEND_SIGNAL       :: 424;     IO_URING_SETUP          :: 425;     IO_URING_ENTER          :: 426;     IO_URING_REGISTER       :: 427;     OPEN_TREE               :: 428;     MOVE_MOUNT              :: 429;     FSOPEN                  :: 430;     FSCONFIG                :: 431;     FSMOUNT                 :: 432;     FSPICK                  :: 433;     PIDFD_OPEN              :: 434;     CLONE3                  :: 435;     CLOSE_RANGE             :: 436;     OPENAT2                 :: 437;     PIDFD_GETFD             :: 438;     FACCESSAT2              :: 439;     PROCESS_MADVISE         :: 440;     EPOLL_PWAIT2            :: 441;     MOUNT_SETATTR           :: 442;     QUOTACTL_FD             :: 443;     LANDLOCK_CREATE_RULESET :: 444;     LANDLOCK_ADD_RULE       :: 445;     LANDLOCK_RESTRICT_SELF  :: 446;     MEMFD_SECRET            :: 447;     PROCESS_MRELEASE        :: 448;     FUTEX_WAITV             :: 449;     SET_MEMPOLICY_HOME_NODE :: 450;     CACHESTAT               :: 451;     FCHMODAT2               :: 452;     MAP_SHADOW_STACK        :: 453;     FUTEX_WAKE              :: 454;     FUTEX_WAIT              :: 455;     FUTEX_REQUEUE           :: 456;     STATMOUNT               :: 457;     LISTMOUNT               :: 458;     LSM_GET_SELF_ATTR       :: 459;     LSM_SET_SELF_ATTR       :: 460;     LSM_LIST_MODULES        :: 461;     MSEAL                   :: 462;     SETXATTRAT              :: 463;     GETXATTRAT              :: 464;     LISTXATTRAT             :: 465;     REMOVEXATTRAT           :: 466;     OPEN_TREE_ATTR          :: 467;     FILE_GETATTR            :: 468;     FILE_SETATTR            :: 469;     LISTNS                  :: 470; } seccomp_init :: inline (action: Scmp_Action) -> ctx: *void {     assert(CPU == .X64 && OS == .LINUX, "System ist NOT x64 Linux!");     return init(action); } seccomp_rule_add :: inline (     ctx: *void,     action: Scmp_Action,     syscall: Scmp_Syscall_x64 )     -> ok: bool {     return cast(bool)rule_add(ctx, action, syscall, 0, null); } seccomp_rule_add :: inline (     ctx: *void,     action: Scmp_Action,     syscall: Scmp_Syscall_x64,     args: ..*void )     -> ok: bool {     return cast(bool)rule_add(ctx, action, syscall, args.count, *args); } SCMP_ACT_ERRNO :: (error_code: int) -> int {     assert(false, "NOT TESTED YET");     return 0x00050000 | ((error_code) & 0x0000ffff); } SCMP_ACT_TRACE :: (process: int) -> int {     assert(false, "NOT TESTED YET");     return 0x7ff00000 | ((process) & 0x0000ffff); } seccomp_load :: (ctx: *void) -> int #foreign SCMP; seccomp_release :: (ctx: *void) -> void #foreign SCMP; #scope_file using,only(assert) Basic :: #import "Basic"; SCMP :: #library,system,no_dll "seccomp"; init :: (action: Scmp_Action) -> ctx: *void #foreign SCMP "seccomp_init"; rule_add :: (     ctx: *void, action: Scmp_Action, syscall: Scmp_Syscall_x64, arg_count: int, args: *void ) -> int #foreign SCMP "seccomp_rule_add"; /* ------------------------------------------------------------------------------ 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. ------------------------------------------------------------------------------ */ diff --git a/README.md b/README.md index c5bb2a3..47f9d1e 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ Currently, only Linux support. | 0x2_Rectcut    | Building UI layouts by dividing rectangles | -          | | 0x2_Stringpad  | Pads a string with any character           | -          | | 0x2_Termcolors | Colors for your terminal                   | -          | | 0x2_Seccomp    | Restricts syscalls                         | -          | | 0x2_Landlock   | Restricts FS & Network                     | -          | ``` diff --git a/tests/landlock.jai b/tests/landlock.jai new file mode 100644 index 0000000..69647c4 --- /dev/null +++ b/tests/landlock.jai @@ -0,0 +1,54 @@ run :: () {     hostname_fp :: "/etc/hostname";     content, ok := read_entire_file(hostname_fp);     assert(ok);     log("Pre Landlock: This is your hostname: »%«", content);     log("OK returns: %", ok);     log("-------------------------------------------------");     sec_landlock_init();     content, ok = read_entire_file(hostname_fp);     log("-------------------------------------------------");     log("Post Landlock: You should NOT see your hostname: »%«", content);     log("OK returns: %", ok);     /** Note: If a FD is already open pre landlock init, you can still use it here! */ } #scope_file DESIRED_VERSION :: 7; sec_landlock_init :: () {     version := landlock_is_version_equal_or_higher(DESIRED_VERSION, true);     if !version {         log("Warning: Your version of landlock is too old.", flags=.WARNING);     }     rules := landlock_all_rules();     ok, ll_fd := landlock_create_ruleset(*rules);     if !ok exit(1);     ok = landlock_lock_privileges();     if !ok exit(1);     ok = landlock_restrict_self(ll_fd);     if !ok exit(1);     log("Landlock is armed."); } #import "Basic"; #import "File"; #import,file "../0x2_Landlock.jai"; diff --git a/tests/run_tests.jai b/tests/run_tests.jai index 91bd816..169e22e 100644 --- a/tests/run_tests.jai +++ b/tests/run_tests.jai @@ -4,6 +4,8 @@ MEMORY_DEBUGGER :: false; HELP :: #string STR_END Commands:     -lk     Run Landlock test     -sc     Run Seccomp test     -tc     Run visual test for `Termcolors` STR_END; @@ -24,6 +26,14 @@ main :: () {         tc.tests_visual();         return;     }     else if array_find(args, "-sc") {         sc.run();         return;     }     else if array_find(args, "-lk") {         lk.run();         return;     }     ok := true; @@ -53,6 +63,8 @@ cl :: #import,file "colored.jai"; df :: #import,file "datefmt.jai"; sp :: #import,file "stringpad.jai"; lx :: #import,file "lexer.jai"; sc :: #import,file "seccomp.jai"; lk :: #import,file "landlock.jai"; #import,file "../0x2_Qtrace.jai"; crash :: #import,file "../0x2_Crashed.jai"; diff --git a/tests/seccomp.jai b/tests/seccomp.jai new file mode 100644 index 0000000..f74c0d9 --- /dev/null +++ b/tests/seccomp.jai @@ -0,0 +1,120 @@ /* This example is a bit sophisticated, because I'm showing a way how to set up a white-list using metaprogramming capabilities from Jai. For generating a white-list of syscalls you can use my tool in `tools/seccomp_collect_syscalls.py`. This records via `strace` syscalls and prints a ready to use array you can copy/paste here. Sometimes you want to only restrict syscalls from a specific point, in that case the same tool looks after a specific marker and only after that it will record syscalls. You can just print this marker in any place of your program:     log("---- SECCOMP BOUNDARY ----"); Look at `sec_seccomp_init()` how to set this up. */ run :: () {     log("Seccomp pre init. Writing to a FD is allowed!");     sec_seccomp_init();     log("Seccomp post init. Writing to a FD is NOT allowed!");     log("If you can read this, something went wrong!"); } #scope_file SECCOMP_ARMED :: true; /** Copy pasta from seccomp_collect_syscalls.py */ SECCOMP_ALLOWED_SYSCALLS :: string.[     "READ",     "PREAD64",     "SET_ROBUST_LIST",     "BRK",     "RT_SIGACTION",     "RSEQ",     "ACCESS",     //! "WRITE",   /** Disallow writing to a FD so we can test it! */     "ARCH_PRCTL",     "PRLIMIT64",     "READLINKAT",     "CLOSE",     "SECCOMP",     "SET_TID_ADDRESS",     "FSTAT",     "PRCTL",     "FUTEX",     "GETRANDOM",     "MUNMAP",     "NEWFSTATAT",     "MMAP",     "OPENAT",     "MPROTECT",     "EXECVE", ]; sec_seccomp_init :: () {     SECCOMP_TEMPLATE :: "had_error |= seccomp_rule_add(ctx, .ALLOW, .%);";     #if SECCOMP_ARMED {         /** Tell seccomp what to do if a violation happened. */         ctx := seccomp_init(.KILL_PROCESS);     } else {         /** For development, you can just log violations. You can view them here:                 ausearch -m SECCOMP         */         ctx := seccomp_init(.LOG);     }     defer seccomp_release(ctx);     if !ctx { log_error("Init failed"); exit(1); }     had_error := false;     /** This generates the `seccomp_rule_add()` functions based of the white-list. */     #insert -> string {         buf: String_Builder;         for SECCOMP_ALLOWED_SYSCALLS {             template := tprint(SECCOMP_TEMPLATE, it);             append(*buf, template);         }         s := builder_to_string(*buf);         return s;     }     /** You can also add rules here. */     had_error |= seccomp_rule_add(ctx, .ALLOW, .EXIT);     had_error |= seccomp_rule_add(ctx, .ALLOW, .EXIT_GROUP);     if had_error {         log_error("Could not add rules.");         exit(1);     }     if seccomp_load(ctx) < 0 {         log_error("Could not load context into kernel");         exit(1);     }     #if SECCOMP_ARMED then log("Seccomp is armed.");     else log("Seccomp is in log mode! NOT ARMED!");     /** After this point every syscall not in the white-list is blocked. */ } #import "Basic"; #import,file "../0x2_Seccomp.jai"; diff --git a/tools/seccomp_collect_syscalls.py b/tools/seccomp_collect_syscalls.py new file mode 100755 index 0000000..2611bb9 --- /dev/null +++ b/tools/seccomp_collect_syscalls.py @@ -0,0 +1,218 @@ #!/bin/python # This program records syscalls. Either from a specific point in your program # or just from the start. # # It works in two steps: Recording syscalls and creating an white-list, ready to paste into your # Jai code. # # In case you want to only restrict syscalls from a specific point on, set this marker # in any place of your program: # #     ``` #     log("---- SECCOMP BOUNDARY ----"); #     ``` # # # First step, recording: # #     ``` #     ./seccomp_collect_syscalls.py -r your_program <optional args for your program> #     ``` # # After that, it depends if you have a boundary marker in your program. # With boundary marker: # #     ``` #     ./seccomp_collect_syscalls.py -p strace.txt #     ``` # # Without boundary marker: # #     ``` #     ./seccomp_collect_syscalls.py -i -p strace.txt #     ``` # # Well, you can pass `-i` even with a boundary marker, it will ignore it anyway. import subprocess import os from sys import exit, argv from pathlib import Path ignore_marker = False HELP = """Usage: syscalls.py [OPTIONS] [FP] OPTIONS     -r <program path>           Records syscalls     -p <strace output file>     Prints the used syscalls from your 'strace.txt'.                                 If the path is omitted, it looks after 'strace.txt'                                 in your current directory.     -i                          Ignore boundary marker when using `-p` """ STRACE_OUTPUT_FP = Path("strace.txt") SCMP_BOUNDARY_MARKER = "---- SECCOMP BOUNDARY ----" def main(args):     global ignore_marker     if len(args) == 1:         print(HELP)         exit(1)     if "h" in args[1] or "help" in args[1]:         print(HELP)         exit(0)     if args[1] == "-r":         if len(args) < 3:             print("Need filepath to program")             exit(1)         strace_run(args[2:])     if args[1] == "-p":         fp = STRACE_OUTPUT_FP if len(args) == 2 else args[2]         fp = Path(fp)         ignore_marker = "-i" in args         gather_unique_syscalls(fp)     print("Unknown argument")     exit(1) def strace_run(program):     cmd = [         "strace",         "-o",         STRACE_OUTPUT_FP,         *program     ]     run_command(cmd)     exit(0) def gather_unique_syscalls(fp):     global ignore_marker     if not fp.exists():         print("File does not exist:", fp)         exit(1)     content = open_file_or_exit(fp)     boundary_idx = next(         (i for i, s in enumerate(content) if SCMP_BOUNDARY_MARKER in s), None     )     if not ignore_marker:         if boundary_idx == None:             print("Cannot find seccomp boundary marker")             exit(1)         boundary_idx += 1         content = content[boundary_idx:]     else:         boundary_idx = 0     syscalls = set()     for i, line in enumerate(content):         idx = line.find("(")         if idx == -1:             syscall = f"[ERR: at line {i + boundary_idx}]"             continue         else:             syscall = line[:idx]         syscalls.add(syscall)     program_fn = os.path.basename(__file__)     print(f"/** Copy pasta from {program_fn} */")     print("SECCOMP_ALLOWED_SYSCALLS :: string.[")     for item in syscalls:         code = f'    "{item.upper()}",'         print(code)     print("];")     exit(0) def run_command(cmd):     try:         subprocess.run(cmd, text=True, check=True)     except subprocess.CalledProcessError as e:         print(f"Command failed {e.returncode}: {e.stderr}")         exit(1) def open_file_or_exit(fp):     try:         with open(fp, 'r', encoding="utf8") as f:             return f.readlines()     except Exception as e:         print(e)         exit(1) if __name__ == "__main__":     args = argv     try:         main(args)     except KeyboardInterrupt:         print("Terminated by user")         exit(1) # ------------------------------------------------------------------------------ # 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. # ------------------------------------------------------------------------------