The userfaultfd() Syscall: Handling Page Faults in User Space

2026-07-18

When a process touches an unmapped or lazily-populated page, the kernel normally decides what to do: allocate a zero page, read from swap, fault a file page in. userfaultfd() hands that decision to user space. You register a range of virtual memory with the kernel, and when a fault hits it, the faulting thread is parked and you get a notification on a file descriptor instead of the usual SIGSEGV or automatic backing.

Mechanically: userfaultfd(O_CLOEXEC) returns an fd. You ioctl UFFDIO_REGISTER to claim a VMA range and pick modes (UFFDIO_REGISTER_MODE_MISSING for absent pages, _WP for write-protection faults, _MINOR for pages that exist but aren't installed in the page table). Then a handler thread does read() on the fd — each read yields a uffd_msg containing the faulting address, thread ID, and flags. You resolve the fault by ioctl'ing UFFDIO_COPY (atomically copy a page of data into the hole), UFFDIO_ZEROPAGE, or UFFDIO_CONTINUE. Only then does the parked thread resume.

The canonical use case is live migration. CRIU and QEMU use userfaultfd for post-copy migration: the VM starts running on the destination host with an empty address space, and every fault triggers an RDMA pull of the missing page from the source. Compare this to pre-copy, where you iteratively ship dirty pages until convergence — post-copy guarantees a bounded switchover time, at the cost of network-latency-per-fault after the switch.

Other real uses: garbage collectors use write-protect mode to implement concurrent marking (mprotect-style, but without the mmap_lock contention and without SIGSEGV round-trips). Distributed shared memory systems fault pages in from remote nodes. Fuzzers snapshot and restore process state by write-protecting the heap and copy-on-write'ing on demand.

Rule of thumb on cost: a userfaultfd round-trip is roughly 5–15 microseconds on modern hardware — one context switch to the handler, one memcpy, one ioctl, one wake. Compare with a normal minor fault at ~1 μs and an anonymous-page major fault at ~10 μs from swap. So userfaultfd is cheap enough to handle sparse faults but catastrophic if you take one per 4KB of a sequential scan. Pre-fault hot regions with UFFDIO_COPY in bulk before the guest touches them.

Security note: unprivileged userfaultfd was famously abused for Dirty COW-style race widening — pausing a kernel copy mid-flight gave attackers unlimited time to swap the target page. Since 5.11, the vm.unprivileged_userfaultfd sysctl defaults to 0, restricting the syscall to CAP_SYS_PTRACE holders unless you opt in.

See it in action: Check out [2016] LinuxCon: Userland Page Faults and Beyond: Why, How and What
#39;s Next by Andrea Arcangeli by KVM Forum to see this theory applied.
Key Takeaway: userfaultfd turns page faults into a user-space message queue, enabling post-copy VM migration, concurrent GCs, and DSM — at ~10μs per fault, so it wins on sparse, latency-tolerant access patterns and loses on dense sequential ones.