On January 2023, a subtle but highly impactful vulnerability was resolved in the Linux kernel’s device core subsystem. Assigned CVE-2022-49371, this bug could result in a kernel deadlock when device drivers are being attached asynchronously. For administrators, developers, or researchers working with Linux-based systems, understanding this issue is crucial, both for system stability and for security trust.
In this post, we will break down what went wrong, examine the code, walk through the potential for exploitation, and provide authoritative references and resources.
The Core Problem: Deadlock in __device_attach
Linux manages hardware devices using structures called "devices." When the kernel detects new hardware, it calls __device_attach to match these against registered drivers.
To prevent simultaneous operations (like two threads messing with the same device), the function locks the device before performing operations:
device_lock(dev);
// ... operations
device_unlock(dev);
However, to speed up things, Linux often *schedules* work asynchronously—a little like spinning off a background task—using async_schedule_dev. But here’s the rub: If asynchronous scheduling fails (because of memory limits or too many tasks), the kernel reverts to executing the function synchronously—while still holding the device lock.
That looks like this (simplified)
__device_attach
device_lock(dev)
async_schedule_dev(__device_attach_async_helper, dev)
async_schedule_node
async_schedule_node_domain(func)
entry = kzalloc(..., GFP_ATOMIC)
if (!entry || atomic_read(&entry_count) > MAX_WORK) {
// FALLBACK: Run func synchronously!
func;
} else {
queue async work
}
device_unlock(dev)
If the asynchronous function (__device_attach_async_helper) also tries to lock the device (which it does), you get an A-A deadlock—same thread tries to lock an already-locked mutex, and gets stuck forever.
Here’s a simplified illustration of the vulnerable section (prior to the fix)
int __device_attach(...)
{
device_lock(dev);
async_schedule_dev(__device_attach_async_helper, dev);
device_unlock(dev);
return ;
}
void __device_attach_async_helper(void *data, async_cookie_t cookie)
{
struct device *dev = data;
device_lock(dev); // <-- DEADLOCK if called synchronously from above!
// ... do the attach logic
device_unlock(dev);
}
If async_schedule_dev cannot schedule asynchronously, it directly calls __device_attach_async_helper while still holding the device lock. Since the helper also locks the device, it causes a classic deadlock.
Asynchronous operations are meant to allow complex or slow tasks to run *in the background*.
- If the system can’t start a background task (resource exhaustion, limit), Linux tries to just do the operation *immediately* instead.
The device lock, used to protect access, is not released before this direct call.
- Thus, the code tries to acquire a lock it already owns — causing a deadlock, locking up the kernel.
While this is not a code execution bug, the results are severe
- Kernel Hang: Remote or local attackers with sufficient privileges (or even buggy kernel modules) can cause the system to hang indefinitely by triggering enough device attaches under constrained conditions (e.g., simulating out-of-memory or work queue exhaustion).
- Denial-of-Service: Malicious actors could exploit this to force the system into an unrecoverable state, effectively taking the machine offline.
Reserve or exhaust kernel memory to force kzalloc to fail in async_schedule_node_domain.
2. Initiate device attachment (e.g., by repeatedly adding/removing hotpluggable devices).
The Patch: Moving Asynchronous Scheduling Outside the Lock
The solution is elegantly simple: Move the call to async_schedule_dev outside the lock.
Here’s what the patch effectively does
int __device_attach(...)
{
// Schedule the async helper *before* locking
async_schedule_dev(__device_attach_async_helper, dev);
device_lock(dev);
// ... other logic, now safe from deadlock
device_unlock(dev);
return ;
}
This way, if scheduling fails, and the fallback "synchronous" function is called, it doesn't happen while holding the device lock, so there's no risk of a deadlock.
Kernel Patch Discussion
CVE Details
- CVE-2022-49371 at CVE.org
- NVD Entry
Linux Kernel Source
How to patch
- Upgrade to a kernel version containing the fix (usually 6.2+ or your distributor’s patched release).
- Check your distributor’s security advisories. For example, Ubuntu, Red Hat, etc.
Workarounds
- None practical, unless you can guarantee device attach events will never coincide with async resource failures (impossible in general use).
Conclusion
CVE-2022-49371 reminds us that concurrency bugs can lurk in even the most mature codebases. While not directly leading to privilege escalation, a deadlock in the driver core means system-wide hangs — nothing short of a denial of service.
Takeaway:
If you maintain or run Linux systems, upgrade your kernel to a version with this fix. If you're developing kernel code, always be wary of locks and synchronous/asynchronous execution patterns — the order can be everything.
Timeline
Published on: 02/26/2025 07:01:13 UTC
Last modified on: 04/14/2025 20:43:02 UTC