In 2021, Linux kernel developers identified and fixed a critical bug in the hso driver that could crash your system or open doors for possible exploits. Cataloged as CVE-2021-46904, this vulnerability was rooted in how the driver managed serial device unregistration — specifically, a classic double-unregistration issue due to bad minor number handling. In this article, we'll break down what went wrong, show you code snippets, and walk through the fix, all in straightforward language. You'll also find direct links to the original sources and technical details useful for researchers and kernel enthusiasts alike.

The Vulnerability: What Was Broken?

The hso driver in Linux manages USB high-speed Option (HSO) devices, which can expose multiple serial (tty) interfaces. When a serial (tty) device is unplugged or shut down, the driver unregisters it using its "minor number" (an ID for tty devices).

The trouble started in the way the function get_free_serial_index() worked. This function looked for an unused minor number but didn't assign it immediately. Between finding a free number and actually marking it as taken, there was a window where two concurrent calls could grab the same number. This meant two devices could share the same ID and both would try to unregister the same device. The first unregistration would work, but the second would result in a null pointer dereference — a crash or worse.

The Bug, Simplified

- get_free_serial_index() found a free minor number, but didn’t "reserve" or "mark" it immediately.

The Exploit: Why It Matters

Why is this a big deal? A null pointer dereference can be more than just a denial-of-service (system crash). If attackers can control the timing (by plugging/unplugging devices, or via crafted drivers), they might be able to force execution paths leading to privilege escalation or code execution, depending on what and where the null pointer dereference occurs.

Here’s a simplified version of the problem code

// Old: get_free_serial_index does not reserve the minor immediately!
int get_free_serial_index(void) {
    for (int i = ; i < HSO_MAX_PORTS; i++) {
        if (serial_table[i] == NULL)
            return i; // Found a "free" one, but don't mark as taken
    }
    return -1;
}

Danger: Multiple calls can return the same "free" index!

After: The Fixed Approach

The developers renamed the function to obtain_minor() and made it assign (reserve) the number right away. Also, freeing is now done via release_minor():

// New: obtain_minor assigns it immediately
int obtain_minor(hso_serial *hso_ser) {
    for (int i = ; i < HSO_MAX_PORTS; i++) {
        if (serial_table[i] == NULL) {
            serial_table[i] = hso_ser; // Reserve the minor instantly
            return i;
        }
    }
    return -1;
}

// Release the minor when done
void release_minor(hso_serial *hso_ser) {
    for (int i = ; i < HSO_MAX_PORTS; i++) {
        if (serial_table[i] == hso_ser) {
            serial_table[i] = NULL; // Free up for next user
            break;
        }
    }
}

Safety added: Each obtain_minor() has a matching release_minor(), so the table always reflects reality.

Patching and Mitigation

Linux distributions started patching this vulnerability shortly after disclosure. If you maintain Linux devices using the hso module, make sure to update to a kernel including the following commit (March 2021):

- Mainline Patch Commit

Fixed in mainline: v5.12-rc6 and later

If you’re stuck on an older kernel, consider disabling the hso module if not needed.

- CVE-2021-46904 at NVD
- Linux Kernel Commit
- LKML Patch discussion
- Red Hat Bugzilla entry

Conclusion & Takeaways

The root cause of CVE-2021-46904 was a classic concurrency and race bug — two threads getting the same “free” resource. This underlines why resource reservation must be immediate whenever possible, especially in kernel code where parallel access is frequent.

What can you learn from this bug?

- Always reserve shared resources atomically (instantly, with no window between lookup and assignment).
- Use clear naming (obtain_minor/release_minor rather than generic get/set).

Add checks and balances: make sure every obtain has a release.

By learning from issues like CVE-2021-46904, we can all write safer, more robust drivers and system code.

Timeline

Published on: 02/26/2024 16:27:45 UTC
Last modified on: 04/17/2024 19:33:10 UTC