GPIO Bounds Checking: Fixing an Out-of-Bounds Access in the py32ioexp Linux Driver
Introduction
Linux kernel drivers are the unsung gatekeepers of hardware security. They sit at the boundary between user space and hardware, translating high-level requests into low-level register operations. When a driver forgets to validate its inputs, that boundary becomes a vulnerability — and in the kernel, vulnerabilities can mean memory corruption, privilege escalation, or system crashes.
This post walks through a high-severity out-of-bounds access vulnerability found and fixed in py32ioexp, a Linux GPIO expander driver. If you write kernel drivers, work with embedded Linux, or simply want to understand how a missing bounds check becomes a real security problem, read on.
What Is This Vulnerability?
The vulnerability lives in modules/py32ioexp-1.0/py32ioexp.c, specifically in the py32io_gpio_direction_input() function. This function is part of the Linux gpio_chip framework and is called whenever a user or kernel subsystem wants to configure a GPIO pin as an input.
The function accepts an unsigned offset parameter — an index representing which GPIO pin to configure. The problem? It never checks whether that offset is actually a valid pin number for the chip.
Every gpio_chip structure in the Linux kernel has an ngpio field that declares exactly how many GPIO lines the chip exposes. If a caller passes an offset of, say, 255 on a chip that only has 16 pins, the driver should reject it. Instead, the unpatched code happily uses that out-of-range value to index into internal arrays or calculate I2C register addresses — potentially reading or writing memory it was never supposed to touch.
The Vulnerability Explained
Technical Details
Here is the vulnerable function before the fix:
static int py32io_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
{
return py32io_gpio_set_direction(chip, offset, GPIO_LINE_DIRECTION_IN);
}
That's it. No validation. The offset value flows directly into py32io_gpio_set_direction(), which uses it to compute array indices or I2C register offsets. If offset exceeds the valid range (0 to chip->ngpio - 1), the result is undefined behavior — typically an out-of-bounds read or write.
How Could It Be Exploited?
On a Linux system, GPIO chips are exposed to userspace through two interfaces:
- Character device:
/dev/gpiochipN(accessed viaioctl) - Sysfs interface:
/sys/class/gpio/
A local user with read/write access to either of these interfaces can craft a request that specifies an out-of-range GPIO pin number. The kernel GPIO core is supposed to validate offsets before dispatching to driver callbacks, but defense-in-depth demands that drivers never trust their inputs — even inputs from the kernel framework itself.
Real-World Impact
Depending on how py32io_gpio_set_direction() uses the offset internally, an attacker could:
- Out-of-bounds read: Access kernel memory beyond the intended array, potentially leaking sensitive data (other driver state, kernel addresses useful for bypassing KASLR).
- Out-of-bounds write: Corrupt adjacent kernel memory structures, potentially leading to a kernel panic or, in a worst-case scenario, privilege escalation.
- I2C register corruption: On hardware, writing to an unintended I2C register address could destabilize or damage connected peripherals.
Attack Scenario
Imagine a small embedded Linux board using a PY32-based I2C GPIO expander with 16 pins (ngpio = 16). A local attacker with access to /dev/gpiochip1 runs:
# Request GPIO line 200 as input on a 16-pin chip
gpioget --mode=input /dev/gpiochip1 200
The kernel GPIO core may or may not catch this before it reaches the driver. If it reaches py32io_gpio_direction_input(), the unvalidated offset 200 is passed straight through. The driver computes a register address or array index based on 200, accesses memory it shouldn't, and the results are unpredictable — from a silent data corruption to a kernel oops.
The Fix
The fix is elegantly simple — a two-line bounds check added at the very top of the function:
Before
static int py32io_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
{
return py32io_gpio_set_direction(chip, offset, GPIO_LINE_DIRECTION_IN);
}
After
static int py32io_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
{
if (offset >= chip->ngpio)
return -EINVAL;
return py32io_gpio_set_direction(chip, offset, GPIO_LINE_DIRECTION_IN);
}
How Does It Solve the Problem?
The check offset >= chip->ngpio catches every invalid pin number:
chip->ngpiois set during driver initialization and reflects the actual number of GPIO lines the hardware exposes.- If
offsetis equal to or greater thanngpio, the function immediately returns-EINVAL— the standard Linux kernel error code for an invalid argument. - No memory is accessed. No I2C transaction is initiated. The invalid request is rejected cleanly and safely.
This is a textbook example of input validation at the trust boundary: validate inputs as early as possible, before they can cause harm downstream.
The same pattern should ideally be applied to all sibling functions in the driver — py32io_gpio_direction_output(), py32io_gpio_get(), py32io_gpio_set(), and any other function that accepts an offset parameter.
Conclusion
A missing bounds check is one of the oldest and most common vulnerabilities in systems programming — yet it remains one of the most impactful. In this case, a single missing if statement in a Linux GPIO driver created a path for local users to trigger out-of-bounds memory access in the kernel.
The fix is two lines. The lesson is timeless: validate your inputs, especially at trust boundaries, and especially in the kernel.
Key takeaways:
- GPIO driver callbacks must validate
offsetagainstchip->ngpiobefore using it as an array index or register offset. - Out-of-bounds accesses in kernel drivers can lead to memory corruption, data leaks, or system instability.
- Defense in depth means validating inputs at every layer, even when you trust the caller.
- Static analysis and KASAN are your friends — use them during development to catch these issues before they reach production.
Security in embedded Linux isn't glamorous, but it's critical. The next time you write a driver callback, take two seconds to add that bounds check. Future you — and your users — will thank you.
This fix was identified and applied automatically by OrbisAI Security. Automated security scanning helps catch these subtle but serious issues before they reach production.