Use the ioctl encoding helpers
Linux provides _IO, _IOR, _IOW, and _IOWR to encode command direction, type, number, and data size. Define commands in a shared UAPI header and avoid hand-crafted magic numbers.
Design structures for evolution
Prefer fixed-width types such as __u32 and __u64 in userspace-facing structures. Avoid raw pointers and architecture-dependent types where possible. Include a size or version field when a structure may need extension, and reserve fields with explicit expectations.
Validate every request
- Reject unknown commands with
-ENOTTY. - Validate ranges, flags, and structure versions.
- Use
copy_from_user()andcopy_to_user()correctly. - Do not trust lengths or pointers supplied by userspace.
- Protect shared device state with suitable synchronization.
Compat and long-term support
32-bit applications on a 64-bit kernel can expose layout differences, especially when structures contain pointers or long. Design stable UAPI structures and consider compat handling where required. Once released, command numbers and structure meanings should not be casually repurposed.
Testing checklist
Test valid commands, unknown commands, bad pointers, truncated structures, out-of-range values, concurrent callers, and repeated open/close. Test both native and compat userspace if your product supports them.
Use kernel UAPI conventions and review the target kernel's ioctl documentation before freezing an interface.
Final takeaway
Low-level engineering is about understanding the contract between hardware, firmware, and the operating system. Start with the specification, form a testable hypothesis, collect evidence, and validate behavior on the target platform.