Linux Kernel User Data Copying Guide- Free Linux Device Drivers Course

 

Linux Kernel User Data Copying
Character Device Drivers · Free Linux Kernel Development Course
Chapter 4 · Lecture 3
Kernel 6.x Updated
Beginner Friendly

Every character device driver eventually needs to move data between an application running in user space and the driver running in kernel space. This is exactly where copy_from_user() and copy_to_user() come in, and in this lecture of our free Linux kernel development course you will learn why a driver can never simply dereference a user space pointer directly, how these two functions safely move buffers across the user-kernel boundary, and how the lighter-weight put_user() and get_user() macros handle single values efficiently on modern Linux kernel 6.x systems.

free linux device drivers course
free linux kernel development course
free embedded linux course
copy_from_user copy_to_user
put_user get_user

What You Will Learn

Why direct pointer access between user space and kernel space is unsafe
copy_to_user() and copy_from_user() function signatures and return values
put_user() and get_user() macros for single variables
An original misc character driver using both APIs
Common bugs, security risks, and performance notes
Testing the driver from the command line

Prerequisites

Basic character device registration (cdev, file_operations)
A Linux 6.x kernel build environment with headers installed
Comfort compiling and inserting kernel modules with insmod / rmmod

Why User Space Pointers Cannot Be Dereferenced Directly

When an application calls read() or write() on your device file, it passes a pointer into its own virtual address space. That pointer is meaningless inside the kernel for two reasons. First, the kernel runs with its own page tables active during a syscall, so a raw user address may not even be mapped the same way. Second, and more importantly, user space is untrusted. An application could pass a bad pointer, a pointer into someone else’s memory, or a pointer designed to trick the driver into leaking kernel memory. Because of this, the kernel never lets a driver use memcpy() or a plain assignment on a user space address. Instead, every driver must go through the dedicated user-copy interface, and that is the whole reason copy_from_user() and copy_to_user() exist.

User Space vs Kernel Space Boundary
User Space
Application buffer
read()/write() call
Validation Layer
copy_to_user() / copy_from_user()
put_user() / get_user()
Kernel Space
Driver’s private buffer
file_operations callback

copy_to_user() and copy_from_user() API

These two functions handle bulk buffer transfers of any size and are the workhorses behind almost every read() and write() implementation:

Function Signatures
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);

The __user annotation is a sparse checker hint marking a pointer as belonging to user space, and it is also a signal to you as the driver author that this pointer must never be dereferenced directly. n is the byte count you intend to transfer. Both functions return the number of bytes that could NOT be copied — a return value of 0 means complete success, and any non-zero value means a partial or failed copy that your driver must treat as an error.

Function Direction On Partial Failure
copy_to_user() Kernel → User Copies only what fit; return value shows uncopied bytes
copy_from_user() User → Kernel Uncopied kernel bytes are zero-padded for safety

Original Example: Buffer Copy in a Misc Driver

Here is an original driver snippet, written for kernel 6.x, showing copy_from_user() and copy_to_user() inside a simple misc device’s write and read callbacks:

ep_userbuf: Kernel-Side Buffer Driver
#define EP_BUF_SIZE 128
static char ep_kbuf[EP_BUF_SIZE];
static size_t ep_kbuf_len;static ssize_t ep_userbuf_write(struct file *filp, const char __user *ubuf,
size_t count, loff_t *pos)
{
if (count > EP_BUF_SIZE)
count = EP_BUF_SIZE;if (copy_from_user(ep_kbuf, ubuf, count))
return -EFAULT;ep_kbuf_len = count;
pr_info(“ep_userbuf: stored %zu bytes from user space\n”, count);
return count;
}static ssize_t ep_userbuf_read(struct file *filp, char __user *ubuf,
size_t count, loff_t *pos)
{
if (*pos >= ep_kbuf_len)
return 0;if (count > ep_kbuf_len – *pos)
count = ep_kbuf_len – *pos;if (copy_to_user(ubuf, ep_kbuf + *pos, count))
return -EFAULT;*pos += count;
return count;
}

Notice the pattern: check the copy function’s return value, and if it is non-zero, return -EFAULT immediately. This single habit prevents an entire class of driver bugs where a partially copied buffer is silently treated as complete.

put_user() and get_user(): Copying Single Values

Copying a whole buffer through copy_to_user() for a single integer or character is wasteful. The kernel provides two macros, put_user() and get_user(), purpose-built for scalar types like char, int, and long:

Macro Usage
put_user(value, user_ptr); /* kernel -> user, returns 0 or -EFAULT */
get_user(kernel_var, user_ptr); /* user -> kernel, returns 0 or -EFAULT */

get_user() sets the destination kernel variable to 0 automatically if the copy fails, so you never end up operating on uninitialized data. Both macros type-check at compile time: the kernel variable and the dereferenced user pointer must be assignable to each other, which catches size mismatches long before they become runtime bugs.

Original Example: A Single-Value Status Register

ep_statusreg: Exposing One Integer via get_user/put_user

static int ep_status_value = 42;

static ssize_t ep_status_read(struct file *filp, char __user *ubuf,
size_t count, loff_t *pos)
{
if (*pos > 0 || count < sizeof(int))
return 0;

if (put_user(ep_status_value, (int __user *)ubuf))
return -EFAULT;

*pos += sizeof(int);
return sizeof(int);
}

