Files¶
With write in --mediate (the default), writes outside what your rules allow are paused and asked about. What counts as a write: opening a file for writing or truncating it, creating, deleting, renaming or linking a file, a directory or a symlink, truncate and mknod. Reads, permissions, owners, extended attributes and timestamps are not mediated.
Lite mode¶
Syscalls paused:
openatwith a write flag (O_WRONLY,O_RDWR,O_CREAT,O_TRUNC), andopenat2always: its flags are in a struct the filter cannot read.unlinkat,mkdirat,mknodat,symlinkat,linkat,renameat,renameat2,truncate.- On x86-64, also the legacy
open(with a write flag),creat,unlink,rmdir,mkdir,mknod,symlink,link,rename.
linkat and the renames are checked on both paths: linking a protected file elsewhere would allow writing it.
What the supervisor reads:
- The path strings, from
/proc/<pid>/mem, and foropenat2itsstruct open_how: anopenat2without a write flag continues at once. - The directory the path is relative to:
/proc/<pid>/cwd, or/proc/<pid>/fd/<n>for a directory file descriptor. In Kubernetes, the path is resolved in/proc/<pid>/root. - Each symlink of the path, one component at a time, with
/proc/selftaken as the calling process's. The last component is followed when the kernel would follow it (not withO_NOFOLLOW, norO_CREAT|O_EXCL).
A path through /proc/<pid>/fd/<n> to a pipe or socket is not a file write and continues. A write to /proc/<pid>/... of another process of the sandbox continues; for a process outside it, the prompt names the file and the process. A path that cannot be read is asked about as an unknown path, "once" only (see Unreadable paths).
TOCTOU: exposed. The kernel resolves the path again when the syscall continues, and another thread can change the path string, the openat2 flags or the files on the way (a symlink, a rename) after the check.
Strict mode¶
The same syscalls are paused and asked about. Then the LSM hooks check the file the kernel is about to modify:
| Hook | Checks |
|---|---|
file_open |
a file opened for writing or with O_TRUNC |
path_mknod |
a file created (not a unix socket bound with bind, which is not a file write) |
path_mkdir, path_rmdir, path_unlink, path_symlink, path_truncate |
the file or directory |
path_link, path_rename |
both the source and the destination |
sb_alloc_security, sb_free_security |
record the filesystems a member mounts (a tmpfs in its namespace...): its session may write them |
sb_kern_mount |
denies members mounting overlayfs (see Overlayfs) |
A file is checked by walking from it up to its filesystem's root, and looking up each component in the session's rules. Pipes, sockets and memfds have no path and are not checked. A file whose path cannot be walked (more than 64 components deep, or a disconnected dentry from open_by_handle_at) is denied. A write to /proc/<pid>/... is allowed for a process of the same session, and otherwise looked up by file name and process name.
When the supervisor cannot read the path, or the process is in another mount namespace or chroot than the session's, it lets the syscall continue without asking, and the kernel's rules decide.
Maps:
| Map | Key → value | Relation | Written by |
|---|---|---|---|
rules |
session, parent node, name → the node: an id for its children, and the allow, ask (expiries) and deny for the node and for everything below it | one entry per path component of each rule's path, shared by the rules below the same directory; a filesystem's root is a node too | the supervisor, translating each rule's path with the session's mounts |
proc |
session, /proc/<pid>/ file name, process name → allow, ask (expiries) and deny |
1:1 with the rules on /proc files of processes outside the sandbox |
the supervisor |
owned_sbs |
filesystem (superblock) → the session whose member mounted it | 1:1 | the BPF programs, at mount and unmount |
grants |
thread → the nodes of the paths of a "once" answer (up to 8: rename and link have two), or a /proc file |
1:1, per thread | the supervisor, before continuing; deleted when the syscall returns |
An ask rule cuts off the allows above it: the file is denied unless a deeper allow, or the thread's grant, allows it. A deny wins over both.
TOCTOU: closed. The hooks run on the dentry the kernel resolved, with the flags it uses.
See Limitations.