A seccomp filter can do more than allow or reject a system call immediately. With user notification, a matching call can be suspended while another process receives a structured request on a listener file descriptor and decides what result the blocked thread receives. The mechanism turns selected syscall decisions into a brokered interface without moving the entire syscall implementation into user space.

The boundary is precise but narrower than a general interposition layer. The kernel still owns syscall dispatch, task state, descriptor tables, and validation performed by kernel code. The broker receives metadata and can return a value, an error, or in supported cases request continued execution of the original syscall. Correct designs account for mutable target memory, notification lifetime, and the fact that a policy decision is not automatically a transaction over process state.

The filter selects notification before the syscall executes

A process installs a seccomp filter whose selected rule returns SECCOMP_RET_USER_NOTIF. When the filter is installed with SECCOMP_FILTER_FLAG_NEW_LISTENER, the installation returns a listener file descriptor. A supervisor that holds that descriptor can receive notifications with SECCOMP_IOCTL_NOTIF_RECV.

The notification contains the target task identifier, syscall number, architecture value, instruction pointer, syscall arguments, and a notification identifier. At this point the selected syscall has not completed. The target thread remains blocked until the notification is answered, interrupted, or otherwise invalidated by task lifecycle events.

This arrangement separates selection from policy. BPF filter logic remains constrained and executes in the seccomp path, while a broker can use richer user-space state to decide a response. That flexibility also expands the protocol surface: the broker now participates in the latency and availability of every syscall routed to it.

A notification identifier represents a transient request

The numeric notification identifier is not a permanent handle to a task or syscall attempt. A request can become stale before the broker finishes processing it, for example when the blocked operation is interrupted. SECCOMP_IOCTL_NOTIF_ID_VALID lets the broker test whether an identifier still refers to a live notification.

Validation matters when a broker performs work with external effects before replying. An identifier that was valid at receive time can cease to be valid later. The validity check narrows a race window; it does not turn the broker’s surrounding operations into an atomic transaction with the target.

The response sent with SECCOMP_IOCTL_NOTIF_SEND must carry the identifier associated with the request. The kernel rejects a response for a notification that is no longer valid. Broker state should therefore be keyed by notification lifetime rather than treating task identifiers alone as request identity.

Syscall arguments are values, not frozen objects

The notification exposes the syscall argument registers captured for the attempted call. For arguments that are scalar values, those register values may be sufficient for a policy decision. Pointer arguments create a different boundary.

An address in a syscall argument refers to memory in the target process. The notification does not copy the pointed-to object into an immutable request payload. Other threads in the target can modify that memory while the broker examines it. A pathname, socket address, or structured argument read by the broker can therefore differ from data later consumed by kernel execution if the original syscall is continued.

This is a classic time-of-check/time-of-use boundary. A broker must not treat a user-space copy of mutable target memory as proof of what a later kernel operation will consume. Policies that require stable object identity need a mechanism whose contract binds the checked object to the eventual operation, rather than relying only on a pointer snapshot.

Continuing a syscall delegates execution back to the kernel

A response can use SECCOMP_USER_NOTIF_FLAG_CONTINUE to tell the kernel to execute the syscall that triggered the notification. This mode is materially different from returning an emulated result. The broker authorizes continuation, but the normal syscall implementation subsequently reads target state according to that syscall’s own semantics.

The continuation flag therefore carries the pointer-race concern directly into policy design. If authorization depended on bytes read from target memory, those bytes may change before kernel consumption. Continuation is appropriate only when the policy remains valid across that mutability or another invariant closes the race.

A broker can instead provide a synthetic return value or error without executing the original syscall. That can represent denial or emulation, but it also places responsibility on the broker to preserve the observable contract expected by the target. Returning success does not cause kernel side effects that the real syscall would have produced.

Descriptor injection binds a broker-owned object to the target

Some mediation patterns need to return a real file descriptor rather than merely an integer that resembles one. SECCOMP_IOCTL_NOTIF_ADDFD lets the broker install a file descriptor into the target’s descriptor table for a live notification. The source descriptor belongs to the broker, while the installed descriptor becomes a reference available to the target.

This mechanism is useful for operations such as brokered file opening because the broker can select and open an object under its own policy, then transfer the resulting kernel reference. The target receives a descriptor to that selected object rather than re-resolving a pathname after authorization.

Descriptor injection has its own lifecycle rules. The notification must still be valid, descriptor flags and target descriptor selection must follow the ioctl contract, and the broker must handle failures without assuming that a requested descriptor number was installed. The operation solves object-reference transfer; it does not make unrelated broker work atomic.

Broker failure becomes part of syscall availability

A thread blocked on user notification depends on the listener side making progress. If the broker is overloaded, deadlocked, or waiting on a resource held by the target, mediated calls can stall. This makes dependency direction a first-class design property.

A broker should avoid requiring a mediated operation from the same dependency chain to service the request. Recursive mediation can otherwise form a cycle in which the target waits for the broker while the broker waits for an action that itself cannot proceed. Separating broker resources, limiting the mediated syscall set, and defining shutdown behavior keep that dependency visible.

Listener lifetime also matters. The file descriptor is a kernel resource whose ownership can be transferred using normal descriptor-passing mechanisms. Process supervision must define which component keeps the listener alive and what happens to blocked requests when that component exits.

User notification is policy delegation, not transparent syscall virtualization

seccomp user notification provides a narrow control point: a filter identifies selected syscall attempts, the kernel blocks those attempts, and a listener receives enough metadata to make a user-space policy decision. The broker can deny, emulate a result, continue execution, or use supported operations such as descriptor injection.

Its strongest designs preserve the distinction between captured register values and mutable target objects. Notification IDs are transient, target memory can change concurrently, continued syscalls execute later under normal kernel semantics, and broker availability becomes part of the target’s execution path.

Those constraints make the interface suitable for carefully bounded mediation. They also prevent a notification broker from being treated as a transparent replacement for kernel syscall semantics. The policy boundary is reliable only when request lifetime, object identity, target-memory races, and broker dependencies are explicit parts of the design.