Why User Buffers Are Not Safe for Direct Use in Linux Kernel and DMA
1.
Introduction
When a user space application passes a buffer to the kernel through a system call, it passes a virtual address. That address points somewhere in the process's virtual address space. The kernel receives it, but it cannot simply use it the way user space does. The rules are different on the kernel side of the boundary, and the rules are even stricter for DMA hardware.
This article explains why user buffers require special handling, what can go wrong when they are used directly, and what mechanisms Linux provides to safely move data between user space and the kernel or a hardware device.
2.
The Problem: User Space Lives in Virtual Memory
2.1 Pages Can Be Swapped Out
A user space virtual address does not guarantee that the corresponding physical page is in RAM at any given moment. The kernel can decide to swap a page out to disk at any time to free up physical memory for other processes. When that happens, the virtual address still exists in the process's page table, but the page table entry is marked not present. The physical page is gone until a page fault brings it back.
If the kernel or a DMA controller tried to access a user buffer that has been swapped out, one of two things would happen. Either the system would stall waiting for the page to be swapped back in, which DMA hardware cannot do, or the access would fail outright. DMA hardware has no page fault handler. It cannot wait. It needs physical memory that is guaranteed to stay in RAM for the entire duration of the transfer.
2.2 Virtual Addresses May Not Be Backed by Physical Memory
Beyond swapping, a user space virtual address may not be backed by any physical page at all. When a process calls malloc() , the kernel creates a virtual memory area describing the allocation but does not immediately allocate physical pages. Those pages only come into existence when the process first touches them, through the demand paging mechanism.
A user space pointer to freshly allocated memory that has never been written to may have no physical page behind it. If the kernel received that pointer and tried to access the memory directly, it would trigger a page fault in kernel context. Depending on the kernel configuration and the access pattern, this can be handled or it can cause a kernel oops. Either way it is not safe to assume the pointer is backed by physical memory.
Fig
1. User buffer states: backed and present, backed but swapped, or not
yet backed by any physical page.
Struggling to implement this for a professional project?
If you need to master full-scale firmware architecture, security, and build automation, join our 1-D or 4-Day Live Implemnetation Workshops. I'll show you the exact direct path to production-ready firmware without the trial and error.
3. Kernel Access to User Buffers
3.1 Why the Kernel Cannot Directly Use User Pointers
The kernel runs in a privileged mode with its own virtual address space. On a 64-bit Linux system, the kernel occupies the upper half of the virtual address space and user processes occupy the lower half. When the kernel is executing a system call on behalf of a process, it can technically access the process's virtual addresses, but it must not do so directly through a raw pointer dereference.
The reason is access safety. A user space program can pass any address it wants to a system call, including addresses that are invalid, unmapped, or point to kernel memory itself. If the kernel dereferenced a user pointer directly without validation, a malicious or buggy application could cause the kernel to read or write arbitrary memory, crash the system, or leak sensitive data.
3.2 copy_from_user and copy_to_user
Linux provides two functions for safely moving data across the user-kernel boundary: copy_from_user() to read from a user buffer into kernel memory, and copy_to_user() to write from kernel memory into a user buffer.
/* reading from user space into kernel buffer */ if (copy_from_user(kernel_buf, user_ptr, size)) return -EFAULT;
/* writing from kernel buffer into user space */ if (copy_to_user(user_ptr, kernel_buf, size)) return -EFAULT; |
These functions do several things that a raw pointer dereference does not. They validate that the user address range is actually within user space. They handle page faults that occur during the copy, bringing swapped pages back from disk if needed. They return a non-zero value if any bytes could not be copied, allowing the kernel to return EFAULT to the caller cleanly.
The copy also has a cost. It means the data lives in two places: the user buffer and the kernel buffer. For small transfers this is acceptable. For large or frequent transfers it becomes a bottleneck, which is why zero-copy mechanisms exist.
Fig 2. copy_from_user and copy_to_user safely bridge the user-kernel boundary with validation and fault handling.
4.
DMA and User Buffers
4.1 Why DMA Needs Physical Addresses
DMA, Direct Memory Access, allows a hardware device to read and write system memory without CPU involvement. The device is programmed with a DMA address and a transfer length, then accesses memory autonomously over the system bus.
The critical detail is that DMA operates on bus-visible addresses, not virtual addresses. These DMA addresses are provided by the kernel and may or may not be the same as physical addresses depending on the system configuration. On systems with an IOMMU or bounce buffering, the DMA address is a translated view of memory specifically for the device.
Unlike the CPU, DMA hardware does not use the process page tables and cannot interpret virtual addresses. It relies entirely on the kernel to supply a valid DMA address that remains usable for the duration of the transfer.
4.2 Why User Buffers Are Not DMA Ready by Default
A user space buffer fails the requirements for DMA in two ways.
First, it is described by a virtual address, not a DMA address. Even if the kernel resolves that virtual address to a physical page at one moment, that mapping is not inherently stable or usable by a device.
Second, user space pages are pageable and movable. The kernel may migrate, reclaim, or swap them out at any time. A DMA transfer requires that the underlying pages remain resident and stable for the entire duration of the operation.
DMA hardware cannot handle page faults or memory migration. It assumes the address it was given remains valid. If the underlying memory changes during a transfer, the result is silent memory corruption.
4.3 Pinning Pages with pin_user_pages
The solution is to pin the user pages in memory for the duration of the DMA transfer. Pinning ensures that the pages remain resident and are not migrated or reclaimed while the device is accessing them.
Modern Linux kernels provide pin_user_pages() for this purpose. This API walks the process page tables, resolves the virtual addresses to struct page objects, and pins those pages for long-term use such as DMA.
However, pinning alone is not sufficient. Devices cannot safely use physical addresses directly. The driver must map the pinned pages into DMA addresses using the DMA API.
struct page *pages[NUM_PAGES]; long pinned;
/* pin user pages in RAM */ pinned = pin_user_pages(user_addr, NUM_PAGES, FOLL_WRITE, pages, NULL); if (pinned < 0) return pinned;
/* map first page for DMA */ dma_addr_t dma = dma_map_page(dev, pages[0], 0, PAGE_SIZE, DMA_TO_DEVICE); if (dma_mapping_error(dev, dma)) { /* unpin pages before returning */ for (int i = 0; i < pinned; i++) put_page(pages[i]); return -EIO; }
/* ... program DMA using dma address ... */
/* unmap after DMA completes */ dma_unmap_page(dev, dma, PAGE_SIZE, DMA_TO_DEVICE);
/* release pinned pages */ for (int i = 0; i < pinned; i++) put_page(pages[i]);
|
While the pages are pinned, the kernel guarantees they will not move. The DMA hardware can safely access the physical addresses for the full duration of the transfer. When the driver calls put_page() on each page, the pin is released and the kernel is free to manage those pages normally again.
Fig
3. pin_user_pages pins user pages in RAM, giving the DMA controller
stable physical addresses for the transfer.
5. Cache Coherency: The Hidden Problem with DMA
Pinning pages and mapping them for DMA solves the address stability problem, but introduces another: cache coherency.
The CPU and the DMA device access memory independently, and the CPU cache sits between the CPU and main memory. Without proper synchronization, the CPU and device can observe different values for the same memory region.
After pages are mapped for DMA using the DMA API, explicit synchronization may be required
/* before device reads from memory */ dma_sync_single_for_device(dev, dma_addr, size, DMA_TO_DEVICE);
/* after device writes to memory */ dma_sync_single_for_cpu(dev, dma_addr, size, DMA_FROM_DEVICE); |
This
is why dma_alloc_coherent() exists. It provides memory where CPU and
device views are automatically kept consistent, either through
hardware cache snooping or by using uncached mappings. The tradeoff
is slower CPU access, so coherent memory is typically used for
control structures rather than large data buffers.
6. The Right Way to Pass Buffers Between Userspace and Kernel
The correct approach depends on what the kernel needs to do with the buffer.
If the kernel needs to read or write a small amount of user data as part of a system call, copy_from_user() and copy_to_user() are the right tool. They are safe, handle all error cases, and are fast enough for small transfers. Most driver read() and write() implementations use them. Cache coherency is not an issue here because the CPU performs the copy and the data flows through the cache normally.
If the kernel needs to share a large buffer with a device for DMA, pin_user_pages() is the right tool. The driver pins the pages, programs the DMA transfer with physical addresses, calls dma_sync_single_for_device() before the transfer and dma_sync_single_for_cpu() after, then unpins the pages. This avoids the double-copy cost of copy_from_user for large transfers.
For
very high throughput scenarios where even pin_user_pages()
overhead is too much, the driver can allocate a DMA-ready buffer at
startup using dma_alloc_coherent() and map it into user space with
mmap() . Unlike pin_user_pages where the user owns the buffer and
the kernel pins it temporarily, here the kernel owns the buffer and
the user accesses it through a shared mapping. There is no pinning at
transfer time and no explicit cache sync needed because the coherent
allocation guarantees it at the hardware level.
7. Conclusion
User space buffers are virtual, pageable, and potentially unbacked. None of those properties are compatible with direct kernel access or DMA hardware. The kernel cannot trust a user pointer without validation, and DMA hardware cannot work with virtual addresses or pages that might disappear mid-transfer.
Linux provides the right tools for each situation: copy_from_user and copy_to_user for safe small transfers across the kernel boundary, pin_user_pages for pinning user pages for DMA, and dma_alloc_coherent with mmap for zero-copy high throughput paths. Beyond pinning, cache coherency is a second layer of correctness that must be handled explicitly .
