FUSE: The Boundary Linux Already Knows How to Enforce
FUSE is often introduced as a way to write a filesystem without writing ordinary kernel code. That description is useful, but it misses the reason kSFS reuses FUSE. FUSE is also a concrete request-and-response contract between Linux and an implementation that the kernel cannot simply trust. By tracing one operation through that contract, we can see what authority stays with Linux, why failure recovery is part of the security boundary, and which costs kSFS later tries to remove without discarding the contract itself.
A filesystem operation becomes a message exchange
Consider a process that asks to remove a file from a FUSE-mounted filesystem. The application issues an ordinary filesystem operation. Linux's virtual file system layer routes that operation to the FUSE driver rather than calling a trusted, native filesystem implementation directly.
The driver packages the operation as a request associated with the filesystem connection. It places the request where the filesystem daemon can receive it and wakes the daemon if it is waiting. The daemon reads the request, interprets the path and operation according to its own filesystem implementation, performs the corresponding work, and writes a response. Linux then matches the response to the outstanding request, wakes the waiting kernel path, and returns success or an error to the application.
There are two different kinds of waiting in that story. The application-side kernel path waits for the filesystem result, while the daemon waits for work to become available. The driver must keep enough state to know which response belongs to which operation and whether a request is still pending, already being processed, completed, or being interrupted. That state stays on the trusted side. It is what lets Linux decide whether a late response should wake a caller, be ignored, or be treated as part of recovery rather than allowing the daemon to define the lifecycle of the kernel request.
The exact internal data structures are more detailed than this description, but the ownership pattern is the important part:
The application owns the original intent, but it does not decide how the kernel routes the request. The FUSE driver owns the connection and the kernel-side request state. The daemon owns the filesystem-specific interpretation and the data or metadata it supplies. The kernel does not hand the daemon unrestricted pointers into the rest of its state; it exchanges data through a defined interface. This is why the paper calls FUSE one of Linux's microkernel-like interfaces: the implementation behaves like a service on the other side of a message boundary, even though the rest of Linux remains monolithic.
The boundary is visible in what the daemon is allowed to provide. The Linux documentation defines a user-space filesystem as one whose data and metadata come from an ordinary user-space process while applications continue to use the normal kernel interface. The daemon can implement an unfamiliar storage format, a network filesystem, or a view over some other data source. It does not become the owner of the kernel's VFS structures merely because Linux asks it for an answer.
That separation has a practical consequence for validation. The kernel can treat a response as untrusted input. It can validate the shape of the response, apply the mount and process-access policy around it, and decide how the result affects the caller. A native in-kernel filesystem is expected to obey the kernel's internal conventions directly; a FUSE daemon must communicate through the conventions that the protocol exposes.
This does not make the daemon's answers correct. A malicious daemon can return misleading metadata, refuse to answer, or implement filesystem semantics badly. The boundary changes what those failures can directly damage. The daemon's memory is outside the kernel's address space, and the connection can be interrupted or aborted. The kernel still has to handle the result, but it has more opportunities to reject, limit, or recover from the implementation's behavior.
The distinction between authority and correctness is easy to miss. Linux can restrict a daemon from opening a device or observing another user's inaccessible process without knowing whether the daemon's directory lookup, free-space accounting, or metadata update is semantically right. The protocol gives the kernel a place to enforce authority; it does not turn the kernel into an oracle for every filesystem rule. That is why FUSE can be a useful security boundary even when different FUSE implementations have different bugs and different levels of compatibility.
The message path also explains why FUSE resembles a microkernel more than a mere library. In a library call, the caller and callee normally share an address space and can directly inspect one another's state. In FUSE, the implementation communicates through a channel with explicit requests, replies, and waiting. The cost of that separation is high, but the separation gives the trusted side a place to enforce rules.
A boundary is also a recovery policy
FUSE's security model begins before a request reaches the daemon. The Linux FUSE documentation describes non-privileged mounts and the restrictions required to keep a mounting user from turning the capability into broader system control. A non-privileged mount is not allowed to expose device files, and nosuid and nodev protections are added so that a mounted image cannot straightforwardly become a privilege-escalation mechanism.
The mount point is also part of the boundary. A user should not be able to mount a filesystem over a file or directory they could not otherwise modify, because doing so could change the behavior observed by another process. The documentation discusses checks on the mount point and restrictions on which processes may access a non-privileged mount. Without those checks, a filesystem daemon might learn the timing of another user's operations or delay an operation in a process that the mounting user cannot otherwise control.
These rules illustrate a broader point: the security of a filesystem service is not determined only by whether its code is outside the kernel. The kernel must control who can create the service, where it can be attached, which processes can use it, and what it can expose. Configuration can relax some restrictions—for example, an administrator may permit broader access when they trust the users—but the relaxation is an explicit policy decision with corresponding information-leakage or denial-of-service risks.
The same principle applies to failure. A FUSE connection exists until the daemon dies or the filesystem is unmounted, and the documentation describes several ways to abort a nonresponsive connection. A broken daemon, a network failure behind a filesystem implementation, an accidental deadlock, and a malicious deadlock do not all have the same cause, but they share an operational requirement: the kernel must be able to stop waiting forever.
One simple deadlock can occur when an application holds a filesystem-related lock while asking the daemon to perform an operation, and the daemon's handling of that request tries to acquire the same lock through another path. The request waits for the daemon; the daemon waits for the lock; neither can make progress. The FUSE documentation also describes subtler reentrant cases in which handling a response triggers more filesystem work while another operation already holds a relevant lock. The details differ, but the lesson is stable. A message boundary does not prevent cycles in the work around the message.
FUSE therefore needs both interruption rules and an escape hatch. An interrupted request may be removed before it reaches user space, or an interrupt request may be sent to the daemon after the original request has been delivered. The daemon may honor that request or ignore it, and races can occur between the original operation and its interruption. When the filesystem cannot locate the original request, the documented behavior includes waiting and eventually returning an error. These are not incidental implementation details. They are how a boundary prevents a stuck participant from permanently owning the kernel's progress.
This is where liveness becomes distinct from memory safety. A daemon can have perfectly valid memory accesses and still never respond. A protocol can have well-formed messages and still allow a cycle of waits. FUSE's abort mechanism does not make every filesystem operation correct, but it gives the trusted side a way to reclaim control of the connection. Any in-kernel adaptation must solve the same problem without relying on the fact that the implementation is a killable user-space process.
Recovery also has a boundary of its own. Aborting a connection can release waiting requests and return errors, but it cannot retroactively make a partially completed filesystem operation semantically safe. The kernel must preserve the rules for references, pending work, and unmounting while it stops the participant. In other words, the trusted side must own not just the happy-path message exchange but also the transition from “waiting for an answer” to “the participant is no longer allowed to answer.” That is a small example of why liveness is a protocol property, not simply a scheduler setting.
Why the contract is valuable even when the transport is slow
The request path has a cost at every crossing. Linux must queue work, wake a user-space process, move request data into a buffer the daemon can read, wait for the daemon to process it, receive the response, and resume the original operation. The kSFS paper identifies system calls, mode and context switches, serialization, and unnecessary copies as sources of overhead. The fixed cost is especially visible when an application performs many small random operations or when multiple threads compete for a single filesystem implementation.
It would be easy to conclude that FUSE is the wrong abstraction because its ordinary implementation is slow. That conclusion would discard the part of FUSE that kSFS finds valuable. The protocol separates a filesystem implementation from the rest of the kernel and provides a recognizable set of operations. Existing FUSE implementations already use that model, and the kernel side already knows how to route requests, apply mount policy, and deal with a connection that stops responding.
The more precise design move is to preserve the semantics while changing the transport. kSFS replaces the user-space device-file exchange with calls into an in-kernel WebAssembly instance. That change removes some mode and context switches, but it does not mean that the filesystem implementation receives the old broad in-kernel interface. The implementation remains on the far side of a FUSE-shaped contract; only the location and cost of the far side change.
This also clarifies what “fast” must mean. It is not enough for a WebAssembly function call to be cheap in isolation. The whole operation still has to validate arguments, move or expose data safely, update filesystem metadata, perform storage I/O, and return a result. If optimizing one step makes another step unsafe—for example, by allowing a module to retain an unchecked pointer into kernel memory—the design has removed the boundary rather than improved it. The protocol is valuable because it gives performance engineering a fixed security shape to preserve.
The kSFS paper's later optimizations follow this logic. Large read and write operations need data movement, but that movement can be arranged through validated zero-copy buffers rather than repeated intermediate copies. Work that updates filesystem metadata may need serialization, while the longer bulk transfer can sometimes proceed concurrently. Those changes are possible because the request semantics identify what the implementation is asking for and because the driver remains responsible for the trusted side of the operation.
The performance cost is therefore a trade-off, not an accidental flaw. Native Linux file systems avoid many crossings because they are trusted participants in the same execution environment. Ordinary FUSE pays for a stronger separation through process transitions and data movement. kSFS tries to occupy the middle: keep the interaction contract and its recovery discipline, move the handler close to the kernel, and add a sandbox that can contain faults. Whether that middle is useful depends on whether the new runtime can enforce memory, control flow, liveness, and resource policy without reintroducing the broad authority that FUSE was designed to avoid.
A FUSE request is thus more than a path from an application to a daemon. It is a compact statement of what the kernel is willing to ask an untrusted filesystem to do, how the answer returns, and what happens when the participant refuses to cooperate. That contract is the foundation kSFS carries forward. The remaining question is how to combine it with an execution model that is safe enough for kernel space and efficient enough not to make the boundary unusable.