Crate linux_ioctl

Source
Expand description

ioctls for Linux APIs.

This library provides a convenient way to bind to Linux ioctls.

It is intended to help with writing wrappers around driver functionality, and tries to mirror the syntax you’ll find in C headers closely.

§Example

Let’s wrap V4L2’s QUERYCAP ioctl.

From linux/videodev2.h:

struct v4l2_capability {
	__u8	driver[16];
	__u8	card[32];
	__u8	bus_info[32];
	__u32   version;
	__u32	capabilities;
	__u32	device_caps;
	__u32	reserved[3];
};
// ...
#define VIDIOC_QUERYCAP		 _IOR('V',  0, struct v4l2_capability)
use std::mem::MaybeUninit;
use linux_ioctl::*;

#[repr(C)]
struct Capability {
    driver: [u8; 16],
    card: [u8; 32],
    bus_info: [u8; 32],
    version: u32,
    capabilities: u32,
    device_caps: u32,
    reserved: [u32; 3],
}

const VIDIOC_QUERYCAP: Ioctl<*mut Capability> = _IOR(b'V', 0);

// Use as follows:

let capability = unsafe {
    let mut capability = MaybeUninit::uninit();
    VIDIOC_QUERYCAP.ioctl(&fd, capability.as_mut_ptr())?;
    capability.assume_init()
};

§Portability

Despite being about Linux APIs, and following the Linux convention for declaring ioctl codes, this library should also work on other operating systems that implement a Linux-comparible ioctl-based API.

For example, FreeBSD implements a variety of compatible interfaces like evdev and V4L2.

§Safety

To safely perform an ioctl, the actual behavior of the kernel-side has to match the behavior expected by userspace (which is encoded in the Ioctl type).

To accomplish this, it is necessary that the Ioctl was constructed correctly by the caller: the direction, type, number, and argument type size are used to build the ioctl request code, and the Rust type used as the ioctl argument has to match what the kernel expects. If the argument is a pointer the kernel will read from or write to, the data behind the pointer also has to be valid, of course (ioctls are arbitrary functions, so the same care is needed as when binding to an arbitrary C function).

However, this is not, strictly speaking, sufficient to ensure safety: several drivers and subsystems share the same ioctl “type” value, which may lead to an ioctl request code that is interpreted differently, depending on which driver receives the request. Since the ioctl request code encodes the size of the argument type, this operation is unlikely to cause a fault when accessing memory, since both argument types have the same size, so the ioctl syscall may complete successfully instead of returning EFAULT.

The result of this situation is that a type intended for data from one driver now has data from an entirely unrelated driver in it, which will likely cause UB, either because a validity invariant was violated by the data written to the structure, or because userspace will trust the kernel to only write valid data (including pointers) to the structure.

While it may technically be possible to tell which driver owns a given device file descriptor by crawling /sys or querying udev, in practice this situation is deemed “sufficiently unlikely to cause problems” and programs don’t bother with this.

One way to rule out this issue is to prevent arbitrary file descriptors from making their way to the ioctl, and to ensure that only files that match the driver’s naming convention are used for these ioctls. For example, an evdev wrapper could refuse to operate on files outside of /dev/input, and a KVM API could always open /dev/kvm without offering a safe API to act on a different device file.

For more information, you can look at the list of ioctl groups here: https://www.kernel.org/doc/html/latest/userspace-api/ioctl/ioctl-number.html

TL;DR: don’t worry about it kitten :)

Structs§

Dir
Direction of an Ioctl.
Ioctl
An ioctl.
NoArgs
Indicates that an Ioctl does not take any arguments.

Constants§

_IOC_NONE
Indicates that an ioctl neither reads nor writes data through its argument.
_IOC_READ
Indicates that an ioctl reads data from the kernel through its pointer argument.
_IOC_READ_WRITE
Indicates that an ioctl both reads and writes data through its pointer argument.
_IOC_WRITE
Indicates that an ioctl writes data to the kernel through its pointer argument.

Functions§

_IO
Creates an Ioctl that doesn’t read or write any userspace data.
_IOC
Manually constructs an Ioctl from its components.
_IOR
Creates an Ioctl that reads data of type T from the kernel.
_IOW
Creates an Ioctl that writes data of type T to the kernel.
_IOWR
Creates an Ioctl that writes and reads data of type T.