A seccomp filter can stop a task at syscall entry and turn that event into a message for another process. With SECCOMP_RET_USER_NOTIF, the kernel does not immediately execute the selected syscall. It creates a notification for a listener, blocks the calling task, and waits for a response that can supply a return value, inject a file descriptor, or permit the syscall to continue.
That boundary is narrower than general syscall emulation. The notification carries syscall metadata and register argument values, while memory referenced by pointer arguments remains in the target process. The target can also disappear or have its notification invalidated while a supervisor is making a decision. Those properties make identity, memory ownership, and response timing part of the interface contract.
The listener belongs to a filter, not one task
Installing a filter with SECCOMP_FILTER_FLAG_NEW_LISTENER returns a notification file descriptor. A filter action of SECCOMP_RET_USER_NOTIF routes matching syscall attempts to that listener.
The listener is associated with the filter. If a task that uses the filter creates descendants that inherit it, notifications from multiple tasks can arrive through the same descriptor. The supervisor therefore cannot treat the descriptor itself as the identity of one caller.
Each received struct seccomp_notif contains an id, a task identifier as visible from the listener context, and struct seccomp_data. The data includes the syscall number, architecture value, instruction pointer, and six syscall argument register values.
The architecture field is part of correct interpretation. Syscall numbers and calling conventions are architecture-dependent, so a filter or supervisor that reasons about a syscall number without the corresponding architecture can attach the wrong meaning to register state.
Register values do not snapshot pointed-to memory
For scalar arguments, the value in seccomp_data.args is the value presented at syscall entry. Pointer arguments are different. The notification contains the pointer value, not a kernel-owned copy of the bytes at that address.
Consider an intercepted operation whose argument names a pathname:
target task supervisor
path buffer = "/a"
syscall(path) ---- notify --> pointer value
blocked reads target memory
makes decisionReading the target memory through an available process-inspection mechanism is a separate operation. The bytes are owned by the target address space, not by the seccomp notification.
A supervisor that copies referenced bytes into its own memory can make its own decision against that copy. That still does not make a later continued syscall consume the same copy. If the supervisor replies with SECCOMP_USER_NOTIF_FLAG_CONTINUE, the kernel resumes the original syscall, which can dereference target memory according to normal syscall semantics. Target memory may have changed between inspection and use.
This is a time-of-check/time-of-use boundary built into continuation. User notification is therefore unsuitable as a standalone security authorization layer for continued syscalls whose security-relevant arguments must be dereferenced from mutable target memory. A separate kernel-enforced restriction may still make continuation safe for a particular design, but that property comes from the additional restriction, not from the notification exchange.
Notification IDs close a task-lifetime race
The target remains a live participant while the supervisor processes a notification. It can be terminated, and a pending notification can cease to be valid before the response arrives.
Linux assigns an identifier to each notification. SECCOMP_IOCTL_NOTIF_ID_VALID lets the supervisor test whether an ID still refers to a valid pending request. Response operations also identify the request by that ID.
The ID matters because a numeric process identifier alone is not a durable transaction handle. Process IDs can be reused after task exit, while the notification ID names the pending seccomp interaction associated with the listener.
An ID-validity check is still a point-in-time observation. A target can cease to exist after the check and before a later operation. Code must therefore handle response or descriptor-injection failure rather than treating a successful validity query as a lease on target lifetime.
Returning a value and continuing the syscall are different actions
A normal notification response can make the blocked syscall appear to return a value or an error without executing the original syscall. In that mode, the supervisor is supplying the visible result of the intercepted operation.
Continuation has different semantics. Setting SECCOMP_USER_NOTIF_FLAG_CONTINUE tells the kernel to execute the syscall after the notification phase. The supervisor is no longer emulating the result; it is releasing the original operation.
That distinction changes the trust boundary. A synthetic result can be based entirely on state held by the supervisor, subject to the surrounding application protocol. Continuation reintroduces the kernel’s normal consumption of target-owned syscall arguments. Any decision made from mutable pointed-to data can become stale before the kernel consumes it.
The two response modes can look similar from the target because both eventually unblock the syscall instruction. Their data dependencies are not similar.
File-descriptor injection transfers a kernel object
SECCOMP_IOCTL_NOTIF_ADDFD gives the supervisor another option for syscalls whose useful result is a file descriptor. The supervisor can place a descriptor into the target’s descriptor table and report the resulting descriptor number.
This is materially different from returning an integer that merely resembles a descriptor number. A file descriptor is a process-local table entry referring to an open file description or another kernel object. Injection creates that table relationship in the target.
The operation can request close-on-exec state for the new descriptor. Linux also provides a mode that combines descriptor injection with sending the notification response, avoiding a separate interval between installing the descriptor and completing the intercepted call.
The supervisor still has to reason about the object it injects. Path resolution, credentials, namespace context, and open flags used to obtain the supervisor-side descriptor determine what kernel object is transferred. Injection does not cause the original target syscall to run under the target’s original lookup context.
Blocking changes signal and cancellation behavior
While a notification is pending, the target task is sleeping inside the seccomp interaction rather than executing the selected syscall. Signals can affect that wait, and a notification may be aborted before the supervisor responds.
Linux provides SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV for designs that need different handling after the supervisor has received the request. With that mode, once the notification has been received, nonfatal signals do not interrupt the target’s wait for the supervisor response, while fatal signals can still terminate it.
This changes coordination semantics, not the meaning of the intercepted syscall. A supervisor that may perform long operations needs an explicit cancellation model because target exit, supervisor failure, and notification invalidation remain possible boundaries.
The listener descriptor is pollable, so it can participate in an event loop. Readiness indicates that notification work is available; it does not guarantee that every request observed as ready will remain valid until arbitrary later processing completes.
The mechanism is an interposition protocol
Seccomp user notification inserts a protocol between syscall entry and either a synthetic result or kernel execution. Its strongest property is not transparent emulation but controlled interposition at a defined kernel boundary.
The filter selects syscall entries. The notification captures register-level metadata. The supervisor owns its copied decision state. The target retains its address space and can change lifetime. A response either substitutes a result, transfers a kernel object through descriptor injection, or releases the original syscall to continue.
Those boundaries determine which designs remain coherent. Operations based on stable scalar metadata are simpler than decisions tied to mutable target pointers. Descriptor injection can move an already-open kernel object without pretending that an integer is sufficient. Continuation is useful only when the remaining kernel execution is acceptable even if target-owned memory changes after inspection.
The interface therefore exposes a precise separation: syscall entry can be suspended and mediated, but the notification does not freeze the target process into an immutable request object.