Skip to content
/linux-syscalls

File & I/O · Section 2

ioctl(2)

Catch-all device-control syscall: perform a driver-defined operation on a file descriptor.

Signature

#include <sys/ioctl.h>

int ioctl(int fd, unsigned long request, ... argp);
fd
Open file descriptor referring to a device, special file, or other ioctl-aware object.
request
Encoded request: macros from sys/ioctl.h or device-specific headers (e.g. <linux/kvm.h>) construct this with _IO/_IOR/_IOW/_IOWR.
argp
Variadic. Often a pointer to a struct; sometimes an integer passed by value; sometimes absent.

Description

ioctl() invokes a driver-specific operation on the object referred to by fd. The request argument is a magic constant encoding the target subsystem, an operation number, and the size and direction of the data in argp. The third argument is variadic — it may be absent, an int passed by value, or a pointer to a struct that the kernel reads, writes, or both. Hundreds of subsystems expose ioctl() interfaces: terminals (TCGETS, TIOCGWINSZ), block devices (BLKGETSIZE, BLKDISCARD), network interfaces (SIOCGIFADDR), KVM (KVM_RUN, KVM_CREATE_VCPU), perf (PERF_EVENT_IOC_ENABLE), file systems (FS_IOC_GETFLAGS), tun/tap, USB, DRM/GPU, and many more. Because every request code is effectively its own mini-syscall, ioctl() represents one of the largest attack surfaces in the Linux kernel.

Architecture mapping

ArchitectureNumberABIEntry point
x86 (i386)54i386sys_ioctl
x64 (x86_64)1664sys_ioctl
ARM64 (aarch64)29

Kernel history

Introduced in Linux 1.0.

  1. 1.0

    ioctl() has been part of Linux since 1.0 and traces back to V7 Unix. Its lack of structure makes it the catch-all interface for everything that does not fit POSIX read/write semantics.

  2. 2.6.36

    The .ioctl driver method was deprecated in favour of .unlocked_ioctl, which is called without the Big Kernel Lock — a major scalability change.

  3. 4.6

    The compat_ioctl path was reorganised so 32-bit-on-64-bit callers go through the same dispatch as native callers, eliminating a long list of duplicated translation code.

seccomp & containers

Docker default profile

Allowed

Podman default profile

Allowed

ioctl() is on the Docker / Podman default allow-lists because terminals, sockets, and many libraries depend on it. The real hardening lever is filtering by request value: most workloads only need TCGETS, TCSETS, TIOCGWINSZ, FIONBIO, FIONREAD — a couple of dozen codes out of thousands. With argument filtering you can allow that subset and deny everything else, which removes most of the historical kernel CVE surface (KVM, perf, drm, usbdev, tun/tap ioctls have all had remote-ish bugs). Pair with a /dev whitelist mount to make this airtight.

libseccomp

// Allow ioctl only for common terminal and FIONBIO/FIONREAD operations;
// block device-specific ioctls (KVM_*, TUN*, USBDEV*) by request value.
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl),
    1, SCMP_A1(SCMP_CMP_EQ, TCGETS));
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl),
    1, SCMP_A1(SCMP_CMP_EQ, FIONBIO));

strace example

$ strace -e ioctl tput cols
ioctl(0, TCGETS, {c_iflag=ICRNL|IXON, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, …}) = 0
ioctl(1, TIOCGWINSZ, {ws_row=24, ws_col=120, ws_xpixel=0, ws_ypixel=0}) = 0

strace decodes many common ioctl request codes (TCGETS, TIOCGWINSZ, BLKGETSIZE…) and unpacks the argument struct; unrecognised codes are printed as hex with the buffer size. Use -e trace=ioctl and -e ioctls=… to filter by specific codes.

Security & observability

ioctl() is one of the most CVE-rich Linux interfaces: dozens of subsystems each have their own request codes, and a bug in any of them is reachable from any process that can open the corresponding device. Notable malware uses: TUN/TAP ioctls (TUNSETIFF) to create stealth network interfaces; perf_event_open + PERF_EVENT_IOC_* for kernel-side tracing primitives; FS_IOC_SETFLAGS to set FS_IMMUTABLE_FL on dropped files (making them hard to remove for unprivileged responders). eBPF tracepoint sys_enter_ioctl captures fd and request; correlate fd to /proc/<pid>/fd to identify the target device. For incident response, an unusual ioctl request value on /dev/net/tun or /dev/kvm from a non-virtualisation workload is high-signal.

Errors

EBADF
fd is not a valid descriptor.
EFAULT
argp points outside the calling process's address space.
EINVAL
The request is recognised but the argument is invalid.
ENOTTY
fd is not associated with a character special device (classical errno), OR the request is not applicable to this device — used loosely to mean "this device does not implement this ioctl".

Related syscalls