A seccomp filter can stop a selected system call before execution and turn it into a request on a listener file descriptor. The calling thread remains blocked while a userspace supervisor examines the notification and returns a result. This creates a mediation boundary that is narrower than tracing every system call and more dynamic than encoding every decision directly in classic BPF.
The mechanism is SECCOMP_RET_USER_NOTIF. A filter returns that action for operations that require external mediation. A filter installed with SECCOMP_FILTER_FLAG_NEW_LISTENER yields a listener file descriptor, and a supervisor uses seccomp notification ioctls on that descriptor.
The filter selects which calls leave the kernel decision path
A conventional seccomp filter resolves a system call immediately with actions such as SECCOMP_RET_ALLOW, SECCOMP_RET_ERRNO, or a terminating action. User notification adds a path in which the filter delegates the decision.
target thread
|
| system call
v
seccomp filter
|
+-- ALLOW ----------> normal syscall execution
|
+-- ERRNO ----------> synthetic failure
|
+-- USER_NOTIF -----> listener fd -----> supervisor
^ |
| |
+---- response ----+The listener belongs to the installed filter rather than to one individual target thread. If tasks sharing that filter generate notifications, the same listener can receive them. This makes the descriptor suitable for an event loop in a container manager or another process supervising a sandbox.
If SECCOMP_RET_USER_NOTIF is selected but no attached listener is available, the system call does not silently proceed. The target receives ENOSYS behavior instead.
Notifications carry register arguments, not copied pointed-to data
SECCOMP_IOCTL_NOTIF_RECV returns a struct seccomp_notif. Its data member contains the system call number, architecture information, instruction pointer, and argument values captured from the system call interface.
For an argument that is itself an integer, file descriptor number, flag mask, or similar scalar, that value is directly present. A pointer argument is different: the notification contains the pointer value, not an immutable copy of the target memory referenced by it.
That distinction creates a time-of-check/time-of-use boundary. A supervisor that reads a pathname from target memory and then makes a policy decision cannot assume the target memory remains unchanged afterward. Multi-threaded target code may modify the referenced bytes concurrently.
A robust mediator copies any required target memory into supervisor-owned storage before basing a decision on it. Even then, allowing the original system call to continue can cause the kernel to read the target memory again, so the supervisor must account for a second observation of mutable state.
Notification IDs identify requests that can disappear
Each notification has an ID. The supervisor returns that ID in SECCOMP_IOCTL_NOTIF_SEND, binding the response to the blocked request.
A notification is not guaranteed to remain valid indefinitely. The target can terminate, or signal handling can abort the blocked operation. SECCOMP_IOCTL_NOTIF_ID_VALID lets a supervisor test whether an ID still refers to a live request before performing an external action whose side effects would be costly to apply to a stale request.
The validity check does not turn a multi-step userspace sequence into a general transaction. State can still change after the check. It is a request-liveness facility, not a universal synchronization primitive.
A response can emulate a return value or continue the original call
The normal response structure, struct seccomp_notif_resp, carries the notification ID plus a return value, an error, and flags. The supervisor can synthesize success or failure without executing the target’s original system call.
Linux also provides SECCOMP_USER_NOTIF_FLAG_CONTINUE. With this flag, the kernel resumes the original system call after the notification. This mode requires particular care for pointer arguments because the syscall implementation can observe target memory after the supervisor’s policy check.
Continuation is therefore not equivalent to executing a frozen request captured at notification time. It resumes an operation whose register arguments were reported earlier but whose referenced memory may still be mutable.
The security boundary also depends on filter precedence. Seccomp evaluates installed filters according to action precedence; a user-notification action does not override a higher-precedence restrictive result from another filter.
ADDFD can install a supervisor-owned descriptor in the target
Some intercepted operations naturally return a file descriptor. A supervisor may need to perform the privileged or policy-sensitive operation itself and then provide the resulting descriptor to the blocked target.
SECCOMP_IOCTL_NOTIF_ADDFD, available on kernels that implement the operation, duplicates a descriptor from the supervisor into the target’s file descriptor table. This is semantically similar to descriptor transfer with SCM_RIGHTS: both sides can hold descriptors referring to the same open file description.
The kernel can allocate the lowest available descriptor number in the target, or SECCOMP_ADDFD_FLAG_SETFD can request a specific target descriptor number. newfd_flags can apply O_CLOEXEC to the installed descriptor.
For an emulated operation such as openat(), the sequence can be:
supervisor receives notification
|
v
supervisor opens approved object
|
v
SECCOMP_IOCTL_NOTIF_ADDFD
|
v
target receives descriptorSECCOMP_ADDFD_FLAG_SEND can combine descriptor installation and the notification response atomically with respect to that operation. This avoids a gap between successfully inserting the descriptor and separately sending the syscall result.
User notification is mediation, not a general privilege boundary by itself
The supervisor has to validate every input that influences a privileged action. A notification reports what the target attempted, but target-controlled file descriptor numbers, pointers, namespaces, credentials, and concurrent state can affect the meaning of those values.
For example, a numeric descriptor argument identifies an entry in the target’s descriptor table, not the supervisor’s table. Acting on the same integer locally refers to an unrelated descriptor. Cross-process descriptor operations require mechanisms designed for that relationship rather than numeric substitution.
Similarly, pathname mediation has filesystem namespace and directory-descriptor context. Reconstructing an operation in the supervisor can produce different resolution semantics unless the mediator deliberately preserves the relevant context.
The useful boundary is therefore precise: seccomp user notification converts selected syscall attempts into synchronous requests that a supervisor can inspect and answer. Correct emulation still requires explicit treatment of mutable memory, descriptor identity, namespaces, request lifetime, and the semantics of the intercepted operation.