static ssize_t ep_status_write(struct file *filp, const char __user *ubuf,
size_t count, loff_t *pos)
{
int new_value;

if (count < sizeof(int))
return -EINVAL;

if (get_user(new_value, (int __user *)ubuf))
return -EFAULT;

ep_status_value = new_value;
return sizeof(int);
}

Common Mistakes When Using copy_from_user() and copy_to_user()

Mistake Why It Breaks
Ignoring the return value A partial copy is treated as a full success, corrupting data silently
Not clamping count against buffer size Leads to kernel buffer overflow before the copy even happens
Calling copy_from_user() with a kernel pointer as the source Undefined behavior; the API assumes the source is a real user address
Reusing get_user()’s output before checking its return code On failure the variable is zeroed, which can mask the real error

Security Considerations for copy_to_user copy_from_user Code

Because copy_to_user() can leak kernel memory to an application if misused, always zero or fully initialize a kernel buffer before copying it out, and never copy more bytes than you have actually written. Modern kernels also enable CONFIG_HARDENED_USERCOPY, which performs additional bounds checking against the target slab object at copy time and panics on an out-of-bounds copy instead of silently corrupting memory — another reason to always pass the correct, clamped length rather than trusting an unchecked count field.

Performance Notes

put_user() and get_user() compile down to a single inline access with fault handling, so they are noticeably cheaper than copy_to_user()/copy_from_user() for one scalar value. For anything larger than a few bytes, however, the bulk functions are the right tool since they are optimized internally for larger transfers.

Testing the Driver From the Command Line

Build, Load, and Exercise the Driver
$ make
$ sudo insmod ep_userbuf.ko
$ echo -n “hello kernel” | sudo tee /dev/ep_userbuf
$ sudo cat /dev/ep_userbuf
hello kernel
$ dmesg | tail -n 3
[ 1234.567890] ep_userbuf: stored 12 bytes from user space
$ sudo rmmod ep_userbuf

Best Practices Checklist

Always clamp the requested count to your kernel buffer size first
Always check the return value of copy_to_user / copy_from_user / put_user / get_user
Use put_user/get_user only for single scalar values, never structures or arrays
Never store or dereference a __user pointer outside the syscall context that gave it to you
Zero-initialize kernel buffers before copying them out to avoid leaking stale memory

Real-World Use Cases

Almost every character driver you will ever write depends on this API: sensor drivers returning readings to a user application, EEPROM or NVRAM drivers exposing raw storage, configuration interfaces that accept tuning parameters, and even simple logging drivers all rely on copy_to_user() and copy_from_user() to move data safely. Anytime your driver implements read() or write() on a device node, you will reach for this exact pair of functions.

Summary and Key Takeaways

User space pointers can never be dereferenced directly inside the kernel
copy_to_user() and copy_from_user() safely move buffers of any size across that boundary
put_user() and get_user() are lightweight macros for single scalar values
A non-zero return value always means a partial or failed copy that must be handled
CONFIG_HARDENED_USERCOPY adds kernel-side bounds checking on modern kernels

Conclusion

Understanding copy_to_user(), copy_from_user(), put_user(), and get_user() is one of the most important steps in this free Linux device drivers course, because every character driver that talks to user space applications depends on getting this boundary right. Get comfortable clamping lengths, checking return values, and choosing the right function for the size of data you are moving, and you will avoid the majority of bugs that new kernel driver developers run into. In the next lecture of this free Linux kernel development course, we will use these exact functions inside a complete driver’s open() and release() methods.

Frequently Asked Questions

Why can’t a driver just use memcpy() on a user space pointer?

A user space pointer may be invalid, unmapped, or deliberately malicious. memcpy() performs no validation, while copy_to_user()/copy_from_user() include fault-handling logic that safely catches bad addresses instead of crashing the kernel.

What does a non-zero return value from copy_from_user() mean?

It reports how many bytes could NOT be copied. Any non-zero value should be treated as a failure, typically by returning -EFAULT from your file operation.

When should I use put_user() instead of copy_to_user()?

Use put_user()/get_user() only when transferring a single scalar value such as an int or char. For buffers, structures, or arrays, use copy_to_user()/copy_from_user() instead.

Does copy_from_user() zero out the kernel buffer on failure?

Yes. If the copy is incomplete, the remaining destination bytes in kernel memory are zero-padded so no stale or leftover kernel data is exposed.

Is access_ok() still required before calling copy_to_user()?

On modern Linux kernel 6.x, copy_to_user() and copy_from_user() perform the necessary address-range validation internally, so manually calling access_ok() beforehand is generally unnecessary in ordinary driver code.

What is CONFIG_HARDENED_USERCOPY?

It is a kernel hardening option that validates the size and bounds of a copy against the actual kernel object being copied to or from, causing the kernel to reject or panic on suspicious out-of-bounds copies rather than allow silent corruption.

Can put_user() and get_user() be used with structures?

No. They only work with scalar types that fit in a machine word such as char, short, int, or long. Structures and arrays must use copy_to_user() and copy_from_user().

Continue the Free Linux Kernel Development Course

Next up: implementing open() and release() for a real character device driver.

 

Leave a Reply

Your email address will not be published. Required fields are marked *