Skip to content

[Bug] OOB write in OpenAMP virtual UART RX ring buffer on stm32mp157a-st-ev1 #11299

@XueDugu

Description

@XueDugu

RT-Thread Version

master at commit fe548e1505

Hardware Type/Architectures

STM32MP157A-ST-EV1 BSP with BSP_USING_OPENAMP enabled.

Develop Toolchain

GCC

Describe the bug

There is a reachable memory-safety issue in the OpenAMP virtual UART receive path of the stm32mp157a-st-ev1 BSP.

VIRT_UART_read_cb() receives rpmsg data from the OpenAMP peer and forwards the externally controlled payload pointer and length into the registered RX callback:

huart->pRxBuffPtr = data;
huart->RxXferSize = len;
huart->RxCpltCallback(huart);

In this BSP, the registered callback is VIRT_UART0_RxCpltCallback() in drv_openamp.c. That callback copies the received payload into a 256-byte ring buffer, but the bounds logic is incorrect:

  1. It checks only if (count < size) once before the copy loop
  2. It does not verify that count + rx_size <= size
  3. It does not wrap offset during the copy loop

As a result, if the ring buffer already contains 255 bytes and a new 2-byte message arrives, the callback writes:

  • The first byte to buf[255]
  • The second byte to buf[256]

This is an out-of-bounds write.

The bug is not limited to the minimal 2-byte case. A larger second message increases the overwrite extent further because the loop continues writing past the end of the 256-byte receive buffer.

In addition, the read path in _read() also contains a wrap bug: it uses if (offset > rbsize) instead of if (offset >= rbsize). Once the ring-buffer state becomes invalid, this can also cause an out-of-bounds read on subsequent reads.

This issue is reachable in the current BSP integration. The input channel is concrete:

  • The BSP is configured with LINUX_RPROC_MASTER
  • CM4 initializes OpenAMP as RPMSG_REMOTE
  • The virtual UART endpoint name is rpmsg-tty-channel
  • The repository already includes tools/rt-thread-shell.py, which writes to /dev/ttyRPMSG0

Therefore, an attacker who can control the OpenAMP peer, or who can send controlled input through the CA7/Linux side into /dev/ttyRPMSG0 or the corresponding rpmsg endpoint, can trigger this bug.


Fix Suggestion

A safe fix should address both the RX callback and the read path.

Suggested changes:

  1. In VIRT_UART0_RxCpltCallback(), compute available space before copying:
    • available = size - count
    • If available == 0, drop the message or return an error
  2. Clamp rx_size to the available space before entering the loop
  3. Wrap offset on each iteration, not only once before the loop
  4. Ensure device->serial.rbuf_count never exceeds device->serial.rbuf_size
  5. In _read(), change the wrap condition from if (offset > rbsize) to if (offset >= rbsize)
  6. Prefer using a common ring-buffer helper instead of open-coded wrap logic

Pseudo-fix direction:

available = size - count;
rx_size = MIN(rx_size, available);

for (i = 0; i < rx_size; i++)
{
    if (offset >= size)
        offset = 0;
    buf[offset++] = huart->pRxBuffPtr[i];
    count++;
}

Exploitation Idea

Precondition: the attacker can control the OpenAMP peer, i.e. the CA7/Linux side can send data to /dev/ttyRPMSG0 or the corresponding rpmsg endpoint.

Minimal PoC idea:

  1. Send 255 bytes from the Linux side to make the CM4-side receive ring buffer nearly full.
  2. Send one more message with length 2.
  3. The second message triggers the out-of-bounds write in VIRT_UART0_RxCpltCallback().

High-level example from the Linux side:

  • Open /dev/ttyRPMSG0
  • Write 255 controlled bytes
  • Then write 2 more controlled bytes

A simple proof-of-concept can be implemented with a short userspace program or script that performs two writes in sequence.

For a more stable reproduction:

  • Make sure the CM4 side is not draining the openamp receive buffer too quickly
  • For example, avoid using openamp as the active console during reproduction, or pause the consumer if applicable

Observation ideas:

  • Monitor for CM4 fault / crash / unexpected behavior
  • Inspect adjacent memory corruption effects after the second write
  • After corrupting the ring state, trigger reads from the openamp device to exercise the buggy _read() wrap logic and observe potential OOB read behavior

Other additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions