SYD(7)
πΈ SYD(7) β Miscellaneous Information Manual πΈ
ok so like β welcome to <pink>SYD(7)</pink> bestie π this one lives in section 7 of the Miscellaneous Information Manual, and it's your little orientation session for sandboxing with Syd. Think of it as the "hey girl, here's the lay of the land" page before we get into the actual categories. No cap, it's the overview page, and honestly? iconic of it.
β¨ NAME β¨
Overview of sandboxing with Syd β that's the whole vibe. This page is the big-picture tour of how sandboxing works with Syd, not the deep-dive. Consider it the map, not the territory πΊοΈβ¨
π¦ SANDBOXING π¦
okay so here's the deal π the list of available sandboxing categories is given below:
and that's literally where we're at β the list is coming, and it's giving main character energy. stay tuned bestie π«Άπ
walkβ confines path traversals, bestie. This sandboxing category gets used during path canonicalization to confine path traversals. Which means its arguments are not necessarily fully canonicalized paths, but they are guaranteed to be absolute paths without any.(dot) or..(dotdot) components. It got split from thestatcategory as of version3.39.0. Together with thestatcategory, path hiding provides a full implementation that's resilient against attempts to unhide otherwise hidden paths by passing through them during path canonicalization. For reference, OpenBSD'sunveil(2)pioneered similar capabilities and remains a mature reference implementation. πlistβ confines directory listings β¨. This category is used during directory lists with thegetdents(2)andgetdents64(2)system calls to filter out disallowed path patterns. It has been split from thestatcategory as of version3.53.0.statβ confines file metadata accesses. The filtered system calls areaccess(2),faccessat(2),faccessat2(2),stat(2),fstat(2),lstat(2),statx(2),newfstatat(2),statfs(2),statfs64(2),fstatfs(2), andfstatfs64(2). In addition, paths may be masked using themaskcommand β and in that case, all filtered system calls on the path will be executed on the character device/dev/nullinstead. Refer to the description of themaskcommand in thesyd(2)manual page for more information π«Ά.readβ confines file reads and extended attribute reads π. The filtered system calls areopen(2),openat(2)andopenat2(2)with theO_RDONLYorO_RDWRflags,getxattr(2),getxattrat(2),lgetxattr(2),fgetxattr(2),listxattr(2),listxattrat(2),flistxattr(2), andllistxattr(2).writeβ confines file writes. The filtered system calls areopen(2),openat(2)andopenat2(2)with theO_WRONLYorO_RDWRflags.execβ confines binary execution and dynamic library loading π₯. The filtered system calls areexecve(2),execveat(2),mmap(2),mmap2(2), andmemfd_create(2). For scripts, the access check is done for both the script and the interpreter binary. The lore here is genuinely stacked:- As of version
3.16.3, Syd checks the paths of the dynamic libraries an executable is linked against forexecaccess as well. This only works for ELF binaries, ngl. - As of version
3.21.2, Syd seals memory file descriptors as non-executable by default, so memory file descriptors are not checked forexecaccess unless the optiontrace/allow_unsafe_memfd:1is set to lift this restriction π. - As of version
3.21.3, Syd hooks into themmap(2)andmmap2(2)system calls and checks the file descriptor forexecaccess when the memory protection mode includesPROT_EXECand flags does not includeMAP_ANONYMOUSβ which typically indicates adlopen(3). So libraries dynamically loaded at runtime get checked forexecaccess as well π . - In addition,
SegvGuardis used to deny execution if a binary is crashing repeatedly, which is similar to the implementation of Grsecurity & HardenedBSD. Refer to the SegvGuard section for more information.
- As of version
ioctlβ confinesioctl(2)requests. Uselock/ioctlto confine theioctl(2)system call for filesystem access. This feature may be used to effectively access GPU, PTY, DRM, and KVM etc. safely π.ioctl(2)requests may be allowed or denied by adding them to the respective list using the optionsallow/ioctl+anddeny/ioctl+. As of version3.38.0, architecture-agnosticioctl(2)decoding was introduced, allowing ioctls to be specified by name in addition to
to numeric values. Refer to the syd(2) manual page for more information. π
okay bestie, the list keeps going and it's honestly giving main-character energy:
createβ confine creation of regular files, memory file descriptors,epoll(7),eventfd(2),fanotify(7),inotify(7),pipe(2),signalfd(2), andtimerfd(2)file descriptors. The filtered system calls arecreat(2),mknod(2),mknodat(2),memfd_create(2),memfd_secret(2),epoll_create(2),epoll_create1(2),eventfd(2),eventfd2(2),fanotify_init(2),inotify_init(2),inotify_init1(2),pipe(2),pipe2(2),signalfd(2),signalfd4(2), andtimerfd_create(2). In addition,open(2),openat(2), andopenat2(2)are filtered whenO_CREATis set andO_TMPFILEis not. Non-file descriptors are matched against a reserved keyword rather than a path:!memfd:,!memfd-hugetlb:when flags includeMFD_HUGETLB,!secretmem,!eventpoll,!eventfd,!fanotify,!inotify,!signalfd,!timerfd, and!pipe.pipe2(2)withO_NOTIFICATION_PIPEuses!notification_pipe, refer to DenyingO_NOTIFICATION_PIPEFlag inpipe2section. As an example,deny/create+!eventpolldeniesepoll(7)creation. β¨deleteβ confine file deletions. The list of filtered system calls isunlink(2)andunlinkat(2). As of version3.33.0,unlinkat(2)is confined by this category if and only ifAT_REMOVEDIRis not set in flags, otherwise it's confined by thermdircategory. ποΈrenameβ confine file renames and hard links. The list of filtered system calls isrename(2),renameat(2),renameat2(2),link(2), andlinkat(2). πreadlinkβ confine symbolic link reads. The list of filtered system calls isreadlink(2)andreadlinkat(2). This category has been split from thestatcategory as of version3.53.0. πsymlinkβ confine creation of symbolic links. The list of filtered system calls issymlink(2)andsymlinkat(2). πͺ’truncateβ confine file truncations. The list of filtered system calls istruncate(2),truncate64(2),ftruncate(2),ftruncate64(2), andfallocate(2). In addition, theopensystem callsopen(2),openat(2), andopenat2(2)are filtered if the flagO_TRUNCis set in arguments and the flagsO_TMPFILEorO_CREATare not set in arguments. βοΈchdirβ confine directory changes. The list of filtered system calls ischdir(2)andfchdir(2). Additional hardening may be achieved using thetrace/deny_dotdot:1option to deny parent directory traversals. It is possible to set this option at runtime before sandbox is locked. This allows for incremental confinement. Refer to the Path Resolution Restriction For Chdir and Open Calls section for more information. πͺreaddirβ confine directory listings. The list of filtered system calls isopen(2),openat(2), andopenat2(2)when they're called on an existing directory regardless of theO_DIRECTORYflag. πmkdirβ confine creation of directories. The list of filtered system calls ismkdir(2),mkdirat(2),mknod(2)andmknodat(2). πrmdirβ confine deletion of directories. The list of filtered system calls isrmdir(2)andunlinkat(2). Noteunlinkat(2)is confined by this category if and only ifAT_REMOVEDIRis set in flags, otherwise it's confined by thedeletecategory. This category was split from thedelete
β¦as of version 3.33.0 these are the categories, bestie π continuing the list:
chown, chgrp
Confines owner and group changes on files. The filtered syscalls are chown(2), chown32(2), fchown(2), fchown32(2), lchown(2), lchown32(2), and fchownat(2). By default, the uid/gid arguments are confined to the caller's own effective credentials β so no sneaky "give it to root" energy π β but you can set trace/allow_unsafe_chown:1 to permit chown to an arbitrary uid/gid. Use responsibly, iykyk π«Ά
chmod
Confines mode changes on files. The filtered syscalls are chmod(2), fchmod(2), fchmodat(2), and fchmodat2(2). On top of that, a umask(2) value may be set using the trace/force_umask option, which is enforced at the chmod(2) boundary as well as during regular file creation. So like, setting trace/force_umask:7177 effectively disallows setting s{u,g}id bits, all group+other bits, and the execute bit for the current user. This feature is useful in setting up W^X (Write XOR Execute) configuration for the sandbox β honestly iconic for hardening π
chattr
Confines extended attribute changes on files. The filtered syscalls are setxattr(2), setxattrat(2), fsetxattr(2), lsetxattr(2), removexattr(2), removexattrat(2), fremovexattr(2), and lremovexattr(2). In addition, Syd ensures that extended attributes whose name starts with one of the prefixes security., system., and trusted. can not be listed or tampered by the sandbox process unless the sandbox lock is off for the respective process π This access can be permitted to the initial sandbox process with lock:exec, or to all sandbox processes with lock:off. And as of version 3.37.0, this restriction may be lifted with trace/allow_unsafe_xattr:1 π
chroot
Confines the change of the root directory using the chroot(2) system call. This sandboxing category can be disabled with trace/allow_unsafe_chroot:1 at startup, and when you do that the chroot(2) system call becomes a no-op. Similarly, the pivot_root(2) system call is denied with the errno(3) EPERM by default unless trace/allow_unsafe_pivot_root:1 is set at startup β in which case it becomes a no-op like chroot(2) π₯ No actual change of root directory takes place either way, fr fr.
Syd must share the root directory with the sandbox process to work correctly. Instead of, like, actually chrooting, Syd will prevent all filesystem access after the first allowed chroot(2) attempt, regardless of the root directory argument. The only exception to that filesystem-access prevention is the chdir(2) system call with the specific argument /, aka the root directory, which is allowed. This ensures a TOCTOU-free way to support the common use-case of cutting all filesystem access by means of a chroot(2) call to /var/empty, which is the common case among unix daemons π§
This sandboxing category does not depend on the Linux capability CAP_SYS_CHROOT, therefore it can be used in an unprivileged context. Syd drops the CAP_SYS_CHROOT Linux capability by default unless trace/allow_unsafe_caps:1 is passed at startup π
notify
Confines file notification access. The filtered syscalls are fanotify_mark(2) and inotify_add_watch(2). This category was split from the stat category as of version 3.50.0. Access and modify events for block and character devices are stripped by default, like Grsecurity's GRKERNSEC_DEVICE_SIDECHANNEL, to prevent device sidechannels π Refer to the Device Sidechannel Mitigations section for more information.
utime
Confines last access and modification time changes on files. The filtered syscalls are utime(2), utimes(2), futimesat(2), utimensat(2), and utimensat_time64(2) β
mkbdevβ confines block device creation, bestie π . The filtered system calls aremknod(2)andmknodat(2). Block device creation is disabled by default to adhere to the principle of secure defaults, with a kernel level seccomp-bpf filter which terminates the process on violation. Like, terminates. And this filter includes the Syd process itself, so a compromised Syd process will not be able to create block devices either β no cap, it's giving zero escape routes π«Ά. Therefore, the user must opt-in at startup using thetrace/allow_unsafe_mkbdev:1option to use this category for path-based access checks on block devices.mkcdevβ confines character device creation β¨. Filtered system calls aremknod(2),mknodat(2), andrenameat2(2)with theRENAME_WHITEOUTflag. Character device creation is disabled by default to adhere to the principle of secure defaults with a kernel level seccomp-bpf filter which terminates the process on violation. This filter includes the Syd process, so a compromised Syd process will not be able to create character devices either. Therefore, the user must opt-in at startup using thetrace/allow_unsafe_mkcdev:1option to use this category for path-based access checks on character devices. Same energy asmkbdev, honestly β the secure-defaults siblings πΈ.mkfifoβ confines named pipe (FIFO) creation π. The filtered system calls aremknod(2)andmknodat(2).mktempβ confines temporary file creation π¦. Filtered system calls areopen(2),openat(2), andopenat2(2)with theO_TMPFILEflag. A rule such asallow/mktemp+/tmppermits the sandbox process to create anonymous temporary files under the directory/tmp. Highkey important distinction: the creation of regular files of temporary nature are confined by thecreatecategory instead. So don't mix them up, iykyk π.netβ confines network access π₯. Socket typesUNIX,IPv4,IPv6,NetLinkandKCAPIare supported β use the optiontrace/allow_unsupp_socket:1to pass-through sockets of unsupported types. UNIX domain sockets are always matched on absolute path, therefore always start with the character/. UNIX abstract sockets are prefixed with the@character before access check. Similarly, unnamed UNIX sockets use the dummy path!unnamedfor access check. Finally, network sandboxing concentrates on confining the initial connection action and leaves out the system callrecvfrom(2)as out of scope for sandbox confinement, for performance reasons and due to a lack of security implications β noting the fact thatrecv*system calls cannot specify target addresses. The system callsrecvmsg(2)andrecvmmsg(2)are confined by therecvfdcategory, which checksSCM_RIGHTSfile descriptor reception π.net/bindβ confines binding network access π. This category confines thebind(2)system call, UNIX domain socket file creation using themknod(2)andmknodat(2)system calls, and UNIX socket-pair creation using thesocketpair(2)system call. Thesocketpair(2)system call uses the dummy path!unnamedfor access check. Unnamed UNIX sockets use the same dummy path π.net/connectβ confines connecting network access β. The filtered system calls areconnect(2),sendto(2),sendmsg(2), andsendmmsg(2). ForIPv4andIPv6sockets, the target address of these system calls are also checked against the IP blocklist β refer to the description of theblockcommand insyd(2)manual page for more information π§.sendfdβ confines sending of file descriptors π. Filtered system calls aresendmsg(2)andsendmmsg(2). The file descriptor
β¦the file descriptor being sent is whatβs matched, not the peer itβs being sent to. A file descriptorβs name starts with the character !, e.g. !file, !pipe, or !memfd:NAME. For the full list of file descriptor names, go read the syd(2) manual page. π«Ά
recvfdβ okay so this one confines reception of file descriptors. The filtered system calls arerecvmsg(2)andrecvmmsg(2). What gets matched is the file descriptor being received, not the peer itβs received from. A file descriptor name begins with the character!, e.g.!file,!pipe, or!memfd:NAME. Refer to thesyd(2)manual page for the list of file descriptor names. πnet/linkβ this confinesnetlink(7)sockets used in communication between kernel and user space. This sandboxing category may be used to specify a list ofnetlink(7)families to allow for the sandbox process. Use e.g.allow/net/link+routeto allow theNETLINK_ROUTEfamily. Refer to thesyd(2)manual page for more information. π°οΈlock/readβ uselandlock(7)to confine file read access. This category corresponds to thelandlock(7)access rightLANDLOCK_ACCESS_FS_READ_FILEand only applies to the content of the directory, not the directory itself. As of version3.33.0,lock/execandlock/readdiraccess rights are confined in their respective categories. Previously, this category included the access rightsLANDLOCK_ACCESS_FS_EXECUTEandLANDLOCK_ACCESS_FS_READ_DIRas well. This category is enforced completely in kernel-space, so it can be used to construct a multi-layered sandbox. Refer to the Lock Sandboxing section for more information. πlock/writeβ uselandlock(7)to confine file write access. This category corresponds to thelandlock(7)access rightLANDLOCK_ACCESS_FS_WRITE_FILEand only applies to the content of the directory, not the directory itself. This category is enforced completely in kernel-space, so it can be used to construct a multi-layered sandbox. Refer to the Lock Sandboxing section for more information. βοΈlock/execβ uselandlock(7)to confine file execution. This category corresponds to thelandlock(7)access rightLANDLOCK_ACCESS_FS_EXECUTEand only applies to the content of the directory, not the directory itself. This category is enforced completely in kernel-space, so it can be used to construct a multi-layered sandbox. Refer to the Lock Sandboxing section for more information. β‘lock/ioctlβ uselandlock(7)to confineioctl(2)operations. This category corresponds to thelandlock(7)access rightLANDLOCK_ACCESS_FS_IOCTL_DEVand only applies to the content of the directory, not the directory itself. This access right is supported as of Landlock ABI version 5, which was introduced withLinux-6.10. This command has no effect when running on older Linux kernels. Usesyd-lock(1)to check the latest Landlock ABI supported by the running Linux kernel. This category is enforced completely in kernel-space, so it can be used to construct a multi-layered sandbox. Refer to the Lock Sandboxing section for more information. ποΈlock/createβ uselandlock(7)to confine file creation, renames and links. This category corresponds to thelandlock(7)access rightLANDLOCK_ACCESS_FS_MAKE_REGand only applies to the content of the directory, not the directory itself. This category is enforced completely in kernel-space, so it can be used to construct a multi-layered sandbox. Refer to the Lock Sandboxing section for more information. π§lock/deleteβ useslandlock(7)to lock down file unlinking, renames and links π«Ά this one's thelandlock(7)access rightLANDLOCK_ACCESS_FS_REMOVE_FILE, and heads up: it only applies to the content of the directory, not the directory itself. fully enforced in kernel-space, so bestie you can stack it into a multi-layered sandbox β¨ peep the Lock Sandboxing section for the deets.lock/renameβ useslandlock(7)to confine linking or renaming a file from or to a different directory (i.e. reparenting a whole file hierarchy, no cap π). maps to thelandlock(7)access rightLANDLOCK_ACCESS_FS_REFER, and again it only applies to the content of the directory, not the directory itself. β οΈ this access right is only supported as of Landlock ABI version 2, which landed withLinux-5.19β so on older Linux kernels this command has no effect π. usesyd-lock(1)to check the latest Landlock ABI your running kernel supports. enforced completely in kernel-space β multi-layered sandbox potential, iconic. refer to the Lock Sandboxing section for more.lock/symlinkβ useslandlock(7)to confine symbolic link creation, renames and links π¦ that's thelandlock(7)access rightLANDLOCK_ACCESS_FS_MAKE_SYM, and it only applies to the content of the directory not the directory itself. fully kernel-space enforced, so multi-layered sandbox go brrr β¨ refer to the Lock Sandboxing section for more.lock/truncateβ useslandlock(7)to confine file truncation viatruncate(2),ftruncate(2),creat(2), oropen(2)withO_TRUNCπ matches thelandlock(7)access rightLANDLOCK_ACCESS_FS_TRUNCATE, and (say it with me) only applies to the content of the directory not the directory itself. β οΈ supported as of Landlock ABI version 3, introduced withLinux-6.2β older Linux kernels? no effect, literally nothing π.syd-lock(1)will tell you the latest Landlock ABI your running kernel supports. completely enforced in kernel-space so it's multi-layered-sandbox ready, periodt. refer to the Lock Sandboxing section for more.lock/readdirβ useslandlock(7)to confine directory listings π corresponds to thelandlock(7)access rightLANDLOCK_ACCESS_FS_READ_DIR, and this one's different bestie: it applies to the given directory and the directories beneath it. fully enforced in kernel-space, so yes, multi-layered sandbox construction, we love to see it π refer to the Lock Sandboxing section for more.lock/mkdirβ useslandlock(7)to confine directory creation and renames π that's thelandlock(7)access rightLANDLOCK_ACCESS_FS_MAKE_DIR, only applies to the content of the directory not the directory itself, enforced completely in kernel-space for that multi-layered sandbox life β¨ refer to the Lock Sandboxing section for more.lock/rmdirβ useslandlock(7)to confine directory deletion and renames πΈ corresponds to thelandlock(7)access rightLANDLOCK_ACCESS_FS_REMOVE_DIR, and only applies to the content of the directory not the directory itself. this category is enforced completely in kernel-space so it can be used to construct a multi-layered sandbox. refer to the Lock β¦lock/mkbdevβ bestie this one useslandlock(7)to confine block device creation + renames + links. It maps to thelandlock(7)access rightLANDLOCK_ACCESS_FS_MAKE_BLOCK. β¨ Lowkey the best part: it's enforced completely in kernel-space, so you can stack it into a multi-layered sandbox. Peep the Lock Sandboxing section for the full tea. πlock/mkcdevβ same energy, but for character device creation, renames and links, vialandlock(7). Corresponds to thelandlock(7)access rightLANDLOCK_ACCESS_FS_MAKE_CHAR. Also enforced completely in kernel-space β multi-layered sandbox material. Lock Sandboxing section, go read it π«Άlock/mkfifoβ named pipe (FIFO) creation, renames and links, confined withlandlock(7). This category corresponds to thelandlock(7)access rightLANDLOCK_ACCESS_FS_MAKE_FIFO. Enforced completely in kernel-space, so yes, multi-layered sandbox, we love to see it. More in the Lock Sandboxing section πlock/bindβlandlock(7)time, but for network ports onbind(2)and for UNIX domain socket creation, renames and links. It corresponds to the Landlock access rightsLANDLOCK_ACCESS_NET_BIND_TCPandLANDLOCK_ACCESS_NET_BIND_UDPfor network ports, plusLANDLOCK_ACCESS_FS_MAKE_SOCKfor UNIX domain sockets β and that last one only applies to the content of the directory, not the directory itself, iykyk. π
Quick ABI lore: LANDLOCK_ACCESS_NET_BIND_TCP is supported as of Landlock ABI version 4, which landed with Linux-6.7. LANDLOCK_ACCESS_NET_BIND_UDP is supported as of Landlock ABI version 10, introduced with Linux-7.2. β οΈ On older Linux kernels this command has no effect β like, literally nothing. Use syd-lock(1) to check the latest Landlock ABI your running kernel supports. This category is enforced completely in kernel-space so it can be used to construct a multi-layered sandbox. Lock Sandboxing section for more π
lock/connectβlandlock(7)confining network ports forconnect(2)and UNIX domain socket resolution. Corresponds to the Landlock access rightsLANDLOCK_ACCESS_NET_CONNECT_TCPandLANDLOCK_ACCESS_NET_CONNECT_SEND_UDPfor network ports, andLANDLOCK_ACCESS_FS_RESOLVE_UNIXfor UNIX domain sockets. That last one restricts bothconnect(2)andsendmsg(2)with an explicit recipient address to UNIX domain sockets created outside thelandlock(7)domain. No cap, it's giving gatekeeping but make it kernel. π¦
ABI check bestie: LANDLOCK_ACCESS_NET_CONNECT_TCP is supported as of Landlock ABI version 4, introduced with Linux-6.7. LANDLOCK_ACCESS_FS_RESOLVE_UNIX is supported as of Landlock ABI version 9, introduced with Linux-7.1. LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP is supported as of Landlock ABI version 10, introduced with Linux-7.2. β οΈ On older Linux kernels this command has no effect. Use syd-lock(1) to check the latest Landlock ABI supported by the running Linux kernel. Enforced completely in kernel-space β multi-layered sandbox, slay. Lock Sandboxing section, you know the drill πΈ
blockβ application firewall, and it has the range to include ipset and netset files, periodt. π₯ The filtered system calls areaccept(2),accept4(2),connect(2),sendto(2),sendmsg(2),sendmmsg(2). IPv4 and IPv6 family sockets are supported. Source and target addresses get checked against the IP blocklist. Refer to the description of theblockcommand insyd(2)β
okay so picking up right where we left off β here's the rest of the sandboxing menu, and bestie, it's long π
fsβ confine file opens based on filesystem type. By default, no filesystem types are allowed π. To make this sandboxing practical, thefsprofile included by thelinuxprofile allows all filesystem types exceptaafs,bpf_fs,securityfs,selinux,smack,debugfs,pstorefs,tracefs,cgroup,cgroup2,nsfs,pid_fd,rdtgroup,devmem,efivarfs,hostfs,mtd_inode_fs,openprom,daxfs,secretmem,bdevfs,binderfs,usbdevice,xenfs, andzonefsπ«. Useallow/fs+<fstype>to allow a filesystem type β¨.forceβ Verified Execution: verifies binary/library integrity atexec(3)/mmap(2)time, which is similar to Veriexec (NetBSD) & IntegriForce (HardenedBSD) π₯. Check the Force Sandboxing section for the deets π«Ά.tpeβ Trusted Path Execution: execution only allowed from Trusted directories for Trusted files which are not writable by group or others, and are optionally owned byrootor current user π. This feature is similar to the implementation of Grsecurity & HardenedBSD. Peep the TPE Sandboxing section for more π§.proxyβ SOCKS5 proxy forwarding with network namespace isolation π¦. Defaults to TOR. Refer to the Proxy Sandboxing section for more info.ptyβ run the sandbox process inside a new pseudoterminal π. See the PTY Sandboxing section for more.mem,pidβ Memory and PID sandboxing: simple, unprivileged alternatives to Control Groups π. Go read the Memory Sandboxing and PID Sandboxing sections for the whole story.uringβ confineio_uring(7)with task-level restrictions new inLinux-7.0β. Allow submission queue operations withallow/uring/op, registrations withallow/uring/reg, and submission flags withallow/uring/flagπ«. Use e.g.allow/uring/op+openatto also permitIORING_OP_OPENAT. Refer to the Uring Sandboxing section for more.SafeSetIDβ safe user/group switching with predefined UID/GID transitions, likeSafeSetIDof the Linux kernel π. Refer to the SafeSetID section for more information.Ghost modeβ detach Syd from the sandbox process, similar toseccomp(2)Level 1, aka "Strict Mode" π»π. Refer to the Ghost mode section for more.
so here's the vibe check on how sandboxing works per category, bestie π«Ά β sandboxing for a category may be on or off. If sandboxing is off, none of the relevant system calls are checked and all access is granted β¨. If, however, sandboxing is on, the action defaults to deny π€ and allowlists and denylists can be used to refine access rights, e.g. allow/read+/etc/passwd. The default action for a sandboxing category may be changed with the respective option, e.g. default/force:kill π₯. Refer to the syd(2) manual page for more information on how to configure Syd sandbox policies.
so if the sandbox process invokes a system call that violates access, this attempt is reported in the system log and the system call is denied from execution π
. There are two ways to customise this behaviour. Syd may be configured to allow some glob(3p) patterns π. If the path argument of the system call which is subject to be modified matches a pattern in the list of allowed glob(3p) patterns, this attempt is not denied π. If, however, it matches a pattern in the list of deny glob(3p) patterns, the attempt is denied π«. If many rules match the same path or address, the last matching pattern wins π β that's the main character energy of rules, fr fr.
it's also possible to use the actions exit, kill, abort, stop, panic, and warn instead of the allow and deny actions π³ (yeah, they go hard). The list of available sandboxing actions is given below π
allowβ bestie this one just green-lights the system call, no warning, no drama. β¨warnβ allows the system call and prints a warning about it. lowkey the learning-mode fave π«Άfilterβ denies the system call silently. not a peep. π€«denyβ denies the system call and warns you. this is the default, so if you donβt pick anything else, this is what you get πpanicβ denies the system call, warns, and panics the current Syd thread. πstopβ denies the system call, warns, and stops the offending process. πabortβ denies the system call, warns, and aborts the offending process. πkillβ denies the system call, warns, and kills the offending process. π₯exitβ warns, then exits Syd immediately withdenyerrno(3)as the exit value. π
deny is the default unless another default action is set using one of the default/<category>:<action> options. refer to syd(2) manual page for more information, bestie.
exit causes Syd to exit immediately with all the sandbox processes running under it. kill makes Syd send the offending process a SIGKILL signal and deny the system call. stop makes Syd send the offending process a SIGSTOP signal and deny the system call. abort makes Syd send the offending process a SIGABRT signal and deny the system call. unlike kill and stop actions, sandbox processes are able to catch the SIGABRT signal, therefore the abort action should only be used for debugging in trusted environments where a core(5) dump file may provide useful information. (trust. π«Ά)
panic causes the respective Syd emulator thread to panic, in which case the system call is denied by an RAII guard. this behaviour of the panic action is currently functionally equivalent to the deny action, however it may be further extended in the future where Syd emulator processes are fork+exec'ed and address space is rerandomized by ASLR on each access violation. no cap.
warn makes Syd allow the system call and print a warning about it, which is used by pandora(1) for learning mode. additionally, Syd may be configured to filter some glob(3p) patterns. in this case a match will prevent Syd from reporting a warning about the access violation β the system call is still denied though. periodt.
for lock/* categories the only available action is allow, and these categories accept path names rather than glob(3p) patterns as arguments. relative paths are permitted for all lock/* categories except lock/bind and lock/connect, which require either an absolute UNIX domain socket path or a port specification as argument. the port specification can be a single port, a closed range such as port1-port2, or a comma-separated list such as 80,443,8000-9000. slay π
π SANDBOX CATEGORY SETS π
as of v3.38.0, multiple categories may be specified split by commas, and the following sets are defined to simplify sandbox profile composition. the names are intentionally chosen to be consistent with OpenBSD's pledge(2) and FreeBSD's capsicum rights(4freebsd):
okay so here's the full menu of category names, and yes it's a lot, but it's lowkey satisfying once you see the pattern π
allβ literally all categories β¨all-lβ all categories exceptlpathall-nβ all categories exceptnpathall-xβ all categories exceptexecall-lnβ all categories exceptlpathandnpathall-lxβ all categories exceptlpathandexecall-nxβ all categories exceptnpathandexecall-lnxβ all categories exceptlpath,npath, andexeclock/allβ alllandlock(7)access rights πlock/all-xβ alllandlock(7)access rights exceptexeclpathβ walk, list, chdir, readlinknpathβ notify, statrpathβ read, readdirlock/rpathβ lock/read, lock/readdirwpathβ write, truncatelock/wpathβ lock/write, lock/truncatecpathβ create, delete, renamelock/cpathβ lock/create, lock/delete, lock/renamedpathβ mkbdev, mkcdevlock/dpathβ lock/mkbdev, lock/mkcdevspathβ mkfifo, symlinklock/spathβ lock/mkfifo, lock/symlinktpathβ mkdir, rmdirlock/tpathβ lock/mkdir, lock/rmdirfownβ chown, chgrpfattrβ chmod, chattr, utimenetβ net/bind, net/connectlock/netβ lock/bind, lock/connectinetβ net/bind, net/connectlock/inetβ lock/bind, lock/connectbnetβ net/bindlock/bnetβ lock/bindcnetβ net/connectlock/cnetβ lock/connectpassfdβ sendfd, recvfd
fr fr the all fam is just "everything, minus the lettered bits" β l is lpath, n is npath, x is exec, so all-lnx is the big vibe check that claps back at all three π«Ά and anything with lock/ in front is the landlock(7) flavoured version, okay? iykyk π
Some examples are given below β these are the actual spin-the-block combos, copy them exactly bestie π₯
default/all:kill
sandbox/inet:off
deny/cpath,rpath,wpath+${HOME}/.ssh/***
kill/spath+/tmp/***
allow/inet+loopback!1024-65535
allow/cnet+tcp!10.0.0.7!5432
kill/unix+/dev/log
like, ${HOME}/.ssh/*** getting a whole deny on cpath,rpath,wpath?? iconic protective queen behaviour π and kill/unix+/dev/log is giving main character energy ngl π¦
β¨ SANDBOX RULE SHORTCUTS β¨
okay bestie so here's the thing β you don't have to spell out sandbox capabilities one at a time if you don't want to π you can pass them to sandbox actions either as a single unit or as a comma-delimited list, like this:
allow/read,write,stat,exec+/***
allow/read,write,stat-/***
deny/read,write,stat+/***
deny/read,write-/***
filter/read,write,stat+/dev/mem
filter/read,write-/dev/mem
and as of version 3.18.14, sandboxing modes can also be given as a single unit or a comma-delimited list, like:
sandbox/read,write,stat,exec:on
sandbox/net,lock:off
as of version 3.19.0, namespace types get the same glow-up β single unit or comma-delimited list, e.g.:
unshare/user,pid,mount:on
unshare/net,cgroup:off
and as of version 3.35.0, default modes too, single or list, e.g:
default/write,truncate:kill
default/read,stat:allow
it's giving "why write ten lines when one slays" fr fr π
πΈ SegvGuard πΈ
As of version 3.16.3, Syd has a simple implementation of SegvGuard, and the inspo is HardenedBSD's version with identical defaults (iykyk). Here's the tea β:
- if a sandbox process receives a signal that may produce a
core(5)dump file forsegvguard/maxcrashestimes (defaults to 5), within a period ofsegvguard/expiryseconds (defaults to 2 minutes), - then subsequent attempts to execute the same executable are denied for
segvguard/suspensionseconds (defaults to 10 minutes).
you can turn SegvGuard off by setting segvguard/expiry:0. it also depends on ptrace(2), so it may also be disabled by setting trace/allow_unsafe_ptrace:1 at startup.
The trigger signals for SegvGuard are SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGIOT, SIGKILL, SIGQUIT, SIGSEGV, SIGSYS, SIGTRAP, SIGXCPU, and SIGXFSZ. and yes, SIGKILL is intentionally in that list even though it's not a core(5) dump file generating signal β it's there so that kill rules trigger SegvGuard. that design was later mirrored in HardenedBSD's work on PaX SEGVGUARD and Capsicum integration π
Check out these links for further info on SegvGuard:
http://en.wikibooks.org/wiki/Grsecurity/Appendix/Grsecurity_and_PaX_Configuration_Options#Deter_exploit_bruteforcinghttp://en.wikibooks.org/wiki/Grsecurity/Appendix/Grsecurity_and_PaX_Configuration_Options#Active_kernel_exploit_responsehttp://phrack.org/archives/issues/59/9.txthttp://phrack.org/archives/issues/58/4.txthttps://github.com/HardenedBSD/hardenedBSD/wiki/segvguard2-ideas---brainstormhttps://hardenedbsd.org/article/shawn-webb/2025-03-01/hardenedbsd-february-2025-status-report
π₯ Force Sandboxing π₯
Force Sandboxing is out here enhancing system security by scrutinizing the path handed to execve(2) and execveat(2) system calls and comparing it against a predefined Integrity Force map β a registry of path-to-checksum correlations, basically a guest list ποΈ
when those calls get invoked, the sandbox computes the checksum of the target binary and cross-references it with the map. discrepancies trigger rule-defined actions: execution might proceed with a logged warning, or it might culminate in the termination of the process in violation π this lets you enforce binary integrity, echoing the preventative ethos of HardenedBSD's Integriforce and NetBSD's Veriexec by preventing unauthorised code execution.
The response to checksum mismatches is configurable: kill, deny, or warn. Use syd-sum(1) for checksum calculation and syd-path(1) for rule creation. See the force command in the syd(2) manual page on how to add/remove entries to/from the Integrity Force map.
As of version 3.16.3, Syd checks the paths of the dynamic libraries an executable is linked against for force access as well. this only works for ELF files, ngl.
As of version 3.21.3, Syd hooks into mmap(2) and mmap2(2) system calls and checks the file descriptor for Force access when the memory protection mode includes PROT_EXEC and flags does not include MAP_ANONYMOUS β which typically indicates a dlopen(3). so libraries dynamically loaded at runtime get checked for Force access too π¦
π TPE sandboxing π
As of version 3.21.0, Syd introduces Trusted Path Execution (TPE) sandboxing, which restricts the execution of binaries to make sure they come from trusted directories. As of version 3.37.2, the binary file must be trusted as well as its parent directory β both, no exceptions.
the intention is to make privilege escalation harder when an account restricted by TPE is compromised, because the attacker won't be able to execute custom binaries which aren't in the trusted path. main character security energy tbh β¨
A binary is trusted if the file and its parent directory meet the following criteria:
- Not writable by group or others.
- Optionally owned by root, controlled by the
tpe/root_ownedoption. - Optionally owned by the current user or root, controlled by the
tpe/user_ownedoption. - Optionally part of the root filesystem, controlled by the
tpe/root_mountoption.
If these criteria are not met, the execution is denied with an EACCES errno(3), and optionally, the offending process can be terminated with the SIGKILL signal using the default/tpe:kill option. this mechanism ensures only binaries from secure, trusted paths can be executed, enhancing security by preventing unauthorized code execution π«Ά
TPE sandboxing operates by checking the the executables at three stages:
- During the system call entry of
execve(2)andexecveat(2)to check scripts. - On
ptrace(2)exec event to check the ELF executable and dynamic loader. - On
mmap(2)when dynamic libraries are mapped to memory, typically withdlopen(3).
(and yeah, the original really does say "the the" β we don't fix, we report π )
TPE can be configured to apply to a specific user group. By default, TPE applies to all users. However, administrators can specify an untrusted GID with the tpe/gid setting, restricting TPE only to users in that group. additionally, TPE can negate GID logic with the tpe/negate setting, making the specified group trusted and exempt from TPE.
Syd's TPE implementation is based on HardenedBSD's, which is inspired by GrSecurity's TPE. Check out the following links for more information:
http://phrack.org/issues/52/6.html#articlehttp://phrack.org/issues/53/8.html#articlehttps://wiki.gentoo.org/wiki/Hardened/Grsecurity_Trusted_Path_Execution
π Lock Sandboxing π
Lock sandboxing utilises the Landlock Linux Security Module for simple unprivileged access control. it's enforced completely in kernel-space, and the policy is also applied to the Syd process itself β so even a compromised Syd process is still stuck inside the landlock(7) sandbox π which means Lock sandboxing can be used to construct a multi-layered sandbox for added security. obsessed.
Lock sandboxing may be turned on with the sandbox/lock:on sandbox command at startup.
Paths to files and file hierarchies should be populated using the lock/* categories, either specifying them one at a time, e.g. allow/lock/read+/usr, allow/lock/write+/dev/null, or by specifying them as a comma delimited list, e.g. allow/lock/read,write,ioctl+/dev/null.
The shorthand lock/all is provided to ease configuration, and it stands for the union of categories lock/read, lock/write, lock/exec, lock/ioctl, lock/create, lock/delete, lock/rename, lock/symlink, lock/truncate, lock/readdir, lock/mkdir, lock/rmdir, lock/mkdev, lock/mkfifo, and lock/bind. the shorthand lock/all-x stands for all lock/all categories except lock/exec. iconic naming fr.
As of version 3.29.0, network confinement is supported, and allowlisted bind(2) and connect(2) ports can be specified using the commands allow/lock/bind+port and allow/lock/connect+port. a closed range in format port1-port2 or a comma-separated list of ports and/or ranges such as 80,443,8000-9000 may also be specified instead of a single port number π
Use the lock/bind category with an absolute path to confine UNIX domain socket creation, renames and links, e.g. allow/lock/bind+/run/user/${SYD_UID}.
As of version 3.35.0, the default compatibility level has been changed to Hard Requirement. compared to the old default Best Effort, this level enforces the sandbox fully. ENOENT ("No such file or directory") errors are made fatal in this level β οΈ the compatibility level may be changed at startup using the command default/lock. See the syd(2) manual page for more information.
π¦ Uring Sandboxing π¦
As of version 3.58.0, Syd confines io_uring(7) with task-level restrictions which are new in Linux-7.0. and bestie, this matters: without this restriction, io_uring(7) can bypass path and network sandboxing π enforcement is entirely in kernel-space and applies to Syd itself.
Uring sandboxing is on by default. It may be turned off with sandbox/uring:off at startup, which lets io_uring(7) pass through to Linux unconfined.
- Allowed submission queue operations are configured with
allow/uring/op. - Allowed registrations are configured with
allow/uring/reg. - Allowed submission flags are configured with
allow/uring/flag.
Operations default to epoll_ctl, read, readv, write, and writev. Registrations default to register_probe. Flags default to async. An empty operation list denies io_uring(7) altogether.
A registration configures a ring rather than submits work. the kernel enforces register restrictions only when at least one registration is allowlisted. the default allows only the read-only register_probe query β this denies the remaining registrations.
Syd denies the io_uring(7) interface altogether using seccomp(2) on Linux kernels older than 7.0. Syd similarly denies io_uring(7) altogether if the task-level restriction cannot be installed. the syd-ring(1) utility may be used to install these restrictions alone without syd(1). Refer to documentation of allow/uring/op, allow/uring/reg and allow/uring/flag commands in the syd(2) manual page for more information.
π Domain Transitions π
As of version 3.55.0, Syd supports sandbox domains: named, self-contained policies among which the sandbox switches at run time β¨
the sandbox always has exactly one active domain, whose policy applies to every process, and Syd keeps no per-process domain table. the domain default always exists. further new domains are created, seeded, removed, and cleared with the domain command.
A per-domain command is addressed to a domain with the prefix @<name>/. an unprefixed command applies to the default domain.
Startup-only settings β those reported as static in the syd(2) manual page β are global and cannot be addressed to a domain, which fails with the errno(3) EINVAL ("Invalid argument"). don't try it, it's not gonna work π
At most 64 domains may be defined, including default. a domain name is 1 to 16 bytes, letter-first ASCII alphanumeric with underscore ([A-Za-z][A-Za-z0-9_]*). every domain's policy is sealed together with the global policy when the sandbox is locked.
this model follows the domain-and-transition mechanism of the TOMOYO and CaitSith Linux Security Modules, in which policy is keyed on an audited label. periodt π
So like, Syd's design shares its whole lineage with CaitSith bestie, but the two of them differ in domain scope and in how policy evolves at run time. CaitSith binds a domain to each task and maintains a per-process domain table. Syd keeps a single active domain and literally no such table β a simpler design that holds one policy in force across the whole sandbox instead of tracking one per process. CaitSith keeps policy editable at run time through a designated manager program. Syd governs editability through a lock whose state decides who, if anyone, may change policy β ranging from all sandbox processes, to only the initial trusted process, to an irreversible sealed state where every domain's policy is fixed for the rest of the sandbox's lifetime. Both systems support a learning workflow where violations are logged rather than enforced and a profile is derived from the record β in Syd that's through the warn action and pandora(1). CaitSith additionally expresses per-rule conditions, quotas, and audit facilities that Syd's model lowkey leaves out. π
π¦ Moves π¦
A move changes the active domain and is always declared by the administrator with the move command. A move is atomic β the active domain is switched in a single step, so every mediated operation sees exactly one domain, never a mix of two. Iconic behaviour, no cap.
- An automatic move is taken when a mediated operation of one kind (
exit,exec,chdir,mmap,bind,connect, oraccept) is allowed on a matching pattern. - A lock-state move switches to a domain named
lock,read, ordrop, when it exists, aslock:on,lock:read, orlock:dropis instantiated. - A manual move is requested at run time by a process with the
cmd/movecommand.
See the domain, move, and cmd/move commands in the syd(2) manual page. π
πΈ Exec moves & the program image πΈ
An exec move matches the new program image, as reported by proc_self_exe(5) β not the execve(2) argument. For a #! script the image is the interpreter. Periodt.
# exec: confine a CGI interpreter. Executing it enters @cgi, which may not
# itself execute any further program.
domain+@cgi
@cgi/sandbox/exec:on
move/@cgi/exec+/usr/bin/php-cgi
# bind: confine a server to its serving phase. Once it has bound its
# listening port it can no longer bind or execute.
domain+@serve
@serve/sandbox/exec,net/bind:on
move/@serve/bind+0.0.0.0/0!443
# connect: after the application reaches its database it may open no further
# outbound connection, bounding exfiltration if it is later compromised.
domain+@db
@db/sandbox/net/connect:on
move/@db/connect+0.0.0.0/0!5432
# accept: once a worker has accepted a client it may neither execute
# programs nor open new connections of its own.
domain+@worker
@worker/sandbox/exec,net/connect:on
move/@worker/accept+0.0.0.0/0!1-65535
# mmap: the moment a plugin object is mapped, forbid loading further code
# and using the network.
domain+@plugin
@plugin/sandbox/exec,net:on
move/@plugin/mmap+/usr/lib/myapp/plugins/**.so
# chdir: on entering the upload directory, restrict the sandbox to that tree.
domain+@upload
@upload/sandbox/read,write:on
@upload/allow/read,write+/srv/upload/**
move/@upload/chdir+/srv/upload
# exit: open the network only while curl runs. Executing curl enters @net,
# and curl's exit returns the sandbox to the base policy.
sandbox/net:on
domain+@net
@net/allow/net/connect+0.0.0.0/0!443
move/@net/exec+/usr/bin/curl
@net/move/@default/exit+/usr/bin/curl
# lock state: a domain named drop is entered on lock:drop; lock and read
# behave the same way for lock:on and lock:read.
domain+@drop
@drop/sandbox/all:on
@drop/allow/write+/var/log/myapp/**
# manual: a program switches to a prepared domain itself, issuing
# cmd/move!untrusted before it parses untrusted input.
domain+@untrusted
@untrusted/sandbox/all:on
# remove one move with '-', or every move of a kind with '^':
move/@net/exec-/usr/bin/curl
move/@net/exec^
π§ Proxy Sandboxing β¨
As of version 3.22.0, Proxy sandboxing in Syd confines network communication exclusively through a designated SOCKS proxy, enforced by the helper utility syd-tor(1). Configured at startup with sandbox/proxy:on, this type implies the use of unshare/net:1, isolating network namespaces to prevent direct network access. Traffic is forwarded from a specified local port (proxy/port:9050) to an external address and port (proxy/ext/host:127.0.0.1, proxy/ext/port:9050). As of version 3.34.1, you may also specify an external UNIX domain socket using e.g. proxy/ext/unix:/path/socks5.sock. All network traffic routes through the proxy using zero-copy data transfers and edge-triggered epoll(7) for event handling. The implementation uses seccomp and Landlock for additional confinement, preventing unauthorized network access outside the defined proxy path. π«Ά
π PTY Sandboxing π
As of version 3.36.0, PTY Sandboxing runs the target process inside a dedicated pseudoterminal managed by the syd-pty(1) helper, isolating all terminal I/O from the host TTY and preventing direct ioctl(2) or control-sequence escapes. The PTY main is proxied via an edge-triggered epoll(7) loop with non-blocking zero-copy splice(2), so no data traverses user space in the clear. A minimal seccomp(2) filter allows only the required PTY ioctls (e.g. TIOCGWINSZ, TIOCSWINSZ) and denies all others β including injection via TIOCSTI β while Landlock locks down access to the PTY device, filesystem, and network. No-exec memory seals and namespace isolation further harden against terminal-based attacks. π
π Memory Sandboxing π
This sandboxing category handles the system calls brk(2), mmap(2), mmap2(2), and mremap(2) and checks the per-process memory usage on each memory allocation request. If the memory usage reaches the maximum value defined by mem/max, the system call is denied with ENOMEM. The virtual memory size can also be limited using mem/vm_max. If the limit is reached on the entry of any of the respective system calls, the system call is denied with ENOMEM and the signal SIGKILL is delivered to the offending process. Subsequent to the delivery of the signal, the process_mrelease(2) system call is called on the process to immediately release memory. The default action may be changed using the default/mem option. The per-process memory usage is a fair estimate calculated using the file proc_pid_smaps(5) summing the following fields together:
Pss(Proportional Set Size) is similar toRss, but accounts for shared memory more accurately by dividing it among the processes that share it.Rss(Resident Set Size) is the portion of memory occupied by a process that is held in RAM.Private_Dirtyrepresents the private memory that has been modified (dirty).Shared_Dirtyrepresents the shared memory that has been modified.
As of version 3.43.1, the memory sandboxing system has been updated to improve memory usage tracking. Syd now enforces a strict memory limit based on allocation granularity, meaning that programs cannot exceed the defined memory limits, even by the amount they allocate at once. This change aligns the limit with the allocation size rather than allowing any overflow beyond the limit. Additionally, memory tracking has been optimized by switching from iterating over proc_pid_smaps(5) to using the more efficient /proc/pid/smaps_rollup, which consolidates memory usage information for better performance and more accurate enforcement of memory constraints. π₯
Memory sandboxing is not an alternative tocgroups(7)! You should usecgroups(7)when you can instead. This sandboxing category is meant for more constrained environments wherecgroups(7)is not supported or not available due to missing permissions or other similar restrictions. π
π PID sandboxing π
This sandboxing category handles the system calls fork(2), vfork(2), clone(2), and clone3(2) and checks the total number of tasks running on the system on each process creation request. If the count reaches the maximum value defined by pid/max, the system call is denied with EAGAIN. If pid/kill is set to true, the signal SIGKILL is delivered to the offending process. This sandboxing category is best coupled with a pid namespace using unshare/pid. In this mode, Syd will check the number of running tasks in the current namespace only.
As of version 3.40.0, with unshare/pid:1 the limit and accounting apply per PID namespace; on Linux 6.14 and newer the namespaced kernel.pid_max sysctl(8) is set to max(pid/max, 301) so the kernel's 300 reserved PIDs do not reduce the configured headroom, and on older kernels kernel.pid_max sysctl(8) is not modified. β
PID sandboxing is not an alternative tocgroups(7)! You should usecgroups(7)when you can instead. This is meant for more constrained environments wherecgroups(7)is not supported or not available due to missing permissions or other similar restrictions. π
π SafeSetID π
SafeSetID, introduced in version 3.16.8, enhancing the management of UID/GID transitions. This feature enables finer-grained control by allowing administrators to explicitly specify permissible transitions for UID and GID changes, thus tightening security constraints around process privilege management. It works by allowing predefined UID and GID transitions that are explicitly configured using the setuid+<source_uid>:<target_uid> and setgid+<source_gid>:<target_gid> commands in the Syd configuration. This ensures that transitions can only occur between specified user and group IDs, and unauthorised privilege escalations are blocked. For instance, a transition might be allowed from a higher-privileged user to a less-privileged user but not vice versa, preventing any escalation of privileges through these system calls. π
As of version 3.24.5, Syd applies a kernel-level seccomp(2) filter by default to terminate all set*uid system calls with UID less than or equal to 11 which is typically the operator user, and all set*gid system calls with GID less than or equal to 14 which is typically the uucp group. This means even a compromised Syd process cannot change privileges using these system calls. As of version 3.54.0, the option trace/allow_unsafe_setid may be used to deny these system calls with EPERM ("Operation not permitted") rather than terminate the offending process. Refer to the output of the command syd-ls setid to see the full list of system calls in this group. π¦
Okay so when a UID or GID transition is defined, Syd keeps CAP_SETUID and CAP_SETGID respectively, and the sandbox process inherits these capabilities from Syd. Since version 3.24.6, Syd drops the CAP_SETUID capability after the first successful UID transition, and similarly drops the CAP_SETGID capability after the first successful GID transition. This means Syd can only ever change its UID and GID once in its lifetime. <emoji>π</emoji> But bestie, this does not completely lock the setid system calls in the sandbox process: transitions to Syd's current UID and GID are continued in the sandbox process, which means the first successful UID and GID transition will continue to function as long as the sandbox process keeps the respective CAP_SETUID and CAP_SETGID capabilities. This allows containing daemons, such as nginx(1), which spawn multiple unprivileged worker processes out of a single main privileged process. slay for daemon containment tbh π
π» Ghost mode π»
Ghost Mode, introduced in Syd version 3.20.0, is a one-way sandboxing mode, closely resembling seccomp(2) Level 1, also known as Strict Mode. This mode enhances security by allowing a process to transition to a highly restrictive state after completing its initial setup. it's giving final form energy β¨
When a sandboxed process is ready for this higher level of confinement, it invokes Ghost Mode by executing the stat(2) system call with the virtual path /dev/syd/ghost. Upon receiving this command, Syd closes the seccomp_unotify(2) file descriptor. This action elevates all previously hooked system calls to a kernel-level deny with the ENOSYS ("Function not implemented") errno(3), making them unavailable. The transition to Ghost Mode is irreversible; once the file descriptor is closed, the process is locked into this restricted state. Only a minimal set of system calls remain available. no take-backs, no escape hatch, periodt π«Ά
The name reflects that, upon closing the seccomp_unotify(2) file descriptor, the sandboxed process effectively detaches from Syd and becomes independent, much like a ghost. Entering ghost mode subsequently causes the syd_mon monitor thread and all syd_emu emulator threads to exit, and the remaining syd_main thread merely waits for the sandbox process to exit without any further intervention. ghosted by your own sandbox, iconic π»π
A process cannot enter Ghost mode once the sandbox lock is set. Alternatively, though, a process can set its process dumpable attribute to zero using the PR_SET_DUMPABLE prctl(2). Under Syd, this achieves almost the same effect as Syd will not be able to emulate system calls with the per-process directory inaccessible. This provides an unprivileged way to enter Ghost mode. lowkey a hack but a legal one π§
π‘οΈ SECURITY π‘οΈ
Syd stands out for its ability to operate without requiring elevated privileges, eliminating the need for root access. This feature significantly simplifies setup and usage. Users benefit from the capability to dynamically configure the sandbox from within, with options to secure it further as needed. no root required bestie, we love accessible security π
Tip: To take a quick peek at the seccomp filters applied by Syd under various different configurations, usesyd <flags...> -Epfcwhere PFC stands for Pseudo Filter Code, which yields a human-readable textual dump of Syd'sseccomp(2)filters. Syd further enrichens the output of this textual dump with#comments. iykyk, peek behind the curtain β¨
π§ Threat Model π§
Syd strictly adheres to the current threat model of seccomp(2). The goal is to restrict how untrusted userspace applications interact with the shared OS kernel through system calls to protect the kernel from userspace exploits (e.g., shellcode or ROP payload). The kernel is trusted. Syd's threat model delineates the sandbox as the trusted interceptor of system calls, while all user applications running within the sandbox are considered untrusted. These untrusted applications can manipulate their execution environment through syscalls, and attackers are assumed to have the capability to execute arbitrary code within these applications. so basically: trust the sandbox, do NOT trust the apps inside it π
Syd uses several mechanisms, including seccomp(2) and ptrace(2) for syscall filtering, landlock(7) for filesystem access restrictions, and namespaces(7) for process and device isolation, to limit the impact of these potential attacks. The threat model assumes that attackers have control over the untrusted user space and may attempt reads, writes, or arbitrary code execution that could influence the behavior of the trusted sandbox or exploit syscall handling. The security of Syd relies on the correctness of its implementation and the underlying Linux kernel features it utilises. It is assumed that there are no vulnerabilities in Syd's interception and handling of syscalls, nor in the enforcement mechanisms provided by landlock(7) and namespaces(7). External attacks via network vectors or physical access to hardware are considered out of scope for this threat model. out of scope = not this page's problem, bestie π¦
"The sandbox lock" is an integral component of Syd's security architecture, which governs the configurability and integrity of the sandbox environment. By default, the sandbox lock is set to on, preventing any further sandbox commands after the initial setup. Once the sandbox is configured and the primary process is executed, the security policies remain unaltered by any untrusted processes within the sandbox. When the lock is set to exec, only the initial sandbox process retains the authority to access and modify the sandbox configuration, enabling a trusted process to securely establish the sandbox parameters while maintaining a pidfd (process ID file descriptor) to the initial process to safeguard against PID recycling attacks. pid recycling attacks stay losing π₯
Conversely, if the lock is set to off, all sandbox processes are permitted to access and modify the sandbox configuration, allowing for broader configurability during the setup phase. However, this state persists only until the sandbox is explicitly locked, after which the lock becomes immutable and the sandbox policies are fixed, preventing any subsequent processes from altering the configuration. This layered locking mechanism, reinforced by the use of pidfd in exec mode, effectively safeguards against untrusted processes attempting to modify sandbox settings to escalate privileges or circumvent restrictions, maintaining a secure execution environment within Syd's framework. layered lock rights >>> everything π
In ipc mode, the sandbox configuration is accessible through a UNIX socket which may or may not be accessible from within the sandbox depending on sandbox ACL rules. In read mode, the sandbox configuration is accessible only to reads, but NOT edits. Transition from lock modes off, exec, and ipc into one of read and on is one-way and idempotent: it results in the sandbox policy getting sealed in memory using the mseal(2) system call either immediately or simultaneously with sandbox process startup. Transitions between lock modes read and on are not permitted. one-way door, no flip-flopping, no cap πͺβ¨
π΅οΈ Accessing remote process memory π΅οΈ
Syd denies various system calls which can access remote process memory such as ptrace(2) and process_vm_writev(2), and common sandboxing profiles such as paludis and user disallow write access to the /proc/pid/mem file. This makes TOCTOU attack vectors harder to realise. TOCTOU girlies stay mad π Refer to the output of the command syd-ls deny to see the full list of denied system calls. go check the receipts, bestie π§Ύ
πͺ Enhanced Handling of PTRACE_TRACEME πͺ
As of version 3.16.3, Syd introduced a new feature for managing the PTRACE_TRACEME operation, aimed at improving stealth against detection. Traditionally, PTRACE_TRACEME is the only ptrace(2) operation allowed by a tracee, which makes it a common target for detection of ptracers. By converting PTRACE_TRACEME into a no-operation (no-op) that always succeeds, Syd aims to subtly prevent simple detection methods that rely on this operation. not me making the detection probe justβ¦ work π
Additionally, other ptrace(2) operations are modified to return an EPERM ("Operation not permitted") errno(3) instead of ENOSYS ("Function not implemented"), which helps reduce the likelihood of the sandbox being detected through these errors. This approach enhances the discreetness of Syd's operation by mitigating straightforward detection tactics used by monitored processes. stealth mode: activated π₯·
As of version 3.19.0, Syd extends this mitigation and turns the system call ptrace(2) into a no-op. Again, this provides a best-effort mitigation against using requests such as PTRACE_ATTACH or PTRACE_SEIZE to detect a ptracer. best-effort but make it cute β¨
As of version 3.47.0, Syd improves this mitigation and turns the prctl(2) calls with PR_SET_PTRACER argument into a no-op. another one bites the dust π₯
As of version 3.47.0, Syd improves this mitigation to defend against intelligent ptrace(2) detectors which use multiple ptrace(2) requests to detect a ptracer. Refer to the following links for more information on intelligent ptrace(2) detection:
https://arxiv.org/pdf/2109.06127https://seblau.github.io/posts/linux-anti-debugginghttps://docs.rs/debugoff
π Hardened procfs and devfs π
Syd enforces restrictions on procfs(5) and devfs file systems by implementing several key measures: denying both the listing and opening of block devices and files of unknown types by omitting entries corresponding to these file types (identified by DT_BLK and DT_UNKNOWN) from directory listings and rejecting open(2) operations on them. This prevents unauthorized enumeration and access to system storage devices, mitigating information disclosure and potential tampering. block devices stay hidden, periodt π«πΎ
Syd also restricts visibility within the /proc directory so that processes can only see their own process IDs, preventing discovery and potential interaction with other running processes, which reduces risks of information leakage, privilege escalation, and process manipulation. Access to the /proc entries of the Syd process itself is explicitly denied, safeguarding the sandbox manager from inspection or interference and preventing access to sensitive information about the sandboxing mechanism that could be exploited to bypass security controls or escape the sandbox. mind your own /proc, bestie π
Additionally, Syd addresses risks associated with magic symbolic links in /proc -- such as /proc/[pid]/exe and /proc/[pid]/fd/* -- by denying access to these links when they refer to processes other than the calling process, thus preventing exposure of sensitive file descriptors or executable paths of other processes and mitigating unauthorized access or container escape scenarios; this mitigation can be disabled with the trace/allow_unsafe_magiclinks:1 option if necessary, though doing so is not recommended. not recommended = don't do it unless you absolutely must, iykyk π
Together, these restrictions confine sandboxed processes to the minimum required visibility. Refer to the following links for more information:
https://forums.whonix.org/t/proc-pid-sched-spy-on-keystrokes-proof-of-concept-spy-gksu/8225https://homes.luddy.indiana.edu/xw7/papers/zhou2013identity.pdfhttps://petsymposium.org/2016/files/papers/Don%E2%80%99t_Interrupt_Me_While_I_Type__Inferring_Text_Entered_Through_Gesture_Typing_on_Android_Keyboards.pdfhttps://staff.ie.cuhk.edu.hk/~khzhang/my-papers/2016-oakland-interrupt.pdfhttps://www.cs.ucr.edu/~zhiyunq/pub/sec14_android_activity_inference.pdfhttps://www.gruss.cc/files/procharvester.pdfhttps://www.kicksecure.com/wiki/Dev/Strong_Linux_User_Account_Isolation#/proc/pid/sched_spy_on_keystrokeshttps://www.openwall.com/lists/oss-security/2011/11/05/3https://www.usenix.org/legacy/event/sec09/tech/full_papers/zhang.pdfhttps://www.openwall.com/lists/oss-security/2025/11/05/3
π©Ί Hardened proc_pid_status(5)
ok so as of version 3.38.0, Syd filters proc_pid_status(5) right at the open(2) boundary β like a bouncer checking IDs at the door β to defeat the common sandbox-fingerprinting heuristics while still keeping compatibility with ordinary tooling. balance, bestie π
When a process (or its threads, yeah them too) reads /proc/<pid>/status or /proc/<pid>/task/<tid>/status, Syd normalizes only the security-critical fields: zeroes out TracerPid, NoNewPrivs, Seccomp, and Seccomp_filters, and rewrites the sandbox-revealing phrases in Speculation_Store_Bypass and SpeculationIndirectBranch. This targeted normalization breaks those trivial anti-analysis checks (ptracer presence, seccomp/no_new_privs probes, speculative mitigation fingerprints) without altering process state. no cap πΈ
The security impact is twofold, and it's giving main character energy on both fronts: untrusted code loses a low-cost oracle for environment discovery β which reduces the likelihood of logic bombs or capability gating based on sandbox detection β and defenders retain observability because the kernel's real enforcement still applies. basically only the user-space view of these select fields is masked. the actual rules? untouched. π
For forensic and debugging workflows that explicitly need the unfiltered view, this mitigation can be temporarily relaxed per trace with trace/allow_unsafe_proc_pid_status:1, and after that toggling back to :0 restores the hardened, stealth-preserving default. cute lil toggle, love that for you π
π·οΈ Hardened uname(2)
As of version 3.15.1, Syd mediates uname(2) and returns a policy-governed utsname that suppresses host identification and constrains kernel disclosure. The release string is synthesized to expose only the Linux major and minor as observed on the host β or, as of 3.36.1, as supplied via SYD_ASSUME_KERNEL for controlled feature detection β while the micro component is randomized per Syd run to limit patch level fingerprinting. and reads of /proc/version and /proc/sys/kernel/osrelease? hardened to present the same masked view. consistent, we stan π¦
As of 3.40.0, the nodename, domainname, and version fields are sourced from the options uts/host, uts/domain, and uts/version with defaults localhost, (none), and a startup random value. As of 3.44.2, this restriction may be relaxed at startup with the option trace/allow_unsafe_uname:1.
Practical effects, listified because we're organized like that β¨:
- disrupting exploit and loader selection that depend on exact release matching
- reducing cross host correlation via stable node and domain labels
- neutralizing sandbox and VM fingerprinting heuristics that key off
uname(2)and the correspondingproc(5)paths - keeping build and compatibility probes functional by retaining
major.minorsemantics while allowing explicit control throughSYD_ASSUME_KERNEL
Workloads that tie licensing, clustering, telemetry, or feature gates to the precise host release or to the original nodename should use the uts options to supply the required identity, or opt out with the relaxation flag. fr fr, don't get caught out π
π Restricting fcntl operations and trace/allow_unsafe_fcntl
As of version 3.54.0, Syd restricts potentially unsafe fcntl(2) operations that could be used to bypass landlock(7) signal scoping protections. By default, Syd denies the F_SETOWN, F_SETOWN_EX, and F_SETSIG operations β which, when combined with O_ASYNC signal-driven I/O, can be exploited to deliver arbitrary signals, including SIGKILL, to processes outside the landlock(7) domain that share the same process group. π
This restriction closes a signal scoping bypass vulnerability in the SIGIO delivery path, where a landlock(7) restricted process could use fcntl(2) with a process group ID to fan signals out to all members of the group, including non-sandboxed processes. like, rude?? π The option trace/allow_unsafe_fcntl:1 may be set at startup to lift this restriction when such signal-driven I/O functionality is required.
ποΈ Restricting prctl option space and trace/allow_unsafe_prctl
Syd confines the scope of permissible prctl(2) operations to enhance security within its sandbox environment. By limiting available prctl(2) options to a specific set β including but not limited to PR_SET_PDEATHSIG, PR_GET_DUMPABLE, PR_SET_NO_NEW_PRIVS, and PR_SET_SECCOMP β only the process control operations required for correct sandbox behaviour remain available, reducing exposure to under-scrutinised prctl(2) calls. π
This restriction is important for preventing security vulnerabilities associated with broader prctl(2) access, such as unauthorised privilege escalations or manipulations of process execution states. However β recognizing the need for flexibility in certain scenarios β Syd offers the option to lift these restrictions through the trace/allow_unsafe_prctl:1 setting. This allows a more permissive prctl(2) environment when required. your call, bestie π
π« Restricting creation of device special files
Since version 3.1.12, Syd has enhanced its security model by disallowing the creation of device special files through the mknod(2) and mknodat(2) system calls. This decision is rooted in mitigating potential security vulnerabilities, as device special files could be exploited to circumvent established path-based access controls within the sandbox environment. π¦
These files β which include character and block devices β can provide direct access to hardware components or enable interactions with kernel modules that could lead to unauthorised actions or data exposure. By restricting their creation, Syd significantly reduces the risk of such exploit paths, and only predefined types of files are permissible β namely FIFOs, regular files, and sockets β aligning with the principle of least privilege by limiting file system operations to those deemed safe within the sandbox's context. periodt β¨
π‘ Sharing Pid namespace with signal protections
Since version 3.6.7, Syd prevents sandboxed processes from sending signals to the Syd process or any of its threads. This protection is implemented by hooking and monitoring system calls related to signal operations, including kill(2), tkill(2), tgkill(2), and pidfd_open(2). When a sandboxed process attempts to send a signal to Syd or its threads, these system calls are intercepted, and the operation is denied at the seccomp level with an EACCES ("Permission denied") errno(3). not today, bestie π
This ensures Syd maintains control over the execution and management of sandboxed processes, safeguarding against interruptions or unauthorised interactions that could compromise the security or stability of the sandbox environment. This mechanism is part of Syd's broader strategy to share the same root, private proc, and mount namespaces with the sandboxed process for simple system call emulation. By making Syd and its threads immune to signals from sandboxed processes, the integrity and isolation of the sandboxed environment are enhanced, preventing sandboxed processes from disrupting Syd. π«Ά
As of version 3.35.2, a scope-only landlock(7) sandbox is installed unconditionally to further isolate the sandbox process from the Syd process. Terminal-generated signals and I/O remain confined to the sandbox's process group and cannot affect Syd or any other processes, further strengthening the sandbox's isolation guarantees alongside the existing seccomp(2) based pid_namespaces(7) protections. iconic, honestly π
βοΈ Process Priority and Resource Management
Since version 3.8.1, Syd has been implementing strategies for smooth host system operation while managing security through its sandboxing mechanism. It sets the nice(2) value of its system call handler threads to 19, which places them at the lowest priority to minimise CPU starvation for other processes. This approach prioritises system stability and fair CPU resource distribution, enabling Syd to handle numerous system calls without degrading host performance. considerate king π
Enhancing this strategy, Syd introduced further adjustments in versions 3.8.6 and 3.9.7 to address I/O and CPU resource management more comprehensively. From version 3.8.6, it sets the I/O priority of the system call handler threads to idle, preventing I/O operations from monopolising resources and starving other processes. Similarly, from version 3.9.7, it adjusts the CPU scheduling priority of these threads to idle, further guarding against CPU starvation. These measures collectively keep Syd's overhead low while sandboxing applications. π
As of version 3.30.0, changes in process and I/O priorities are inherited by sandbox processes as well, and sandbox processes are prevented from making any further changes. The option trace/allow_unsafe_nice may be set at startup to prevent Syd from making any changes and allow sandbox processes access to the system calls that are used to make process and I/O priority changes.
π§Έ Restricting Resource Limits, Core Dumps, and trace/allow_unsafe_prlimit
Since version 3.9.6, Syd has implemented restrictions on setting process resource limits and generating core dumps for the sandboxed process. This prevents the sandboxed process from altering resource limits or producing core dumps. Use trace/allow_unsafe_prlimit:1 to disable this restriction. simple as that, no notes π
π° Enhancing Sandbox Security with Landlock
Since version 3.0.1, Syd uses landlock(7) to enforce advanced filesystem sandboxing for sandboxed processes. By integrating Landlock, Syd allows even unprivileged processes to create sandboxes with fine-grained access control over filesystem operations without requiring elevated permissions. this is the good stuff πβ¨
This mitigates the risk of security breaches stemming from bugs or malicious behaviors in applications by restricting ambient rights, such as global filesystem or network access. Landlock operates by allowing processes to self-impose restrictions on their access to system resources, effectively creating a secure environment that limits their operation to a specified set of files and directories. This mechanism is particularly useful for running legacy daemons or applications that require specific environmental setups, as it allows for the precise tailoring of access rights, limiting processes to designated parts of the filesystem. π¦
For instance, by setting Landlock rules, Syd can confine a process's filesystem interactions to read-only or read-write operations on explicitly allowed paths, thus preventing unauthorised access to sensitive areas of the system. chef's kiss π
Like, the fact that Syd puts itself inside the Landlock-enforced sandbox too? That's the extra layer of security, bestie. It means even if the Syd process itself got compromised, the attacker's ability to manipulate the sandboxed environment or access unauthorised resources would be significantly constrained. Self-sandboxing is main character energy fr fr. π
π¦ Namespace Isolation in Syd π¦
Syd levels up sandbox isolation through namespaces, starting from version 3.0.2. Then version 3.9.10 added a key enhancement by restricting user subnamespace creation β which addressed a path sandboxing bypass vulnerability. Not me being obsessed with a strategic limitation that thwarts sandboxed processes from altering their namespace environment to access restricted filesystem areas. π
And since version 3.11.2, Syd maintains process capabilities within user namespaces, mirroring the unshare(1) command's --keep-caps behavior β so sandboxed processes get to retain necessary operational capabilities. Iconic.
Plus Syd utilises the powerful bind command within the mount namespace to create secure, isolated environments, letting specific filesystem locations be remounted with custom attributes β like ro, noexec, nosuid, nodev, or nosymfollow β to further restrict sandboxed processes' access to the filesystem. Slay. β¨
Syd also introduces enhanced isolation within the mount namespace by offering options to bind mount temporary directories over /dev/shm and /tmp, giving sandboxed processes private instances of these directories. This prevents inter-process communication through shared memory and mitigates the risk of temporary file-based attacks, further solidifying the sandbox's defence mechanisms. It's giving fortress. π°
- As of version
3.35.2, an empty mount namespace may be built from scratch starting with theroot:tmpfscommand. - As of version
3.11.2, Syd mounts theprocfs(5)filesystem privately with thehidepid=2option, enhancing privacy by concealing process information from unauthorised users. - As of version
3.37.2, this option is changed tohidepid=4, which is new inLinux>=5.8for added hardening. - As of version
3.39.0, the optionsubset=pidis also supplied to the privateprocfs(5)mount for added hardening. (This option is also new inLinux>=5.8.) π«Ά
Syd's container and immutable profiles exemplify its adaptability, offering everything from isolated to highly restrictive environments. The container profile provides a general-purpose sandbox, while the immutable profile enforces stricter controls β such as making key system directories read-only β to prevent tampering. This approach illustrates Syd's use of kernel features for sandbox security. See syd-cat -pcontainer and syd-cat -pimmutable to list the rules in these sandboxing profiles.
As of version 3.23.0, Syd has further strengthened its security with the introduction of a time namespace, represented by the unshare/time:1 option β it allows Syd to reset the boot-time clock so that the uptime(1) command reports container uptime instead of host uptime. β°
The creation of namespaces, including mount, UTS, IPC, user, PID, net, cgroup, and time is denied by default to prevent unauthorized namespace manipulation that could undermine path sandboxing security. To allow specific namespace types, administrators must explicitly enable them via the trace/allow_unsafe_namespace setting.
Another restriction to note: the system calls mount(2), mount_setattr(2), umount(2), and umount2(2) are denied by default unless mount namespace is allowed. This change tightens control over process capabilities and isolation, reinforcing the defense mechanisms against potential security breaches. π
π Restricting environment and trace/allow_unsafe_env π
As of version 3.11.1, Syd has implemented measures to clear unsafe environment variables, such as LD_PRELOAD, enhancing security by preventing the manipulation of dynamic linker behavior by sandboxed processes. This action mitigates risks associated with dynamic linker hijacking, where adversaries may load malicious shared libraries to execute unauthorised code β potentially leading to privilege escalation, persistence, or defence evasion. Like, rude.
Variables like LD_PRELOAD allow specifying additional shared objects to be loaded before any others, which could be exploited to override legitimate functions with malicious ones, thus hijacking the execution flow of a program. To accommodate scenarios where developers might need to use these variables for legitimate purposes, Syd allows this security feature to be disabled at startup with trace/allow_unsafe_env:1.
Refer to the output of the command syd-ls env to see the full list of environment variables that Syd clears from the environment of the sandbox process. π
As of version 3.39.0, Syd additionally clears LANG and the full set of LC_* locale variables (e.g. LC_CTYPE, LC_TIME, LC_ALL, etc.) to avoid leaking locale settings into the sandboxed process β preventing subtle behavior differences or information disclosure that could be abused. Similarly, the TZ variable is cleared to prevent leaking timezone settings to the sandbox process. The builtin linux profile masks the file /etc/localtime and the glob(3p) pattern /usr/share/zoneinfo/** with the file /usr/share/zoneinfo/UTC, preventing another vector of timezone settings leaking into the environment of the sandbox process. No timezone leaks on my watch, periodt. π
For controlled exceptions, the CLI -e flag provides per-variable control:
-e var=valinjectsvar=valinto the child environment-e varremovesvarfrom the child environment-e var=explicitly passes through an otherwise unsafe variable
Any of these forms may be repeated as needed. π
π Managing Linux Capabilities for Enhanced Security π
Since its 3.0.17 release, Syd strategically curtails specific Linux capabilities(7) for sandboxed processes to strengthen security. By revoking privileges such as CAP_SYS_ADMIN among others, Syd reduces the risk of privilege escalation and system compromise. Even if a sandboxed process is compromised, its ability to perform sensitive operations is severely limited.
The full list of dropped capabilities β including but not limited to CAP_NET_ADMIN, CAP_SYS_MODULE, and CAP_SYS_RAWIO β limits what a compromised process can do. Refer to the output of the command syd-ls drop to see the full list of capabilities(7) that Syd drops at startup. π
Exceptions, introduced in version 3.11.1, offer a more granular security model:
- retaining
CAP_NET_BIND_SERVICEwithtrace/allow_unsafe_bind:1 CAP_NET_RAWwithtrace/allow_unsafe_socket:1CAP_SYSLOGwithtrace/allow_unsafe_syslog:1CAP_SYS_TIMEwithtrace/allow_unsafe_time:1
These exceptions allow for necessary network, syslog and time adjustments within the sandbox, providing flexibility without significantly compromising security. Balance, bestie. βοΈ
Since version 3.12.5, Syd allows the user to prevent dropping capabilities at startup using the command trace/allow_unsafe_caps:1. This command may be used to construct privileged containers with Syd.
π§ Path Resolution Restriction For Chdir and Open Calls π§
In Syd version 3.15.1, a configurable security feature is available to address the risk of directory traversal attacks by restricting the use of .. components in path arguments for chdir(2), open(2), openat(2), openat2(2), and creat(2) system calls.
Heads up: this feature is off by default, providing broad compatibility and operational flexibility for a range of applications.
When enabled with the trace/deny_dotdot:1 command, Syd strengthens its defence mechanisms against unauthorised directory access, echoing the flexibility seen in FreeBSD's vfs.lookup_cap_dotdot sysctl. This allows for a targeted approach to filesystem security, where administrators can tailor the sandbox's behavior to match specific security requirements or operational contexts.
By drawing on the security insights of FreeBSD and HardenedBSD, Syd provides a versatile toolset for managing path traversal security, adaptable to the unique demands of various application environments. Refer to the following links for more information:
https://man.freebsd.org/cgi/man.cgi?open(2)https://cgit.freebsd.org/src/tree/sys/kern/vfs_lookup.c#n351
π Enhanced Symbolic Link Validation π
As of version 3.13.0, Syd enhances security by enforcing stricter validation on symbolic links within /proc/pid/fd, /proc/pid/cwd, /proc/pid/exe, and /proc/pid/root, addressing potential misuse in container escape scenarios.
Specifically, Syd returns an EACCES ("Permission denied") errno(3) for attempts to resolve these symbolic links if they do not pertain to the current process β akin to implementing RESOLVE_NO_MAGICLINKS behavior of the openat2(2) system call. This measure effectively hardens the sandbox against attacks exploiting these links to access resources outside the intended confinement, strengthening the isolation provided by Syd and mitigating common vectors for privilege escalation and sandbox escape.
As of version 3.14.5, Syd keeps intercepting path system calls even if sandboxing is off, making this protection unconditional. Unconditional protection is so iconic honestly. π
π₯ Trusted Symbolic Links π₯
As of version 3.37.2, Syd implements a strict symbolic-link hardening mechanism that intercepts every symlink(7) resolution within untrusted directories β those marked world-writable, group-writable, or carrying the sticky bit β and denies any follow operation, returning EACCES ("Permission denied"). This behavior mirrors GrSecurity's CONFIG_GRKERNSEC_LINK and guarantees that symlink chains in shared or temporary locations cannot be weaponized for TOCTOU or link-trick exploits. Ngl that's a vibe check and the symlinks FAILED. π
Under the default policy, neither direct nor nested symlinks in untrusted paths will be traversed, and the check is applied at the seccomp(2) interception layer prior to any mutable state changes β for an early, fail-close enforcement.
Administrators may relax this restriction at startup or runtime by enabling the trace/allow_unsafe_symlinks:1 option, which restores legacy symlink behavior for compatibility β but at the cost of re-exposing potential link-based race vulnerabilities. Choose wisely, bestie. π
Refer to the following links for more information:
https://wiki.gentoo.org/wiki/Hardened/Grsecurity2_Quickstarthttps://en.wikibooks.org/wiki/Grsecurity/Appendix/Grsecurity_and_PaX_Configuration_Options#Linking_restrictionshttps://xorl.wordpress.com/2010/11/11/grkernsec_link-linking-restrictions/https://man7.org/linux/man-pages/man5/proc_sys_fs.5.html
π Trusted Hardlinks π
okay so as of version 3.37.4, Syd rolled out a Trusted Hardlinks policy and honestly it's giving main character energy in the vuln-mitigation department β¨ Here's the vibe: there's a whole class of vulnerabilities that comes from unsafe hardlink creation β specifically the ones that let people pull off time-of-check-to-use-of-time (TOCTOU) exploitation and privilege escalation in shared filesystem environments. So Syd just... says no π
This mitigation puts strict constraints on which files are even allowed to be linked, based on their visibility, their mutability, and their privilege-related attributes. A file can only be a hardlink target if the caller can access it for both reading and writing β so no immutable or opaque targets, because those are exactly the kind of thing that gets used in multi-stage attack chains. On top of that, the file has to be a regular file, and it can't have any privilege-escalation enablers like the set-user-ID bit, or that combo of set-group-ID + group-executable permissions. π
The way these checks work: they're performed preemptively and unconditionally during syscall handling, which means Syd isn't relying on ambient filesystem state at all and stays intact even in adversarial conditions. Iconic behaviour honestly π«Ά
Admins can relax this for compatibility with the trace/allow_unsafe_hardlinks:1 option β but bestie, please know that doing so reintroduces well-documented link-based race conditions and completely undermines the guarantees Syd's secure execution model gives you. So like... your call, but π
Want more info? Here's the reading list:
- https://wiki.gentoo.org/wiki/Hardened/Grsecurity2_Quickstart
- https://en.wikibooks.org/wiki/Grsecurity/Appendix/Grsecurity_and_PaX_Configuration_Options#Linking_restrictions
- https://xorl.wordpress.com/2010/11/11/grkernsec_link-linking-restrictions/
- https://man7.org/linux/man-pages/man5/proc_sys_fs.5.html
π Trusted File Creation π
As of version 3.37.4, Syd also enforces a strict Trusted File Creation policy, and this one's designed to shut down the longstanding race-condition vulnerabilities that come with unprivileged use of O_CREAT in shared or adversarial environments. πΈ
So it builds on the Linux kernel's protected_fifos and protected_regular sysctls β plus the stricter semantics of grsecurity's CONFIG_GRKERNSEC_FIFO β and it blocks all O_CREAT operations that target pre-existing FIFOs or regular files, unless the calling process owns the file and the file is neither group-writable nor world-writable. And that's completely regardless of the parent directory's ownership or permissions, which is the spicy part π₯
Unlike upstream Linux, which lets certain accesses through if the file sits in a directory owned by the caller, Syd straight-up eliminates that dependency. Why? To close subtle privilege-boundary gaps and keep enforcement consistent and capability-centric, even in nested namespace or idmapped mount scenarios. This means users can't preempt or hijack file-based IPC or partial writes through shared directories β and usability is kept alive through precise capability trimming. π
If you've got legacy workloads or a permissive setup, you can selectively disable this with trace/allow_unsafe_create:1 β though π¬ doing so reintroduces exposure to well-documented filesystem race attacks.
And as of version 3.45.0, Syd extends this policy to also deny file creation through dangling symbolic links, as part of its filesystem race hardening. Here's how: at the open(2) boundary, the presence of O_CREAT implicitly adds O_NOFOLLOW unless O_EXCL is also specified. So any attempt to create or truncate a path whose final component is a symlink will just fail instead of resolving the link target. No cap. π
This directly addresses the class of vulnerabilities where privileged components get tricked into creating or modifying files behind attacker-controlled symlinks β like:
- CVE-2021-28153 in GLib (file creation via dangling symlink replacement)
- CVE-2018-15664 (docker cp path traversal via symlink and mount races)
- CVE-2019-16884 (runc bind-mount escape through user-controlled symlinked host paths)
- CVE-2021-30465 (runc container escape via crafted
/procand mount races) - CVE-2025-31133 (runc maskedPath abuse to obtain writable procfs bindings)
- CVE-2025-52565 (runc
/dev/consolebind-mount symlink races leading to writable procfs targets) - CVE-2025-52881 (runc redirected writes bypassing LSM enforcement to arbitrary procfs files)
By enforcing fail-closed semantics for all O_CREAT operations that run into symlinks, Syd blocks these patterns even when the higher-level code is out here assuming symbolic links can't influence file creation. π
The receipts:
- https://wiki.gentoo.org/wiki/Hardened/Grsecurity2_Quickstart
- https://en.wikibooks.org/wiki/Grsecurity/Appendix/Grsecurity_and_PaX_Configuration_Options#FIFO_restrictions
- https://xorl.wordpress.com/2010/11/24/grkernsec_fifo-named-pipe-restrictions/
- https://man7.org/linux/man-pages/man5/proc_sys_fs.5.html
- https://nvd.nist.gov/vuln/detail/CVE-2021-28153
- https://github.com/advisories/GHSA-9hh6-p5c5-mmmf
- https://nvd.nist.gov/vuln/detail/CVE-2018-15664
- https://nvd.nist.gov/vuln/detail/CVE-2019-16884
- https://nvd.nist.gov/vuln/detail/CVE-2021-30465
- https://nvd.nist.gov/vuln/detail/CVE-2025-31133
- https://nvd.nist.gov/vuln/detail/CVE-2025-52565
- https://nvd.nist.gov/vuln/detail/CVE-2025-52881
- https://www.openwall.com/lists/oss-security/2025/11/05/3
- https://github.com/opencontainers/runc/security
- https://www.starlab.io/blog/linux-symbolic-links-convenient-useful-and-a-whole-lot-of-trouble
π¦ Memory-Deny-Write-Execute Protections π¦
Syd version 3.14.1 implements Memory-Deny-Write-Execute (MDWE) protections, aligning with the PR_SET_MDWE and PR_MDWE_REFUSE_EXEC_GAIN functionality that landed in Linux kernel 6.3. What it does: it prevents creating memory mappings that are simultaneously writable and executable, staying super close to the executable space protection mechanisms inspired by the PaX project. π
And Syd doesn't stop there β it fortifies those MDWE protections with kernel-level seccomp filters on the critical system calls, including mmap(2), mmap2(2), mprotect(2), pkey_mprotect(2), and shmat(2). These filters intercept and restrict anything that could contravene MDWE policy, like trying to make non-executable mappings executable, or mapping shared memory segments with executable permissions. So by combining PR_SET_MDWE for preemptive kernel enforcement with seccomp filters for granular, kernel-level control over syscall execution, Syd gives you a real defence against memory exploitation. β¨
You can relax this with the trace/allow_unsafe_exec_memory:1 sandbox command at startup. But heads up: even with the restriction relaxed, Syd is still going to call PR_SET_MDWE β it'll just use the PR_MDWE_NO_INHERIT flag so the MDWE protection doesn't propagate to child processes on fork(2).
As of version 3.25.0, Syd kills the process on memory errors instead of denying these syscalls with EACCES ("Permission denied"). Why is that a slay? Because the system administrator gets a notification via dmesg(1) and has a much higher chance of reacting quickly to investigate potentially malicious activity π₯ Plus, repeated failures are going to trigger SegvGuard. Not me being watched by the sandbox π
As of version 3.37.0, Syd addresses a fundamental architectural vulnerability in the Linux kernel's MDWE implementation through proactive file descriptor writability assessment during memory mapping operations. Big words, but here's the tea: this enhancement directly mitigates Linux kernel bug 219227, which exposes a critical W^X enforcement bypass where adversaries can dodge memory protection by exploiting the semantic disconnect between file-backed memory mappings and their underlying file descriptors. π
The vuln manifests when executable memory regions are mapped with PROT_READ|PROT_EXEC permissions from file descriptors that still hold write access β which enables post-mapping modification of executable memory content through standard file I/O. So basically read-only executable mappings get transformed into mutable code regions, violating fundamental W^X invariants. Rude.
By implementing mandatory writability validation before permitting any file-backed executable memory mapping, Syd enforces strict temporal isolation between memory mapping permissions and underlying file descriptor capabilities. That prevents exploitation of this kernel-level abstraction leakage, which would otherwise enable arbitrary code injection through seemingly benign file operations. And this defence operates at the syscall interception layer, preventing code injection through writable file descriptors backing executable mappings. This restriction may be relaxed using the trace/allow_unsafe_exec_memory:1 sandbox command at startup. π
π Advanced Memory Protection Mechanisms π
Syd version 3.15.1 adds a seccomp BPF hook to block executable+shared memory mappings, targeting a critical vulnerability exploitation pathway. And as of version 3.21.3, Syd also blocks executable+anonymous memory. π―
These updates refine the sandbox's defence against unauthorised memory access and arbitrary code execution by inspecting and filtering system calls β in particular mmap(2) and mmap2(2) β to prevent dangerous memory mapping combos. While this beefed-up security reduces exposure to buffer overflows and code injections, it does acknowledge potential legitimate use cases that might need exceptions, like Just-In-Time (JIT) compilation and plugin architectures. π«Ά
To accommodate those necessary exceptions, Syd lets you relax these restrictions with explicit configuration through the trace/allow_unsafe_exec_memory:1 command.
β Null Address Mapping Prevention β
As of version 3.15.1, Syd introduced an update inspired by HardenedBSD, specifically aimed at bolstering the sandbox's defences against null pointer dereference vulnerabilities. Following HardenedBSD's model, Syd now has a security measure that completely prohibits mapping memory at the NULL address via the mmap(2) and mmap2(2) system calls with the MAP_FIXED and MAP_FIXED_NOREPLACE flags. π«
Seccomp filter rules deny these mapping requests when the first argument (addr) is zero, returning EACCES ("Permission denied"). By preventing allocation at address zero, Syd renders null pointer dereference exploits non-viable, which reduces null pointer dereference exploits. π
Small note: Linux hasvm/mmap_min_addrwhich guards against this already β so this acts as a second layer of defence. Unlike Syd, Linux allows processes with theCAP_SYS_RAWIOcapability to edit/override this value. π
As of version 3.37.0, Syd caps this value at page size like OpenBSD does, for added hardening against such edits. π‘οΈ
As of version 3.25.0, every address lower than the value ofvm/mmap_min_addrat Syd startup gets folded into the seccomp filter, and the action for those is set to kill process rather than deny withEACCES. Why? So the system administrator actually gets a notification viadmesg(1)and has a way higher chance to react soon and investigate the potentially malicious activity. Also β repeated failures are going to trigger SegvGuard. π
π§ Default Memory Allocator Security Enhancement
As of version 3.46.0, Syd has transitioned to using the GrapheneOS allocator as its default memory allocator. Bestie, this is a big deal. This new allocator leverages modern hardware capabilities to provide substantial defenses against common vulnerabilities like heap memory corruption, while reducing the lifetime of sensitive data in memory. The previously used mimalloc with the secure option offered notable security improvements, sure, but the GrapheneOS allocator goes further with features like out-of-line metadata protection, fine-grained randomization, and aggressive consistency checks. It incorporates advanced techniques such as hardware memory tagging for probabilistic detection of use-after-free errors, zero-on-free with write-after-free detection, and randomized quarantines to mitigate use-after-free vulnerabilities. The allocator is designed to prevent traditional exploitation methods by introducing high entropy, random base allocations across multiple memory regions, and it offers a portable solution being adopted by other security-focused operating systems like Secureblue. It also heavily influenced the next-generation musl malloc implementation, improving security with minimal memory usage. Refer to the following links for more information:
π Enhanced Security for Memory File Descriptors
In version 3.21.1, Syd enhanced security by introducing restrictions on memory file descriptors (memfds). The memfd_create(2) system call is now sandboxed under Create sandboxing, with the name argument prepended with !memfd: before access checks. This allows administrators to globally deny access to memfds using rules like deny/create+!memfd:*. Additionally, the memfd_secret(2) system call β which requires the secretmem.enable=1 boot option and is seldom used β was denied to prevent potential exploits. Even though file I/O is restricted on secret memfds, attackers could abuse them to write payloads and map them as executable, thus bypassing denylisted code execution controls. π
Building on these changes, version 3.21.2 further fortifies security by making memfds non-executable by default. This is achieved by removing the MFD_EXEC flag and adding the MFD_NOEXEC_SEAL flag to memfd_create(2), preventing memfds from being made executable. Heads up: the MFD_NOEXEC_SEAL flag requires Linux-6.3 or newer to function. These measures collectively mitigate the risk of memfd abuse, which can involve executing malicious code within a sandbox, circumventing security mechanisms like Exec, Force, and TPE sandboxing. For scenarios where executable or secret memfds are genuinely required, the trace/allow_unsafe_memfd:1 option allows for relaxing these restrictions, though it introduces increased security risks. By default, these restrictions prevent attackers from using memfds as a vector for unauthorized code execution. β¨
π Path Masking
Introduced in version 3.16.7, the Path Masking feature in Syd enhances security by enabling the obfuscation of file contents without denying access to the file itself. This functionality is critical in scenarios where compatibility requires file presence, but not file readability. Path Masking works by redirecting any attempt to open(2) a specified file to the character device /dev/null, effectively presenting an empty file to the sandboxed process. The original file metadata remains unchanged, which is necessary for applications that perform operations based on this data. Masked files retain their execute permission, so a binary subject to the mask runs normally while its content appears empty to read operations. π«₯
Files subject to masking are selected with glob(3p) patterns. By default, Syd masks sensitive paths such as /proc/cmdline to prevent the leakage of potentially sensitive boot parameters, aligning with Syd's security-first design philosophy. Path Masking is a strict security enhancement that minimises the risk of sensitive data exposure while maintaining necessary system functionality and compliance with expected application behaviors. π
𧦠Refined Socket System Call Enforcement
In Syd version 3.16.12, we have strengthened the enforcement of socket system call restrictions within the sandbox using kernel-level BPF filters. This enhancement builds upon existing features by embedding these controls directly into the Syd process; even if Syd is compromised, it cannot utilise or manipulate denied socket domains. This proactive measure restricts socket creation strictly to permitted domains such as UNIX (AF_UNIX), IPv4 (AF_INET), and IPv6 (AF_INET6), restricting socket creation to permitted domains. The trace/allow_unsupp_socket:1 option allows for the extension of permissible socket domains, catering to specific needs but potentially increasing exposure risks. Additionally, trace/allow_safe_kcapi:1 enables access to the Kernel Crypto API, facilitating necessary cryptographic operations directly at the kernel level. These enhancements provide a more secure and configurable environment, allowing administrators precise control over network interactions. π₯
βοΈ Enhanced Execution Control (EEC)
The Enhanced Execution Control (EEC) feature, introduced in Syd version 3.17.0, hardens the sandbox. This feature strategically disables the execve(2) and execveat(2) system calls for the Syd process after they are no longer required for executing the sandbox process, thus safeguarding against their potential abuse by a compromised Syd process. The prohibition of these critical system calls adds a strong layer to the existing Memory-Deny-Write-Execute (MDWE) protections, intensifying the system's defences against exploit techniques such as code injection or return-oriented programming (ROP). Concurrently, EEC ensures that the ptrace(2) syscall is limited following the initial use of the PTRACE_SEIZE call for execution-related mitigations. This action effectively prevents subsequent system trace operations, barring unauthorised process attachments and further securing the system against manipulation. Together, these measures enhance Syd's security architecture. π
As of version 3.17.1, the Enhanced Execution Control (EEC) has been further strengthened by integrating mprotect(2) hardening mechanisms specifically targeting the prevention of the ret2mprotect exploitation technique. This enhancement blocks attempts to alter memory protections to executable (using the PROT_EXEC flag) via the mprotect(2) and pkey_mprotect(2) system calls. By adding these checks, EEC mitigates the risk associated with compromised Syd processes by enforcing strict memory policies that prevent unauthorised memory from becoming executable, blocking ROP and code injection attacks. π‘οΈ
As of version 3.23.9, the Enhanced Execution Control (EEC) feature has been expanded to mitigate Sigreturn Oriented Programming (SROP) attacks by denying access to the system calls sigreturn(2) and rt_sigreturn(2) for syd(1), syd-oci(1), and syd-tor(1). Given the lack of signal handlers, these system calls have no legitimate use. By preventing these calls, the system is better protected against SROP attacks, which involve manipulating signal handler frames to control program state. For further reading, refer to section 2.4.4 Sigreturn-oriented programming in the Low-Level Software Security book (URL: https://llsoftsec.github.io/llsoftsecbook/#sigreturn-oriented-programming). SROP (Bosman and Bos 2014) is a special case of ROP where the attacker creates a fake signal handler frame and calls sigreturn(2), a system call on many UNIX-type systems normally called upon return from a signal handler, which restores the state of the process based on the state saved on the signal handler's stack by the kernel previously. The ability to fake a signal handler frame and call sigreturn gives an attacker a simple way to control the state of the program. π
π΅οΈ Enhanced execve and execveat Syscall Validation
As of version 3.24.2, security enhancements to execve(2) and execveat(2) syscalls have been introduced to thwart simple Return-Oriented Programming (ROP) attacks. Per the Linux execve(2) manpage:
"On Linux, argv and envp can be specified as NULL. In both cases, this has the same effect as specifying the argument as a pointer to a list containing a single null pointer. Do not take advantage of this nonstandard and nonportable misfeature! On many other UNIX systems, specifying argv as NULL will result in an error (EFAULT: "Bad address"). Some other UNIX systems treat the envp==NULL case the same as Linux."
Based on this guidance, Syd now rejects execve(2) and execveat(2) with EFAULT when one of the pathname, argv and envp arguments is NULL. This mitigation targets basic ROP chains where NULL pointers are used as placeholders to bypass argument validation checks, a common tactic in exploiting buffer overflow vulnerabilities. For example, a typical ROP chain trying to execute execve(2) with argv and envp set to NULL would be intercepted and denied under these rules:
0x0000: 0x40ee2b pop rdx; ret
0x0008: 0x0 [arg2] rdx = 0
0x0010: 0x402885 pop rsi; ret
0x0018: 0x0 [arg1] rsi = 0
0x0020: 0x4013cc pop rdi; ret
0x0028: 0x460000 [arg0] rdi = 4587520
0x0030: 0x438780 execve
An attacker might circumvent this mitigation by arranging that none of the critical syscall arguments are NULL. This requires a more complex ROP chain, potentially increasing the complexity of the exploit and reducing the number of vulnerable targets. This focused security measure enhances system resilience against simple ROP exploits while maintaining compliance with POSIX standards, promoting robustness and cross-platform security. π
As of version 3.25.0, Syd terminates the process upon entering these system calls with NULL arguments rather than denying them with EFAULT. This ensures the system administrator gets a notification via kernel audit log, ie. dmesg(1), about potentially malicious activity. In addition, repeated failures are going to trigger SegvGuard. π¨
We also checked and β ngl β the same issue is lowkey chilling on HardenedBSD too, so we told upstream about it π :
- Issue:
https://git.hardenedbsd.org/hardenedbsd/HardenedBSD/-/issues/106 - Fix:
https://git.hardenedbsd.org/hardenedbsd/HardenedBSD/-/commit/cd93be7afbcfd134b45b52961fc9c6907984c85f
β¨ Securebits and Kernel-Assisted Executability β¨
Okay so as of version 3.41.0, Syd sets up the per-thread securebits in a kernel-cooperative way, which is literally the main character energy of privilege handling π. Here's the tea, in order:
- On Linux 6.14 and newer β the ones that ship the executability-check interface (
execveat(2)withAT_EXECVE_CHECK) plus the matching interpreter self-restriction securebits β Syd first tries to install a strict securebits configuration (with locks, bestie, locks) that hardens capability semantics and execution constraints. - If the kernel is like "nope π" and refuses changes because of privilege (e.g.
CAP_SETPCAPisn't present) and returnsEPERM("Operation not permitted"), Syd deterministically degrades to the unprivileged, interpreter-facing policy only. That means it enables and locks a file-descriptor-based executability check, and prohibits interactive snippet execution unless that same kernel probe passes. - On older kernels, the secure-exec policy setup is treated as a no-op and startup just proceeds without altering executability behavior. Chill. Nothing happens. π¦
This initialization is inherited across forks and execs (with the kernel rule that the keep-capabilities base flag is cleared on exec), it's orthogonal to the no_new_privs attribute, and it's designed to be monotonic and predictable under mixed-privilege and mixed-kernel deployments. In practice that means: unsupported features are ignored, permission failures do not abort startup, and the resulting state is the strongest policy the kernel will actually accept. Slay.
Bestie you can opt out of these defaults per deployment, and here's your menu π: -trace/allow_unsafe_exec_script:1β skips the script/file vetting policy. -trace/allow_unsafe_exec_interactive:1β allows interactive interpreter inputs again. -trace/allow_unsafe_exec_null:1β permits legacy exec with NULLargv/envp, as described in the previous subsection. -trace/allow_unsafe_cap_fixup:1β preserves traditional UID/capability-fixup semantics.
Refer to the following links for more information:
https://docs.kernel.org/userspace-api/check_exec.htmlhttps://man7.org/linux/man-pages/man2/execveat.2.htmlhttps://man7.org/linux/man-pages/man7/capabilities.7.htmlhttps://man7.org/linux/man-pages/man2/prctl.2.htmlhttps://man7.org/linux/man-pages/man2/pr_set_securebits.2const.htmlhttps://www.man7.org/linux/man-pages/man2/PR_SET_KEEPCAPS.2const.html
π Enhanced Path Integrity Measures π
As of version 3.17.4, Syd rejects file system paths containing control characters right at the sandbox access-check boundary. And any such path that still manages to reach the logging subsystem gets sanitized before output, so nobody can pull terminal escape-sequence injection or log-poisoning attacks on us. Together these checks close a whole class of input-handling vulnerabilities at the path-processing layer. We love a clean boundary π.
As of version 3.18.6, you can relax this restriction with trace/allow_unsafe_filename:1. And yes, this setting may be toggled from within the sandbox at runtime β but only prior to locking the sandbox π.
As of version 3.28.0, Syd levelled up its path integrity measures by incorporating an implementation based on David A. Wheeler's Safename Linux Security Module (LSM) patches. This update doesn't just prevent creating filenames with potentially harmful characters β it also hides existing files that have such names. Invalid filenames are now denied with an EILSEQ ("Illegal byte sequence") errno(3) when necessary. And in alignment with Wheeler's recommendations on restricting dangerous filenames, the validation now enforces stricter rules:
- Control Characters: filenames containing control characters (bytes
0x00β0x1Fand0x7F) are denied. - UTF-8 Encoding: filenames must be valid UTF-8 sequences.
- Forbidden Characters: the following characters are disallowed in filenames because they may interfere with shell operations or get misinterpreted by programs:
*,?,[,],",<,>,|,(,),&,',!,\,;,$, and`. - Leading Characters: filenames cannot start with a space (
), a dash (-), or a tilde (~). - Trailing Characters: filenames cannot end with a space (
).
As of version 3.37.9, the space checks got extended to cover UTF-8 whitespace, thanks to an idea by Jacob Bachmeyer β see https://seclists.org/oss-sec/2025/q3/123 for more info. π¦
As of version 3.38.0, the characters :, {, and } got removed from the forbidden set to improve usability and reduce false positives. Why? Because : is used all over /dev and /proc, and {} are used by firefox(1) for filenames under the profile directory. Real ones know π
.
As of version 3.48.0, the deny errno(3) changed from EINVAL ("Invalid argument") to EILSEQ ("Illegal byte sequence") so it matches ZFS behaviour. Consistency is the vibe β¨.
As of version 3.49.0, the values of uts/host and uts/domain must be safe filenames too, for added hardening.
Both new and existing filenames are validated against all these rules. For more information, refer to the following links:
https://dwheeler.com/essays/fixing-unix-linux-filenames.htmlhttps://lwn.net/Articles/686021/https://lwn.net/Articles/686789/https://lwn.net/Articles/686792/
π¦ Device Sidechannel Mitigations π¦
As of Syd version 3.21.0, Syd's device sidechannel mitigations align closely with GRKERNSEC_DEVICE_SIDECHANNEL in Grsecurity, aiming to prevent timing analyses on block or character devices via stat(2) or inotify(7)/fanotify(7).
- For stat-family system calls, Syd β just like Grsecurity β matches the last access and modification times to the creation time for devices, which thwarts unprivileged user timing attacks. π₯
- For fanotify(7)/inotify(7), instead of dropping events, Syd strips access and modify flags at syscall entry, which prevents unsafe
fanotify(7)/inotify(7)event generation.
This ensures unauthorized users can't figure out sensitive information β like the length of the administrator password π. Syd dynamically strips these flags to block sidechannel attacks without breaking functionality, which is the flex tbh.
As of version 3.40.0, these mitigations can be disabled using the options trace/allow_unsafe_stat_bdev, trace/allow_unsafe_stat_cdev, trace/allow_unsafe_notify_bdev, and trace/allow_unsafe_notify_cdev respectively. Refer to the following links for more information:
https://web.archive.org/web/20130111093624/http://vladz.devzero.fr/013_ptmx-timing.phphttps://en.wikibooks.org/wiki/Grsecurity/Appendix/Grsecurity_and_PaX_Configuration_Options#Eliminate_stat/notify-based_device_sidechannels
π Restricting CPU Emulation System Calls π
As of version 3.22.1, Syd denies the modify_ldt(2), subpage_prot(2), switch_endian(2), vm86(2), and vm86old(2) system calls by default β all of those are associated with CPU emulation functionalities. These calls can only be allowed if the trace/allow_unsafe_cpu option is explicitly set. This restriction helps mitigate potential vulnerabilities and unauthorized access that can arise from modifying CPU state or memory protections π‘οΈ.
π Kernel Keyring Access Restriction π
To enhance system security, access to the kernel's key management facility via the add_key(2), keyctl(2), and request_key(2) system calls is restricted by default as of version 3.22.1. These calls matter because they manage keys within the kernel β adding keys, manipulating keyrings, and requesting keys. The restriction aims to prevent unauthorized or potentially harmful modifications to keyrings, permitting only safe, controlled access β¨.
Administrators can relax this restriction by enabling the "trace/allow_unsafe_keyring" option, which allows these system calls to be executed when they're needed for legitimate purposes.
Because of this restriction, Syd is not affected by CVE-2024-42318 β even though we use Landlock π. See here for more information: https://www.openwall.com/lists/oss-security/2024/08/17/2
π§ Restricting Memory Protection Keys System Calls π§
As of version 3.22.1, Syd denies the pkey_alloc(2), pkey_free(2), and pkey_mprotect(2) system calls by default, so sandboxed processes can't go changing memory access permissions via memory protection keys. Use trace/allow_unsafe_pkey to allow these calls. Simple as that π
.
πΈ Restricting vmsplice System Call πΈ
As of version 3.23.5, Syd disables the vmsplice(2) system call by default to enhance security. This syscall is identified as a potential vector for memory corruption and privilege escalation, and it poses significant risks in sandboxed environments π. By default, disabling vmsplice(2) removes this risk. Fun fact: Podman applies the same restriction. Refer to the following links for more information:
As of version 3.41.3, the vmsplice(2) call may be permitted at startup using the trace/allow_unsafe_vmsplice:1 option.
π«Ά Enforcing Position-Independent Executables (PIE) π«Ά
As of version 3.23.9, Syd mandates that all executables must be Position-Independent Executables (PIE) to use Address Space Layout Randomization (ASLR). Why do we care? Because PIE lets executables be loaded at random memory addresses, which makes it way harder for attackers to predict where the executable code lives. That randomization thwarts a bunch of exploit types β think buffer overflow attacks β that rely on predictable memory addresses to execute malicious code. Iconic behaviour, honestly π₯.
To accommodate scenarios where PIE isn't feasible, users can relax this restriction using the trace/allow_unsafe_exec_nopie:1 option. That keeps compatibility while still enforcing a secure default. Best of both worlds π.
β Enforcing Non-Executable Stack β
As of version 3.23.16, Syd mandates that all executables must have a non-executable stack to enhance security. A non-executable stack helps prevent various types of exploits β like stack-based buffer overflow attacks β by making it more difficult for attackers to execute malicious code from the stack. This complements the PIE enforcement above, they're literally a duo π.
To accommodate scenarios where a non-executable stack isn't feasible, administrators can relax this restriction using the trace/allow_unsafe_exec_stack:1 option. Again: compatibility preserved, secure default enforced. Periodt. π
β¦and like, as of version 3.23.19 Syd enforces this restriction at the mmap(2) boundary too, so bestie, it is literally no longer possible to dlopen(3) a library with an executable stack just to change the process's stack permissions to executable. That's a whole door slammed shut π₯ and it's genuinely useful for mitigating attacks such as CVE-2023-38408. Full tea right here: https://www.qualys.com/2023/07/19/cve-2023-38408/rce-openssh-forwarded-ssh-agent.txt
And as of version 3.25.0, Syd doesn't merely deny the system call in that case β it kills the process π, because that keeps things consistent with the other memory-related seccomp filters. Which is honestly such a main-character move: the system administrator gets a notification via the audit log and has a way higher chance to react soon and investigate the potentially malicious activity. Plus, repeated failures are going to trigger SegvGuard. π
π Mitigation against Page Cache Attacks π
As of version 3.25.0, Syd denies the mincore(2) system call by default β it's typically not needed during a normal run, and it has been successfully (ab)used for page cache attacks: https://arxiv.org/pdf/1901.01161
To quote the Countermeasures section of the article (paraphrased, but every receipt kept):
Our side-channel attack targets the operating system page cache via operating system interfaces and behavior β so yeah, it clearly can be mitigated by modifying the operating system implementation. Privileged Access. TheQueryWorkingSetExandmincoresystem calls are the core of our side-channel attack, and requiring a higher privilege level for these system calls stops the attack. The downside of restricting access to these system calls is that existing programs which currently make use of these system calls might break. So we analyzed how frequentlymincoreis called by any of the software running on a typical Linux installation. We used the Linux perf tools to measure over a 5 hour period whenever thesys_enter_mincoresystem call is called by any application. During these 5 hours a user performed regular operations on the system β running various work-related tools like Libre Office, gcc, Clion, Thunderbird, Firefox, Nautilus, and Evince, but also non-work-related tools like Spotify. The system was also running regular background tasks during this time frame. Surprisingly, thesys_enter_mincoresystem call was not called a single time. This indicates that making themincoresystem call privileged is feasible and would mitigate our attack at a very low implementation cost.
As of version 3.35.2, the new system call cachestat(2) is also denied, for the same reason β it's basically a scalable version of the mincore(2) system call. And again, as of version 3.35.2, the option trace/allow_unsafe_page_cache has been added so you can relax this restriction at startup. You might need that to make direct rendering work with Firefox family browsers. π¦
π Enforcing AT_SECURE and UID/GID Verification π
As of version 3.27.0, Syd levels up security by enforcing the AT_SECURE flag in the auxiliary vector of executables at the ptrace(2) boundary, upon receiving the PTRACE_EVENT_EXEC event, to enforce secure-execution mode. That event happens after the executable binary is loaded into memory but before it starts executing. This enforcement ensures the C library operates in a secure mode, disabling unsafe behaviors like loading untrusted dynamic libraries or accessing insecure environment variables. On top of that, Syd performs strict UID and GID verification to confirm the process's user and group IDs match the expected values, preventing unauthorized privilege escalation. If the verification fails or the AT_SECURE flag cannot be set, Syd terminates the process to prevent potential security breaches. π
Ngl, this mitigation can be relaxed at startup with the option trace/allow_unsafe_exec_libc:1, but doing so is not recommended β it reduces the effectiveness of the sandbox.
Secure-execution mode is also enforced by apparmor(7), and it may also be enforced by other LSMs and eBPF. You may find some implications of the secure-execution mode below. Refer to the ld.so(8) and getauxval(3) manual pages for implications of secure-execution mode on your system.
glibc's dynamic linker strips/ignores dangerous LD_* variables in secure-execution mode, including LD_LIBRARY_PATH, LD_PRELOAD (only standard dirs; paths with slashes ignored), LD_AUDIT, LD_DEBUG, LD_DEBUG_OUTPUT, LD_DYNAMIC_WEAK, LD_HWCAP_MASK, LD_ORIGIN_PATH, LD_PROFILE, LD_SHOW_AUXV, LD_USE_LOAD_BIAS, etc. glibc also treats some non-LD_* variables as unsafe in secure-execution mode: GCONV_PATH, GETCONF_DIR, HOSTALIASES, LOCALDOMAIN, LOCPATH, MALLOC_TRACE, NIS_PATH, NLSPATH, RESOLV_HOST_CONF, RES_OPTIONS, TMPDIR, TZDIR (stripped/ignored). Refer to the ld.so(8) manual page for more information.
As of version 3.11.1, Syd also strips unsafe environment variables before executing the sandbox process by default, and this can be disabled altogether with trace/allow_unsafe_env:1, or unsafe environment variables can be selectively allowed using the -e var= format, e.g. -eLD_PRELOAD= β¨ Refer to the Restricting environment and trace/allow_unsafe_env section for more information.
glibc's LD_PREFER_MAP_32BIT_EXEC is always disabled in secure-execution mode (mitigates ASLR-weakening). Historical bugs (e.g., CVE-2019-19126) fixed cases where this wasn't ignored after a security transition. Refer to the ld.so(8) manual page and the following links for more information:
- https://lists.gnu.org/archive/html/info-gnu/2020-02/msg00001.html
- https://alas.aws.amazon.com/ALAS-2021-1511.html
glibc GLIBC_TUNABLES environment variable handling under AT_SECURE: tunables carry security levels (SXID_ERASE, SXID_IGNORE) so they're ignored/erased for secure-execution mode; post-CVE-2023-4911 hardening ensures secure-execution mode invocations with hostile GLIBC_TUNABLES are blocked/terminated. Refer to the following links for more information:
- https://lwn.net/Articles/947736/
- https://access.redhat.com/security/cve/cve-2023-4911
- https://nvd.nist.gov/vuln/detail/CVE-2023-4911
glibc secure_getenv(3) returns NULL when AT_SECURE is set; any glibc subsystem that uses secure_getenv(3) (e.g., timezone, locale, iconv, resolver paths) will ignore environment overrides in secure-execution mode. Similarly, calling getauxval(3) with the flag AT_SECURE returns true in secure-execution mode.
musl libc honors AT_SECURE and likewise ignores preload/library/locale environment knobs in secure-execution mode; examples include LD_PRELOAD, LD_LIBRARY_PATH, and MUSL_LOCPATH. Refer to the following links for more information:
Because the Linux host kernel is not aware of Syd setting the AT_SECURE bit, the proc_pid_auxv(5) file will report the bit as unset. On the contrary, when verbose logging is turned on using the log/verbose:1 option, Syd will correctly log this bit as set after parsing the proc_pid_auxv(5) file of the sandbox process. π§
π Process Name Modification Restriction π
As of version 3.28.0, Syd introduces a critical security enhancement that logs and denies attempts to set a process's name using the PR_SET_NAME prctl(2) request. This mitigation is necessary because it prevents malicious software from disguising itself under legitimate process names such as apache or other system daemons, thereby thwarting attempts to evade detection and maintain stealth within the system. By default, any invocation of PR_SET_NAME within the sandboxed environment is intercepted; the action is logged for audit purposes if verbose logging is on, and the system call is denied with success return β essentially turning it into a no-op. π
If there is a legitimate need to permit process name changes within the sandbox, this restriction can be overridden by enabling the trace/allow_unsafe_prctl:1 option, which allows PR_SET_NAME requests to succeed without logging.
β‘ Speculative Execution Mitigation β‘
As of version 3.30.0, Syd integrates a mitigation mechanism using the prctl(2) system call to enforce speculative execution controls, to mitigate advanced speculative execution vulnerabilities such as Spectre and related side-channel attacks. Upon initialization, Syd attempts to apply the PR_SPEC_FORCE_DISABLE setting for critical speculative execution features β namely PR_SPEC_STORE_BYPASS, PR_SPEC_INDIRECT_BRANCH, and PR_SPEC_L1D_FLUSH β thereby irrevocably disabling these CPU-level misfeatures when permissible. This ensures that, where supported by the underlying kernel and hardware, speculative execution is constrained to eliminate potential avenues for data leakage and privilege escalation across privilege domains.
The mitigation is conditionally enforced based on the availability of per-task control via prctl(2), and any inability to apply these settings due to architectural constraints or insufficient permissions results in logged informational messages without disrupting sandbox operations. Furthermore, administrators retain the capability to override this security policy through the trace/allow_unsafe_exec_speculative:1 configuration option, permitting flexibility in environments where speculative execution controls may need to be relaxed for compatibility or performance reasons. Refer to the links below for more information:
- https://docs.kernel.org/admin-guide/hw-vuln/spectre.html
- https://docs.kernel.org/userspace-api/spec_ctrl.html
π² Cryptographically Randomized Sysinfo π²
Since Syd 3.28.0, the sysinfo(2) system call has been cryptographically obfuscated by applying high-entropy offsets to memory fields (e.g., total RAM, free RAM) and constraining them to plausible power-of-two boundaries, frustrating trivial attempts at system fingerprinting. π΅οΈββοΈ Specifically, uptime and idle counters each incorporate a distinct offset up to 0xFF_FFFF (~194 days) unless unshare/time:1, when time starts from zero, while load averages are randomized in fixed-point format and clamped to realistic upper limits. Administrators seeking genuine system metrics may disable these transformations via trace/allow_unsafe_sysinfo:1, albeit at the cost of enabling straightforward correlation and potential data leakage.
As of Syd 3.58.1, when Memory Sandboxing is enabled with a non-zero mem/max limit, memory fields are no longer randomized. totalram reports the configured limit, and freeram reports the remaining size based on current memory usage of the calling process. bufferram and sharedram are reported as zero in this case. β
π Memory Sealing of Sandbox Policy Regions on Lock
Okay so starting with version 3.33.1, Syd hits your policy-critical memory regions with Linux's mseal(2) syscall β and it does this at the exact moment the sandbox gets locked with lock:on. The way that works is: every mutable structure that has any say in access control (think ACLs, action filters, syscall mediation rules) gets sealed at the virtual memory level. π«Ά
And bestie, this is not like your classic permission schemes such as W^X or mprotect(2) β mseal(2) protects against structural manipulation of the memory mappings themselves. It stops mmap(2), mremap(2), mprotect(2), munmap(2), and destructive madvise(2) operations from altering sealed VMAs. Periodt. π
This absolutely eliminates the attacker primitives that rely on reclaiming, remapping, or changing permissions on enforcement data, which closes off those advanced data-oriented exploitation paths β you know the ones, policy subversion through remapped ACLs, or revoking constraints by resetting memory permissions. Not on her watch. π
She's not a hater though: Syd still permits legit late-stage policy configuration during startup and defers the sealing until lock:on is called. After that? Mutation of enforcement state is structurally frozen, fr fr. The process is one-way and idempotent β sealed memory cannot be unsealed β giving you strong guarantees once lockdown is complete. π
For diagnostic or non-hardened environments, you can explicitly disable this with the startup toggle trace/allow_unsafe_nomseal:1. Only do this if you're fully aware of the resulting relaxation in protection, okay? Like, fully. π
When it's enabled, sealing substantially raises the integrity threshold of the sandbox: post-lock policy enforcement is immune to both direct and indirect memory-level tampering. Iconic.
π Force Close-on-Exec File Descriptors
The trace/force_cloexec option (introduced in Syd version 3.35.2) makes sure that all creat(2), open(2), openat(2), openat2(2), memfd_create(2), socket(2), accept(2), and accept4(2) system calls made by the sandbox process include the O_CLOEXEC flag. β¨
You can toggle this at runtime through Syd's virtual stat API, so you can dynamically adjust your confinement levels whenever you need. The O_CLOEXEC flag, when it's set on file descriptors, guarantees they get automatically closed when a new program is executed via execve(2) or similar system calls. That auto-closure is so critical for security and safety β it stops file descriptors from being unintentionally inherited by newly executed programs, which could otherwise lead to unauthorized access to sensitive files or resources. π
By enforcing O_CLOEXEC across every open(2) call, Syd mitigates the risk of file descriptor leakage, effectively isolating the sandboxed environment and giving newly spawned processes a clean execution context. It's giving clean slate energy. πΈ
π² Force Randomized File Descriptors
The trace/force_rand_fd option (also introduced in Syd version 3.35.2) makes sure that all creat(2), open(2), openat(2), openat2(2), memfd_create(2), socket(2), accept(2), and accept4(2) system calls made by the sandbox process allocate file descriptors at random available slots instead of the lowest-numbered one. π¦
When this feature is on, Syd specifies a random available slot (rather than the lowest-numbered one) to the SECCOMP_IOCTL_NOTIF_ADDFD operation, which is the thing used to install a file descriptor into the sandbox process. Randomizing file descriptor numbers makes it significantly harder for an attacker to predict or deliberately reuse critical descriptors, and it raises the bar against file-descriptor reuse and collision attacks. π₯
Heads up, bestie β enabling this may break programs that rely on the POSIX guarantee that open(2) returns the lowest available descriptor. So like, vibe check your workload first. π¬
This behavior can be toggled at runtime via Syd's virtual stat API, so operators can enable or disable descriptor randomization without restarting or recompiling the sandboxed process. We're also cooperating with the HardenedBSD project to implement a similar feature in the BSD kernel β refer to the following link for more information: https://git.hardenedbsd.org/hardenedbsd/HardenedBSD/-/issues/117 π
πͺ Syscall Argument Cookies
To further harden the seccomp(2) boundary, as of version 3.35.2 Syd embeds cryptographically-strong, per-instance "cookies" into unused architecture-defined syscall argument slots β like, for example, the 5th and 6th arguments of openat2(2). π
These cookies are generated at startup via the OS random number generator using getrandom(2), and they get checked in the BPF filter so that only calls bearing the correct 32- or 64-bit values will be allowed. By requiring this unpredictable token, Syd raises the bar against arbitrary or forged syscalls: attackers have to first discover or leak the randomized cookies despite Address Space Layout Randomization (ASLR) before they can mount a successful path or network operation. Good luck with that, babe. π
This approach effectively transforms unused syscall parameters into an application-level authorization mechanism β preventing trivial reuse of legitimate code paths and mitigating time-of-check-to-time-of-use (TOCTTOU) and ROP payloads that rely on guessing or omitting optional arguments. In combination with absolute path enforcement and the denial of relative descriptors (e.g. AT_FDCWD), syscall argument cookies form a lightweight, zero-cost integrity check that elevates syscall hardening without kernel modifications or performance penalties. Obsessed, honestly. β¨
As an example, here's how the filters look in pseudo filter code for the system calls openat2(2) and socket(2) on x86-64. openat2(2) uses two unused arguments as cookies and socket(2) uses three. In addition, openat2(2) denies negative file descriptor arguments such as AT_FDCWD:
# filter for syscall "openat2" (437) [priority: 65528]
if ($syscall == 437)
if ($a0.hi32 > 0)
else
if ($a0.hi32 == 0)
if ($a0.lo32 > 2147483647)
else
if ($a4.hi32 == 2047080271)
if ($a4.lo32 == 419766579)
if ($a5.hi32 == 2863373132)
if ($a5.lo32 == 396738706)
action ALLOW;
else
if ($a4.hi32 == 2047080271)
if ($a4.lo32 == 419766579)
if ($a5.hi32 == 2863373132)
if ($a5.lo32 == 396738706)
action ALLOW;
# filter for syscall "socket" (41) [priority: 65529]
if ($syscall == 41)
if ($a3.hi32 == 3378530982)
if ($a3.lo32 == 4160747949)
if ($a4.hi32 == 2899982880)
if ($a4.lo32 == 990920938)
if ($a5.hi32 == 3611760485)
if ($a5.lo32 == 1163305215)
action ALLOW;
Another example is how the critical seccomp(2) notify ioctl(2) requests SECCOMP_IOCTL_NOTIF_SEND and SECCOMP_IOCTL_NOTIF_ADDFD are confined for the Syd emulator threads. π§
SECCOMP_IOCTL_NOTIF_SEND is critical because it allows pass-through of system calls to the host Linux kernel with the SECCOMP_USER_NOTIF_FLAG_CONTINUE flag in the seccomp(2) response data structure β and that flag must be used with the utmost care, because in the hands of an attacker it can be a tool for further exploitation. π
SECCOMP_IOCTL_NOTIF_ADDFD is critical because it allows file descriptor transfer between the Syd process and the sandbox process, and in the hands of an attacker it can be a tool for file descriptor stealing. π
As part of this mitigation, three syscall cookies are enforced for ioctl(2) system calls with the SECCOMP_IOCTL_NOTIF_SEND and SECCOMP_IOCTL_NOTIF_ADDFD requests. Coupled with the startup randomization of the seccomp(2) notify file descriptor, this mitigation raises the bar for an attacker trying to call arbitrary or forged syscalls within a compromised Syd emulator thread. Excerpt from the seccomp filter in pseudo filter code is given below:
# Syd monitor rules with seccomp fd 626
#
# pseudo filter code start
#
# filter for arch x86_64 (3221225534)
...
# filter for syscall "ioctl" (16) [priority: 65497]
if ($syscall == 16)
if ($a0.hi32 == 0)
if ($a0.lo32 == 626)
if ($a1.hi32 == 4294967295)
if ($a1.lo32 == SECCOMP_IOCTL_NOTIF_RECV)
action ALLOW;
if ($a1.lo32 == SECCOMP_IOCTL_NOTIF_SEND)
if ($a3.hi32 == 4195042482)
if ($a3.lo32 == 329284685)
if ($a4.hi32 == 3163914537)
if ($a4.lo32 == 2000745976)
if ($a5.hi32 == 3932715328)
if ($a5.lo32 == 2409429749)
action ALLOW;
if ($a1.lo32 == SECCOMP_IOCTL_NOTIF_ADDFD)
if ($a3.hi32 == 2387882717)
if ($a3.lo32 == 529632567)
if ($a4.hi32 == 2017338540)
if ($a4.lo32 == 3732042218)
if ($a5.hi32 == 4202049614)
if ($a5.lo32 == 546113052)
action ALLOW;
if ($a1.lo32 == SECCOMP_IOCTL_NOTIF_SET_FLAGS)
action ALLOW;
if ($a1.lo32 == SECCOMP_IOCTL_NOTIF_ID_VALID)
action ALLOW;
if ($a1.hi32 == 0)
if ($a1.lo32 == SECCOMP_IOCTL_NOTIF_RECV)
action ALLOW;
if ($a1.lo32 == SECCOMP_IOCTL_NOTIF_SEND)
if ($a3.hi32 == 4195042482)
if ($a3.lo32 == 329284685)
if ($a4.hi32 == 3163914537)
if ($a4.lo32 == 2000745976)
if ($a5.hi32 == 3932715328)
if ($a5.lo32 == 2409429749)
action ALLOW;
if ($a1.lo32 == SECCOMP_IOCTL_NOTIF_ADDFD)
if ($a3.hi32 == 2387882717)
if ($a3.lo32 == 529632567)
if ($a4.hi32 == 2017338540)
if ($a4.lo32 == 3732042218)
if ($a5.hi32 == 4202049614)
if ($a5.lo32 == 546113052)
action ALLOW;
if ($a1.lo32 == SECCOMP_IOCTL_NOTIF_SET_FLAGS)
action ALLOW;
if ($a1.lo32 == SECCOMP_IOCTL_NOTIF_ID_VALID)
action ALLOW;
...
# default action
action KILL_PROCESS;
# invalid architecture action
action KILL_PROCESS;
Okay bestie so that's the shape of the filter π β and here's the list of system calls protected by cookies, js so you know what's being guarded. And no, it's not final-final, the list may be further extended in the future to cover more system calls used by Syd:
execveat(2)withAT_EXECVE_CHECKexit_group(2)kill(2),pidfd_open(2),pidfd_getfd(2),pidfd_send_signal(2),ptrace(2)ioctl(2)PROCMAP_QUERYSECCOMP_IOCTL_NOTIF_RECVSECCOMP_IOCTL_NOTIF_SENDSECCOMP_IOCTL_NOTIF_ADDFD
seccomp(2)faccessat2(2),fchdir(2),fchmod(2),fchmodat(2),fchmodat2(2),fchown(2),fchownat(2)getdents64(2),readlinkat(2),statx(2),inotify_add_watch(2),sysinfo(2)mkdirat(2),mknodat(2)linkat(2),renameat2(2),symlinkat(2),unlinkat(2)memfd_create(2),memfd_secret(2)openat2(2)read(2),write(2)close(2),close_range(2),dup3(2)pipe2(2)kcmp(2),waitid(2)socket(2),bind(2),connect(2),accept4(2)(64-bit only)recvmsg(2),sendmsg(2)(64-bit only)getpeername(2),getsockname(2),getsockopt(2)(64-bit only)fallocate(2)(64-bit only)truncate(2),truncate64(2),ftruncate(2)uname(2),umask(2),utimensat(2),utimensat_time64(2)fsetxattr(2),lsetxattr(2)fgetxattr(2),flistxattr(2)fremovexattr(2),lremovexattr(2)epoll_create1(2),eventfd2(2)
As of version 3.36.0, this mitigation may be disabled at startup using the trace/allow_unsafe_nocookie:1 option. (Don't tho. Like, why would you. π)
As of version 3.48.2, all cookies reside in a single contiguous memory region hardened with guard pages, read-only protection, and mseal(2) where available. This consolidation reduces entropy consumption to one getrandom(2) call and eliminates per-cookie allocation overhead while preserving the cryptographic unpredictability of each token. Iconic efficiency behaviour. β¨
As of version 3.54.0, the cookie pool employs a randomized indirection table to decouple logical cookie identifiers from their physical memory offsets. At startup, a Fisher-Yates permutation Ο of size N (where N is the number of cookie slots) is generated using getrandom(2) with rejection sampling to eliminate modular reduction bias. The permutation is constructed in place in the data region, then immediately sealed. The permutation resides in a separate guard-page protected memory region that is madvise(2) MADV_DONTDUMP, read-only protected with mprotect(2), and sealed with mseal(2). Cookie lookups index through Ο, so that leaking the pool's base address no longer reveals which physical offset corresponds to which system call without independently compromising the indirection table. the way that is genuinely so much math for a sandbox and I respect it so much π
π Shared Memory Hardening π
As of version 3.48.0, Syd denies access to sysvipc(7) and mq_overview(7) system calls by default to enforce a strict shared-nothing architecture. This hardening eliminates an entire class of inter-process communication (IPC) vulnerabilities, including "memory squatting" attacks where malicious actors preemptively allocate shared memory keys to hijack or disrupt legitimate applications, as detailed in the research by Portcullis. By blocking the creation and usage of System V shared memory, semaphores, message queues, and POSIX message queues, Syd closes kernel code paths that have historically harbored privilege escalation and information leakage bugs. This strict isolation aligns with modern container security best practices, preventing sandboxed processes from interfering with the host or other containers via shared global namespaces. If legacy application compatibility is required, these subsystems can be selectively re-enabled using the trace/allow_unsafe_shm:1 and trace/allow_unsafe_msgqueue:1 options, partially exposing the sandbox to the aforementioned risks. and like, we mean literally exposing it, not in a cute way π Refer to the following links for more information:
https://man7.org/linux/man-pages/man7/sysvipc.7.htmlhttps://man7.org/linux/man-pages/man7/mq_overview.7.htmlhttps://labs.portcullis.co.uk/whitepapers/memory-squatting-attacks-on-system-v-shared-memory/https://labs.portcullis.co.uk/presentations/i-miss-lsd/https://www.cve.org/CVERecord?id=CVE-2013-0254
π¦ Shared Memory Permissions Hardening π¦
As of version 3.37.0, Syd introduces a kernel-enforced mitigation against System V shared memory squatting by conditioning allow rules on strict permission masks. By inspecting the mode bits passed to shmget(2), msgget(2), semget(2) and mq_open(2) system calls, the sandbox admits creates only when user-, group-, and other-permission fields exclude unsafe write or execute flags (i.e., no bits set in mask 0o177). This measure prevents untrusted processes from elevating permissions after creation or exploiting legacy IPC segments with permissive ACLs, which could lead to disclosure or corruption of shared pages. Based on the attack taxonomy described in Memory Squatting: Attacks on System V Shared Memory (Portcullis, 2013), mode checks take place within the seccomp(2) BPF filter before any mapping. The IPC_SET operations of the shmctl(2), msgctl(2), and semctl(2) system calls are also denied, preventing permission changes after creation. Additionally, any attempt to attach a shared memory segment with the SHM_EXEC flag via shmat(2) is denied to enforce W^X policies, blocking executable mappings through shared memory. The seccomp(2) filter also blocks the MSG_STAT_ANY, SEM_STAT_ANY, and SHM_STAT_ANY operations (Linux 4.17+), which would otherwise return segment metadata without verifying its mode, mitigating unintended information leaks. ptw nobody gets to snoop the metadata, that's the whole point π«Ά This mitigation is applied in the parent seccomp(2) filter, binding the Syd process itself to these restrictions. Administrators may relax this policy at startup using the trace/allow_unsafe_perm_msgqueue:1 and trace/allow_unsafe_perm_shm:1 options, but doing so reintroduces the classic squatting vulnerabilities documented in CVE-2013-0254 and related research. so like, do not do that unless you absolutely have to, fr fr π For more information refer to the following links:
https://labs.portcullis.co.uk/whitepapers/memory-squatting-attacks-on-system-v-shared-memory/https://labs.portcullis.co.uk/presentations/i-miss-lsd/https://www.cve.org/CVERecord?id=CVE-2013-0254
πΈ Mitigation Against Heap Spraying πΈ
As of version 3.23.18, Syd disables the msgsnd(2) system call by default to mitigate kernel heap-spraying attacks. System V message queues allocate large, contiguous kernel-heap buffers under caller control, making msgsnd(2) a practical primitive for heap layout manipulation. CVE-2016-6187, CVE-2021-22555, and CVE-2021-26708 each exploited this property to achieve predictable heap placement and subsequent privilege escalation or kernel code execution. Blocking msgsnd(2) at the seccomp layer removes this allocation primitive from the attacker's reach. no spray, no slay (for them, anyway) β¨ Administrators who need System V message queues for legitimate IPC can re-enable the call with trace/allow_unsafe_shm:1. Refer to the following links for more information:
https://en.wikipedia.org/wiki/Heap_sprayinghttps://grsecurity.net/how_autoslab_changes_the_memory_unsafety_gamehttps://duasynt.com/blog/cve-2016-6187-heap-off-by-one-exploithttps://google.github.io/security-research/pocs/linux/cve-2021-22555/writeup.htmlhttps://a13xp0p0v.github.io/2021/02/09/CVE-2021-26708.html
π Denying Restartable Sequences
okay so as of version 3.37.0, Syd just says no to restartable sequences β the rseq(2) system call β by default, and bestie, that single choice substantially elevates the security baseline of the whole sandbox. π
here's the tea: the restartable sequences interface lets user space register per-thread critical regions with kernel-enforced atomicity guarantees, which sounds iconicβ¦ but it also exposes a user-controlled abort handler address. π and in adversarial scenarios that's the whole problem β attackers who can manipulate process memory or the rseq(2) registration can redirect execution to arbitrary, attacker-chosen code locations on preemption or CPU migration. that bypasses intra-process isolation boundaries and subverts stuff like memory protection keys or control-flow integrity. literally not the vibe.
by prohibiting rseq(2), Syd eliminates this kernel-facilitated control-flow transfer primitive entirely, foreclosing a whole class of attacks that exploit restartable sequence state for privilege escalation, sandbox escape, or bypass of compartmentalization. this mitigation is giving least-privilege syscall surface and strong adherence to modern threat models β allowing only strictly necessary system calls and neutralizing emergent attack vectors rooted in kernel-user collaboration. π
administrators may explicitly re-enable this system call if it's required for compatibility, using the trace/allow_unsafe_rseq:1 startup option β with the understanding that doing so weakens this critical security boundary. for more info, refer to these links:
- https://arxiv.org/abs/2108.03705
- https://arxiv.org/abs/2406.07429
- https://www.usenix.org/system/files/usenixsecurity24-yang-fangfei.pdf
π Personality Syscall Restrictions
as of version 3.37.0, Syd implements strict restrictions on the personality(2) system call to mitigate security vulnerabilities associated with unsafe personality(2) flags β and the two main characters here are ADDR_NO_RANDOMIZE, which can disable Address Space Layout Randomization (ASLR) π«£ (a fundamental memory protection mechanism that prevents reliable exploitation of memory corruption vulnerabilities by randomizing memory layout), and READ_IMPLIES_EXEC, which can bypass memory protections provided by Memory-Deny-Write-Execute, aka W^X.
this security enhancement aligns Syd with industry-standard container runtimes including Docker and Podman, which employ identical restrictions to balance security with application compatibility by maintaining an allowlist of safe personality values:
PER_LINUXfor standard Linux execution domainPER_LINUX32for 32-bit compatibilityUNAME26for legacy kernel version reportingPER_LINUX32|UNAME26for combined 32-bit and legacy compatibilityGET_PERSONALITYfor querying currentpersonality(2)without modification
the implementation follows the principle of least privilege by denying all potentially dangerous personality(2) modifications while permitting only required compatibility requirements β preventing malicious actors from using personality(2) flags to make exploits more predictable and reliable, a behavior specifically monitored by security detection systems. π
administrators who require unrestricted personality system call access can disable these restrictions using trace/allow_unsafe_personality:1, though this should be undertaken with careful consideration of the security implications, since it potentially exposes the sandbox to personality-based security bypasses that could compromise the isolation guarantees provided by Syd's broader security hardening strategy β the one encompassing strict system call filtering, capability restrictions, and resource access controls. highkey do not just flip that on for funsies. π€
as of version 3.47.0, Syd extends these protections by adding ADDR_COMPAT_LAYOUT β which forces a legacy, more predictable memory layout β and MMAP_PAGE_ZERO β which allows mapping page 0 and can turn NULL-pointer dereferences into code execution π β to the personality(2) "kill list", so that any attempt within the sandbox to enable READ_IMPLIES_EXEC, ADDR_NO_RANDOMIZE, ADDR_COMPAT_LAYOUT, or MMAP_PAGE_ZERO results in immediate termination of the offending process. periodt.
during sandbox setup, Syd also clears all four of these flags from the inherited personality(2) so that untrusted workloads always start with ASLR-friendly layouts and without the ability to rely on legacy low-entropy address layouts or exploit NULL-pointer mappings. β¨
π§΅ Thread-Level Filesystem and File-Descriptor Namespace Isolation
as of version 3.37.2, Syd's interrupt, IPC and emulator worker threads are each placed into their own filesystem and file-descriptor namespace by unshare(2)'ing both CLONE_FS and CLONE_FILES. π¦
this per-thread isolation ensures that working directory, umask(2) and open-file table changes in one thread cannot leak into β or be influenced by β any other, closing subtle attack vectors such as TOCTOU races on shared procfs(5) or fd entries, descriptor reuse across threads, and cwd-based side channels. by scoping thread-local filesystem state and descriptor tables, this enhancement hardens Syd's sandbox manager against advanced multithreading exploits and preserves strict separation between the monitoring and emulation components. love that for us. π«Ά
π¨ Denying MSG_OOB Flag in send/recv System Calls
as of version 3.37.5, Syd unconditionally denies the use of the MSG_OOB flag in all send(2), sendto(2), sendmsg(2), and sendmmsg(2) calls β regardless of socket family β by returning the EOPNOTSUPP ("Operation not supported on transport endpoint") errno(3). and as of version 3.41.1, the restriction also includes the system calls recv(2), recvfrom(2), recvmsg(2), and recvmmsg(2). no exceptions, no loopholes, no OOB. π«
this measure addresses long-standing security concerns with out-of-band messaging semantics in stream sockets, where urgent data bypasses normal in-order delivery rules and is handled via separate kernel paths. such semantics are rarely required by modern software but introduce complexity and subtle state transitions inside the kernel's networking stack, which have historically led to memory safety bugs and race conditions exploitable from unprivileged code. by default, removing MSG_OOB support removes reachable kernel OOB code paths for sandboxed processes without impacting typical application behavior. iconic behavior tbh.
for controlled environments where MSG_OOB is explicitly required, Syd provides the opt-in trace/allow_unsafe_oob:1 flag to restore legacy behavior β though enabling it reintroduces the inherent risks associated with out-of-band data handling. π
this mitigation is enabled by default on all architectures without the socketcall(2) multiplexer, which are: aarch64, arm, loongarch64, mips64, mipsel64, parisc, parisc64, riscv64, x32, and x86_64. on architectures with the socketcall(2) multiplexer β x86, m68k, mips, mipsel, ppc, ppc64, ppc64le, s390, s390x, sheb, and sh β this mitigation historically did not apply. however, as of version 3.57.0 socketcall(2) is denied by default, forcing libcs onto the direct system calls, so this mitigation is effective on those architectures too β unless trace/allow_unsafe_socketcall:1 is set at startup. slay. β¨
for more information refer to the following links:
- https://googleprojectzero.blogspot.com/2025/08/from-chrome-renderer-code-exec-to-kernel.html
- https://chromium-review.googlesource.com/c/chromium/src/+/6711812
- https://u1f383.github.io/linux/2025/10/03/analyze-linux-kernel-1-day-0aeb54ac.html
π¬ Denying O_NOTIFICATION_PIPE Flag in pipe2
notification pipes require trace/allow_unsafe_pipe:1. without this option, pipe2(2) with O_NOTIFICATION_PIPE fails with ENOPKG β exactly like on a Linux kernel built without CONFIG_WATCH_QUEUE. π
when the option is set, Syd checks each O_NOTIFICATION_PIPE pipe against the dummy path !notification_pipe under the create category. a rule such as deny/create+!notification_pipe then fails with ENOPKG. iykyk.
this restriction addresses the security risks associated with notification pipes β a specialized and seldom-used mechanism designed for delivering kernel event notifications (currently only from the keys subsystem) to userspace when the kernel is built with CONFIG_WATCH_QUEUE. unlike normal pipes, notification pipes operate with distinct semantics and are tightly integrated with kernel internals, creating a more complex and less widely audited code path. π¬
historically, vulnerabilities in notification pipe handling have demonstrated that exposing this functionality to unprivileged, sandboxed code can create exploitable kernel entry points. because typical sandboxed applications β including high-risk workloads such as browser renderers β have no legitimate need for notification pipes, denying !notification_pipe eliminates an entire class of low-value yet high-risk kernel interfaces. it's giving spring cleaning for syscalls. π§Ή
refer to the following links for more information:
- https://chromium-review.googlesource.com/c/chromium/src/+/4128252
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?qt=grep&q=watch_queue
π§ madvise(2) Hardening
as of version 3.41.3, Syd tightens its seccomp(2) BPF policy by argument-filtering madvise(2) down to an allow-list that's safe for untrusted workloads and has well-understood locality:
MADV_SEQUENTIALMADV_DONTNEEDMADV_REMOVEMADV_HUGEPAGEMADV_NOHUGEPAGEMADV_DONTDUMPMADV_COLLAPSEMADV_POPULATE_READMADV_POPULATE_WRITE- and (since Linux 6.13) the lightweight guard operations
MADV_GUARD_INSTALL/MADV_GUARD_REMOVEβ page-table-level red zones that fault on access without VMA churn π‘οΈ
the advice MADV_HWPOISON is denied, and all other advice are treated as no-op because they enable cross-domain information leaks or system-wide pressure channels with no isolation benefit. like, for example:
MADV_MERGEABLEdrives KSM deduplication, which has been repeatedly shown to enable cross-VM/process side channels and targeted bit-flip exploitation (Flip Feng Shui) as well as newer remote and timing channels πMADV_WILLNEED/MADV_RANDOMmanipulate page-cache residency and prefetch behavior that underpin page-cache side-channel attacks- reclaim steering like
MADV_FREE/MADV_COLD/MADV_PAGEOUTintroduces externally observable memory-pressure/timing signals and accounting ambiguity that sandboxes should not expose - privileged page state changes
MADV_SOFT_OFFLINE/MADV_HWPOISONare unnecessary in least-authority contexts and remain outside the sandbox contract even if capability checks would reject them
this design follows the strict syscall-and-argument allow-listing discipline also employed by Google's Sandbox2/Sandboxed-API while remaining specific to Syd's threat model. π
to temporarily relax this mitigation for tracing/compatibility, set trace/allow_unsafe_madvise:1 at startup β otherwise unsafe advice remain blocked by default. refer to the following links for more information:
- https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_razavi.pdf
- https://www.ndss-symposium.org/wp-content/uploads/2022-81-paper.pdf
- https://svs.informatik.uni-hamburg.de/publications/2024/Lindemann_ACSAC2024_FakeDD.pdf
- https://arxiv.org/pdf/1901.01161
- https://lwn.net/Articles/790123/
- https://lwn.net/Articles/1011366/
- https://developers.google.com/code-sandboxing/sandbox2/explained
- https://developers.google.com/code-sandboxing/sandboxed-api/explained
𧦠setsockopt(2) Hardening
okay so as of version 3.46.1, Syd drops a whole fine-grained setsockopt(2) hardening layer on you. it denies a curated little hitlist of historically fragile / highly privileged socket(2) options β and the way it does it is by matching on the (level, optname) pair inside a dedicated seccomp(2) filter. so extra. so effective. π
what's on the list, you ask? bestie, it's a lot:
- netfilter rule programming (
iptables,ip6tables,arptables,ebtables) - multicast routing control
- IPv4/IPv6 multicast group management
- IPv6 header manipulation
- TCP repair and upper-layer protocol hooks
- congestion control selection
- UDP corking
AF_PACKETring/fanout configuration- BPF-based socket filters
- VSOCK buffer sizing
Syd turns all these dangerous combos into success-returning no-ops β like it emulates a successful setsockopt(2) while silently yeeting the request into the void π³οΈ. literally just discarding it. and that's actually the slay part, because it keeps compatibility with apps that merely probe for these features but never actually rely on their semantics, while simultaneously removing a whole pile of kernel code that was reachable from unprivileged processes. no cap.
This mitigation is enabled by default on all architectures without the socketcall(2) multiplexer, which are: aarch64, arm, loongarch64, mips64, mipsel64, parisc, parisc64, riscv64, x32, and x86_64. β¨
On architectures with the socketcall(2) multiplexer β x86, m68k, mips, mipsel, ppc, ppc64, ppc64le, s390, s390x, sheb, and sh β this mitigation historically did not apply π. BUT. as of version 3.57.0, socketcall(2) is denied by default, which forces libcs onto the direct system calls, so this mitigation is effective on those architectures too β unless trace/allow_unsafe_socketcall:1 is set at startup. iykyk.
The mitigation may be relaxed at startup using the option trace/allow_unsafe_setsockopt:1.
And as of version 3.57.0, Syd denies each unsupported option level with one seccomp(2) rule matched on the level alone, instead of one rule per option name. glow up fr fr:
SOL_ALGis allowed withtrace/allow_unsafe_kcapi:1- the raw families
CAN_RAW,AF_PACKETandAF_XDPare allowed withtrace/allow_unsafe_socket:1 - and the rest β things like SCTP, DCCP, TLS, TIPC, Bluetooth and VSOCK β go through
trace/allow_unsupp_socket:1
The supported levels are still denied per option though. no shortcuts bestie.
Refer to the following links for more information:
- https://nvd.nist.gov/vuln/detail/CVE-2016-9793
- https://www.cve.org/CVERecord?id=CVE-2016-9793
- https://security-tracker.debian.org/tracker/CVE-2016-9793
- https://ubuntu.com/security/CVE-2016-9793
- https://www.exploit-db.com/exploits/41995
- https://nvd.nist.gov/vuln/detail/CVE-2017-6346
- https://www.cve.org/CVERecord?id=CVE-2017-6346
- https://security-tracker.debian.org/tracker/CVE-2017-6346
- https://ubuntu.com/security/CVE-2017-6346
- https://www.cvedetails.com/cve/CVE-2017-6346/
- https://nvd.nist.gov/vuln/detail/CVE-2018-18559
- https://www.cve.org/CVERecord?id=CVE-2018-18559
- https://security-tracker.debian.org/tracker/CVE-2018-18559
- https://ubuntu.com/security/CVE-2018-18559
- https://www.cvedetails.com/cve/CVE-2018-18559/
- https://nvd.nist.gov/vuln/detail/CVE-2020-14386
- https://www.openwall.com/lists/oss-security/2020/09/03/3
- https://unit42.paloaltonetworks.com/cve-2020-14386/
- https://sysdig.com/blog/cve-2020-14386-falco
- https://gvisor.dev/blog/2020/09/18/containing-a-real-vulnerability/
- https://www.cve.org/CVERecord?id=CVE-2007-1353
- https://nvd.nist.gov/vuln/detail/CVE-2007-1353
- https://security-tracker.debian.org/tracker/CVE-2007-1353
- https://ubuntu.com/security/CVE-2007-1353
- https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2007-1353
- https://ssd-disclosure.com/ssd-advisory-linux-kernel-af_packet-use-after-free-2/
- https://github.com/V4bel/dirtyfrag
π§ Hardening against kernel pointer misuse
As of version 3.48.0, Syd hardens against kernel pointer misuse by default. and honestly? iconic. π
This mitigation deploys a seccomp(2) BPF filter to inspect system call arguments that are known to accept pointers. If a user-supplied argument is detected to point into kernel memory, the seccomp(2) filter returns EFAULT ("Bad address") without passing it on to the host kernel. so the kernel never even sees it. blocked. deleted. π
This defense-in-depth measure effectively neutralizes a whole class of critical vulnerabilities where the kernel fails to validate that a user-supplied pointer actually resides in user-space memory β like when the access_ok() checks are justβ¦ missing π β which typically leads to arbitrary kernel memory corruption. A seminal example of such a vulnerability is CVE-2017-5123, where the waitid(2) system call failed to validate the infop argument, allowing unprivileged users to trigger arbitrary kernel writes. yikes.
To disable this mitigation, set the configuration option trace/allow_unsafe_kptr:1 at startup.
bestie. do not just casually disable this one. it's on by default for a reason π
Refer to the following links for more information:
- https://lwn.net/Articles/736348/
- https://www.cvedetails.com/cve/CVE-2017-5123/
- https://salls.github.io/Linux-Kernel-CVE-2017-5123/
- https://github.com/salls/kernel-exploits/blob/master/CVE-2017-5123/exploit_smap_bypass.c
- https://www.cvedetails.com/cve/CVE-2018-1000199
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f67b15037a7a
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=27747f8bc355
π Hardening executable mappings
As of version 3.48.0, Syd does some self-hardening by enforcing immutability and Execute-Only Memory (XOM) protections on its own executable mappings during initialization. it's giving main character protecting herself. π«Ά
This mitigation was inspired by the OpenBSD mimmutable(2) system call introduced by Theo de Raadt, and its whole goal is to protect the sandbox monitor itself from compromise and code-reuse attacks like Return-Oriented Programming (ROP). how? by iterating over its executable Virtual Memory Areas (VMAs) and applying mprotect(2) to limit permissions to PROT_EXEC (blocking PROT_READ) and mseal(2) to render them immutable. these operations prevent attackers from scanning the text segment for gadgets or remapping memory to bypass W^X (Write XOR Execute) policies. literally no gadgets for you. π«
Note that this hardening is applied on a best-effort basis β specifically, mseal(2) is only available on 64-bit Linux kernels (version 6.10+), and mprotect(2) XOM support depends on the underlying architecture and kernel configuration. so it's not guaranteed everywhere, bestie, that's just how it is π
The hardening may be disabled at startup using the option trace/allow_unsafe_noxom:1.
Refer to the following links for more information:
- https://lwn.net/Articles/779478/
- https://lwn.net/Articles/948129/
- https://lwn.net/Articles/958438/
- https://lwn.net/Articles/978010/
- https://lwn.net/Articles/1006375/
- https://man.openbsd.org/mimmutable.2
- https://www.openbsd.org/papers/csw2023.pdf
π¦ Restricting File Descriptor Passing via SCM_RIGHTS
Since version 3.31.0, Syd denies file descriptors referring to block devices, directories and symbolic links from being transmitted via sendmsg(2) and sendmmsg(2) with SCM_RIGHTS on UNIX domain sockets. this is in alignment with OpenBSD pledge(2), which disallows file descriptors referring to directories from being sent. β¨
why does it matter? because passing those descriptors lets a receiving process perform I/O on files it has no path-based access to. think about it:
- a directory descriptor enables
fchdir(2)into denied subtrees - a block device descriptor affords raw sector access that bypasses filesystem access controls entirely
As of version 3.50.0, the restriction was extended to character devices, FIFOs, sockets, anonymous memory file descriptors from memfd_create(2), secret memory file descriptors from memfd_secret(2), magic symbolic links, and miscellaneous kernel objects such as epoll(7) file descriptors. the receipts:
- character device transfers expose device-specific
ioctl(2)interfaces outside the path-based sandbox model - socket transfers hand over an established network endpoint, circumventing network sandboxing
- memory file descriptor transfers carry anonymous executable-capable pages that bypass Exec and TPE sandboxing
Each of these has a name that a sendfd or recvfd rule may match. π
πΈ Immutable Sticky Bit
As of version 3.50.0, Syd enforces the immutability of the sticky bit (S_ISVTX) on directories right at the chmod(2) boundary. it intercepts chmod(2), fchmod(2), fchmodat(2), and fchmodat2(2) system calls, and β for any directory that already carries the sticky bit β it re-inserts S_ISVTX into the requested mode before forwarding the operation to the kernel, with a warning. π
and the check is performed atomically on the file descriptor obtained during path resolution, which eliminates any TOCTOU window between the mode query and the mode modification. no race, no cap.
the sticky bit on directories such as /tmp is a critical security primitive that restricts file deletion and renaming to the file owner, the directory owner, or the superuser. removing it β whether by accident, misconfiguration, or a compromised process β re-exposes the directory to symlink and hardlink race attacks including file clobbering. π
As of version 3.54.0, this enforcement also covers the setgid bit (S_ISGID) on directories. When setgid bit is set on a directory, files created within it inherit directory's group ownership rather than creating process's effective group, which is essential for shared-directory security models (e.g. group-writable project directories). Removing setgid bit β whether by accident, misconfiguration, or a compromised process β breaks group inheritance and may re-expose the directory to permission escalation. If a directory already carries S_ISGID and a chmod(2) call would remove it, Syd re-inserts the bit and emits a warning, mirroring the sticky bit behaviour. πͺ
Both enforcements may be disabled using thetrace/allow_unsafe_sticky:1option, which restores standardchmod(2)semantics at the cost of re-exposing the described vulnerabilities. so likeβ¦ pick your fighter, bestie π
ποΈ Deleted File Access Mediation
Okay so this one is kind of a big deal, bestie. As of version 3.54.0, Syd mediates access to deleted files through open file descriptors and proc_pid_fd(5) magic links, and it denies that access with ENOENT ("No such file or directory") by default. Periodt. π
Here's the lore: on Linux, when a file gets unlinked while a process still holds an open file descriptor to it, the kernel preserves the file's inode(7) and data until all referencing descriptors are closed. Cute, right? WRONG. That behaviour creates a sandbox bypass vector. π A process can open a file, have it removed from the directory tree, and then just keep on read(2), write(2), or mmap(2)-ing its contents completely unchecked by path-based access controls, because the file no longer exists at any policy-governed path. It's giving "slipped out the back door."
Syd enforces the deleted file restriction at two points: in proc_pid_fd(5) magic link resolution during path canonicalization, and in file descriptor based path lookup. Both enforcement points operate on the file descriptor obtained during resolution, which eliminates any TOCTOU window between the deleted-state query and the access decision. No gap, no window, no drama. β¨
This mitigation is inspired by AppArmor's PATH_MEDIATE_DELETED flag. When AppArmor encounters an unlinked dentry during path name resolution, it checks whether PATH_MEDIATE_DELETED is set in the profile flags. If the flag is not set, access to the deleted file is denied with ENOENT ("No such file or directory"). Syd adopts the same default-deny semantics, because we love a consistent queen. π
Administrators may relax this restriction using the trace/allow_unsafe_deleted:1 option for workloads that legitimately require continued access to deleted files β but like, fair warning: doing so reintroduces the described bypass vector. You've been told. π
Refer to the following links for more information:
- https://gitlab.com/apparmor/apparmor/-/wikis/FAQ#failed-name-lookup---deleted-entry
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/apparmor/include/path.h
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/apparmor/path.c
- https://manpages.ubuntu.com/manpages/noble/en/man5/apparmor.d.5.html
- https://man7.org/linux/man-pages/man5/proc_pid_fd.5.html
π¬ Inheriting File Descriptors
As of version 3.57.0, Syd controls which file descriptors the sandbox process inherits across execve(2). By default it closes every file descriptor except stdio(3) (0, 1, 2), so file descriptors opened or inherited by the supervisor don't leak into untrusted code. Bestie, we do not leak. We are not a leaky vessel. π«
Why so serious about this? Because a leaked file descriptor is reachable as a proc_pid_fd(5) magic symbolic link that Linux resolves to the underlying inode(7) regardless of path or mount_namespaces(7) confinement. So literally a single file descriptor to a host directory can defeat the sandbox β exactly like in the runc(1) breakouts CVE-2016-9962 and CVE-2024-21626. One fd. Whole sandbox. Gone. π
The passfd command selects file descriptors to keep, as numbers, inclusive ranges, or the keywords stdio, stdin, stdout, stderr, and all. The remaining file descriptors are closed with close_range(2) just before execve(2). Clean.
The nullfd command instead redirects file descriptors to /dev/null. This guards against a program β in particular a set-user-ID one β that starts with a standard file descriptor closed and binds the next descriptor it opens to slot 0, 1, or 2, mixing file data with standard input, output, or error, as in CVE-2002-0820. Gross. π€’
Syd opens /dev/null with openat2(2) under the RESOLVE_NO_SYMLINKS and RESOLVE_NO_MAGICLINKS resolve flags, then confirms it is the expected character device with major 1, minor 3 device numbers before dup2(2). Trust but verify, always. π
A file descriptor redirected with nullfd is implicitly inherited, so it doesn't also need to be listed in passfd. The /dev/null redirection takes precedence over passfd, and the redirected descriptor is preserved across execve(2). By default stdio(3) is passed and no file descriptor is redirected.
Refer to the following links for more information:
- https://access.redhat.com/security/vulnerabilities/cve-2016-9962
- https://github.com/opencontainers/runc/discussions/4213
- https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv
- https://github.com/strikoder/cve-2024-21626-runc-1.1.11-escape
- https://labs.snyk.io/resources/cve-2024-21626-runc-process-cwd-container-breakout/
- https://nvd.nist.gov/vuln/detail/CVE-2002-0820
- https://nvd.nist.gov/vuln/detail/CVE-2016-9962
- https://nvd.nist.gov/vuln/detail/CVE-2024-21626
- https://www.kb.cert.org/vuls/id/314963
- https://www.wiz.io/blog/leaky-vessels-container-escape-vulnerabilities
π BUGS
Yeah yeah, we know. Even icons have lore. Let's get into it. π§
π― Pointer Modification TOCTOU
So when Syd checks a system call argument and then returns SECCOMP_USER_NOTIF_FLAG_CONTINUE, the kernel executes the call with the original pointer. And a memory-sharing attacker β like threads, ptrace(2), or process_vm_writev(2) β can overwrite the pointer target between the check and the use. Classic. π€
Good news though: ptrace(2) and process_vm_writev(2) are by default denied inside the sandbox, so attacks using these vectors must come from outside the sandbox. Still, affected system calls and mitigations are the following:
execve(2)/execveat(2): Syd hooksPTRACE_EVENT_EXECand validatesproc_pid_exe(5)at theptrace(2)exec-stop. If the executed path is denylisted, the process is terminated. πchdir(2)/fchdir(2): Syd hooksptrace(2)syscall-exit and validatesproc_pid_cwd(5). If the directory is denylisted, the process is terminated.open(2)withO_PATH: Syd convertsO_PATHtoO_RDONLYand emulates the call viaSECCOMP_IOCTL_NOTIF_ADDFD, eliminating the TOCTOU vector at the cost of a semantic downgrade. See O_PATH to O_RDONLY Downgrade below. π
Other file-related calls are emulated with openat2(2) and SECCOMP_IOCTL_NOTIF_ADDFD, making them TOCTOU-free. Slay. Refer to the Linux kernel bug 218501 for more information: https://bugzilla.kernel.org/show_bug.cgi?id=218501
Restricting file execution with landlock(7) using the lock/exec category isn't affected by this issue. β¨
π O_PATH to O_RDONLY Downgrade
When a sandboxed process opens a file with O_PATH, the resulting file descriptor is a lightweight handle that permits only a restricted set of operations β fstat(2), fchdir(2), descriptor duplication, passing via sendmsg(2) with SCM_RIGHTS, and use as a dirfd argument in *at system calls β but does not convey read, write, or execute permissions on the file content itself.
Syd cannot faithfully emulate O_PATH because the kernel's SECCOMP_IOCTL_NOTIF_ADDFD interface, used to install a supervisor-opened file descriptor into the supervised process, does not support O_PATH descriptors. The kernel just... said no. π
As a consequence, Syd downgrades O_PATH to O_RDONLY before opening the file and transferring the descriptor. This emulation eliminates the pointer-modification TOCTOU race that would arise from using SECCOMP_USER_NOTIF_FLAG_CONTINUE, but it introduces a semantic discrepancy: the installed descriptor carries read permission rather than the minimal, permission-less O_PATH semantics. In practice the difference is observable in the following ways:
read(2)and related data-access system calls that would returnEBADFon a genuineO_PATHdescriptor will succeed on the downgraded descriptor.fcntl(2)F_GETFLreportsO_RDONLYinstead ofO_PATH, andproc_pid_fdinfo(5)reportsflags:without theO_PATH(010000000) bit.- Programs that intentionally use
O_PATHto avoid triggering side effects (e.g., deviceopen(2)handlers, automount, or audit events) may see those side effects with the emulated descriptor. π
This limitation is tracked in the upstream kernel bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=218501
As of version 3.50.1, Syd additionally inserts O_NONBLOCK into the downgraded flags to prevent indefinite blocking on FIFOs and spurious device-open side effects that a genuine O_PATH descriptor would never cause. She said "not today, hang." π
πΊοΈ File Descriptor TOCTOU on mmap
When mmap(2) maps a file with PROT_EXEC, both the file descriptor and the file's contents can change between Syd's check and the kernel's mapping. Which means... loading modified code. Yeah. That's the whole thing. π¬
Mitigation: By default, Syd uses ptrace(2) to intercept mmap(2) at entry, then re-checks proc_pid_maps(5) at syscall exit. If a denylisted path appears, the process is terminated, preventing execution of files from denylisted locations. Lowkey important caveat though: this does not prevent content modification of allowlisted files. π«Ά
#οΈβ£ Hashbang Script Execution
Hashbang (#!) script execution is a two-phase operation: Syd checks the script path, then the kernel re-parses the hashbang and executes the interpreter. And the interpreter can be replaced between those two phases. Two-step, one sneaky swap. π©°
Mitigation: On Linux 6.14+, Syd sets SECBIT_EXEC_RESTRICT_FILE and its lock. Interpreters supporting this check verify executability via execveat(2) with AT_EXECVE_CHECK on the open file descriptor before loading, which is TOCTOU-free. Iconic behaviour honestly. π
π¬ Interactive Code Bypass
Interpreters accepting code via -e, -c, environment variables, or a REPL completely bypass file-based execution checks. Like, there's no file to check, bestie. It's just vibes and code. π
Mitigation: On Linux 6.14+, Syd sets SECBIT_EXEC_DENY_INTERACTIVE and its lock. Cooperating interpreters refuse non-FD-sourced code. Teamwork makes the dream work. π€
π HISTORY & DESIGN π
Okay so grab a snack bestie, because Syd has lore π and honestly it's giving generational talent:
sydbox-0β https://git.sr.ht/~alip/syd/tree/sydbox-0 β aptrace(2)based sandbox.sydbox-1β https://git.sr.ht/~alip/syd/tree/sydbox-1 β aptrace(2)andseccomp(2)based sandbox.sydbox-2β https://git.sr.ht/~alip/syd/tree/sydbox-1 β aseccomp(2)and seccomp-notify based sandbox.sydbox-3β a rewrite ofsydbox-2in Rust and it's what you are looking at. π
This codebase has a history of a bit over 15 years and up to this point we have used C11 as our implementation language for various reasons. With sydbox-3 we took the next step and rewrote the sandbox from scratch using the Rust programming language with the only non-Rust dependency being libseccomp. Although we inherit many ideas and design decisions from the old codebase, we also don't shy away from radically changing the internal implementation making it much simpler, idiomatic, and less prone to bugs. We have proper multiarch support since release 3.0.11, e.g on x86-64, you can run your x32 or x86 binaries just fine under Syd. π¦
This version takes advantage of multithreading and handles system calls using a thread pool whose size is equal to the number of CPUs on the running machine and utilises globsets to match a list of patterns at once, thus continues to perform reasonably well even with very long rulesets. This version also comes with four new sandboxing categories called Lock Sandboxing, Memory Sandboxing, PID sandboxing, Stat Sandboxing, Force Sandboxing: Lock Sandboxing utilises the Landlock Linux Security Module (LSM), Memory Sandboxing allows the user to define a per-process memory limit, PID sandboxing allows the user to define a limit on the maximum number of running tasks under the sandbox, Stat Sandboxing can be used to effectively hide files and directories from the sandboxed process whereas Force Sandboxing can be used to verify file checksums prior to exec, similar to HardenedBSD's Integriforce and NetBSD's Veriexec. π₯
Finally, the new Syd has support for namespaces. Use e.g. syd -munshare/user:1 to create a user namespace. You may use mount, uts, ipc, pid, net, and cgroup instead of user to create various namespaces. You may use the container profile as a shorthand to create namespaces with syd -pcontainer. β¨
You may use Syd as your login shell because it is very practical to have a restricted user. To do this simply add /path/to/syd to the file /etc/shells and do chsh -s /path/to/syd username as root. In this mode the sandbox may be configured using the files /etc/user.syd-3 and ~/.user.syd-3. If you want to restrict user configuration of the sandbox, lock the sandbox using lock:on at the end of the site-wide configuration file. π«Ά
πΈ EXHERBO πΈ
Syd is the default sandbox of Exherbo Linux. We use it to provide a restricted environment under which package builds run with controlled access to file system and network resources. exheres-0 has a function called esandbox to interact with Syd. no cap, that's iconic π
π¦ SEE ALSO π¦
Go read these, bestie, they're literally the whole syllabus:
syd(1), syd(2), syd(5), seccomp(2), pidfd_getfd(2), pidfd_send_signal(2), ioctl(2), ioctl_tty(2), prctl(2), namespaces(7), cgroup_namespaces(7), ipc_namespaces(7), mount_namespaces(7), network_namespaces(7), pid_namespaces(7), user_namespaces(7), uts_namespaces(7), sydtutorial(7)
https://exherbo.org/docs/eapi/exheres-for-smarties.html#sandboxing
π AUTHORS π
Maintained by Ali Polatel. Up-to-date sources can be found at https://gitlab.exherbo.org/sydbox/sydbox.git and on Radicle at rad:z38HCnbmcDegA2BMxuPaPRPMdp6wF. Bugs/patches can be submitted to https://gitlab.exherbo.org/groups/sydbox/-/issues. Discuss in #sydbox on Libera Chat or in #sydbox:mailstation.de on Matrix. ππ«Ά
and that's a wrap on the whole page, bestie!! This one's stamped 2026-09-01 with the manual tag SYD(7) down in the footer β go forth, sandbox responsibly, and slay β¨π
π
π¦ where this page comes from
- arch rolling: syd 3.59.0-1 Β· GPL-3.0-only
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms