CVE-2018-9387 - Heap Overflow in mnh-sm.c Explained — How a Simple Integer Overflow Led to Local Privilege Escalation

In 2018, a critical vulnerability known as CVE-2018-9387 was discovered in multiple builds of Android. The issue was found in the file mnh-sm.c, a part of Qualcomm's multimedia driver stack. This bug allows local users to escalate their privileges without any user interaction, thanks to a classic case of integer overflow leading to a heap overflow.

Let's break down how the bug works, why it's dangerous, look at the original code, and see how attackers could exploit it.

Where Does the Vulnerability Happen?

The file mnh-sm.c is part of Qualcomm's code used in various devices running Android. The vulnerable functions perform memory allocations based on sizes passed by a local client—the kernel trusts this user input without proper checks.

The Root Cause: Integer Overflow Leads to Heap Overflow

The problem with integer overflows is that if a calculation "wraps around" after exceeding its maximum value, the resulting value can be much smaller than intended. When this happens before a memory allocation, the system can allocate too little memory, but then write more data than there is space for—a classic heap overflow.

Here's a simplified (but similar) example of the faulty logic

int count = user_provided_nr_elements;
size_t item_size = sizeof(struct some_struct);

size_t total_size = count * item_size;
void *buffer = kmalloc(total_size, GFP_KERNEL);

// Later...
for (int i = ; i < count; i++) {
    memcpy(buffer + i * item_size, from_user_buffer + i * item_size, item_size);
}

What's Wrong?

If count is very large, the multiplication (count * item_size) can wrap around and result in a small total_size. kmalloc() allocates less memory than needed, but the code still writes as if it allocated enough (count items). The memory is overrun, which is a heap overflow.

The Vulnerable Code in mnh-sm.c

The original AOSP commit is not directly available, but similar patterns can be found in the code that was patched.

Here’s a *simplified* excerpt inspired by the real vulnerable pattern

int mnh_sm_process_request(struct sm_request *req) {
    int nr_items = req->num_items;  // User input, untrusted!
    size_t sz = nr_items * sizeof(struct mnh_data);

    struct mnh_data *buf = kmalloc(sz, GFP_KERNEL);
    if (!buf)
        return -ENOMEM;

    for (int i = ; i < nr_items; i++) {
        // Attacker can trigger buffer overflow here
        copy_from_user(&buf[i], user_ptr + i, sizeof(struct mnh_data));
    }
}

No input validation is performed. If the attacker sets nr_items to a huge number, sz becomes small because of integer overflow, but for loop still writes as if there was enough memory.

Attack Scenario

1. Local attacker (who can run code on the device) crafts a request to the vulnerable driver, passing a very large num_items.
2. The kernel allocates a very small buffer (kmalloc(sz)), but the copy loop writes a massive amount of data, overflowing into nearby memory.
3. By shaping the data and heap layout, attacker can overwrite sensitive data structures in the kernel heap—potentially gaining execution control or escalating privileges.

Proof of Concept (PoC) Outline

*Note: For safety, the following code does not exploit a real target, but shows the basic logic.*

#include <stdio.h>
#include <stdlib.h>

int main() {
    int num_items = x40000000; // Large number, causes integer overflow
    size_t sz = num_items * sizeof(int);

    printf("Allocating %zu bytes for %d items\n", sz, num_items);
    int *buf = malloc(sz);

    if (!buf) {
        perror("malloc failed");
        return 1;
    }

    // Overrun the buffer (the same pattern as in kernel driver)
    for (int i = ; i < num_items; i++) {
        buf[i] = i; // This overruns heap; in kernel, could smash structures
    }

    free(buf);
    return ;
}

In the real kernel, this could overwrite management structures, function pointers, or credentials—leading to root access.

The patch simply adds checks before doing the dangerous calculation

if (nr_items > MAX_SAFE_NR_ITEMS)
    return -EINVAL;

size_t sz;
if (check_add_overflow(nr_items, sizeof(struct mnh_data), &sz))
    return -EINVAL;

Or using a safer allocation helper (kernel API)

struct mnh_data *buf = kmalloc_array(nr_items, sizeof(struct mnh_data), GFP_KERNEL);

These helpers return NULL safely if the multiplication would overflow.

More Reading

- National Vulnerability Database Entry for CVE-2018-9387
- Qualcomm Security Bulletin
- Integer Overflows in Kernel Allocations, LWN.net
- kmalloc_array() in Linux Kernel Documentation

Conclusion

CVE-2018-9387 is a textbook example of why unchecked arithmetic in memory allocation can be so dangerous—especially in the kernel. A small programming oversight allowed any local app on a vulnerable device to potentially gain root, without the user clicking a thing.

The fix was simple: always check arithmetic for overflows before allocating memory. Modern kernels provide helpers to catch these bugs—use them!

If you're working with kernel or driver code, remember this bug. Integer overflows are quiet, but their consequences can be catastrophic.


*If you want to know more about this vulnerability or how integer overflows can lead to security disasters, feel free to ask!*

Timeline

Published on: 01/18/2025 00:15:23 UTC
Last modified on: 03/14/2025 16:15:26 UTC