Skip to content

[Bug] graphic: negative framebuffer offset enables arbitrary kernel read/write in RT-Smart #11969

Open

Description

@XueDugu

RT-Thread Version

v5.3.0 through current master 08cad4ee9cda4ea30bc3dfab1d71318c1c50f749.
The vulnerable code was introduced by 5abecc1fd0c3 on 2025-12-12.
The current graphic.c blob is
14de5a6cda124e86c87991d298501cec42d9bf43.

Affected area

Device drivers / Graphic

Hardware-BSP vendor

Other: QEMU ramfb and Raspberry Pi Pico 2 W / RP2350

Architecture

ARM / AArch64

Board and hardware details

The RT-Smart user-to-kernel boundary was reproduced on
qemu-virt64-aarch64 with the stock QEMU ramfb device. The board-level
kernel API primitive was reproduced on a Raspberry Pi Pico 2 W (RP2350 ARM
Secure) over USB CDC.

Develop Toolchain

RT-Thread AArch64 musl GCC for the RT-Smart kernel and user ELF; QEMU
full-system execution. The Pico 2 W image uses Pico SDK 2.3.1 and ARM GNU GCC
10.3.1.

Describe the bug

_graphic_read() and _graphic_write() accept a signed device offset without
checking pos >= 0 or pos < framebuffer_len:

res = rt_min_t(rt_ssize_t, plane->framebuffer_len - pos, size);
if (res > 0)
{
    rt_memcpy(plane->framebuffer + pos, buffer, res);
}

RT-Smart pread64() and pwrite64() pass the offset through DFS to the
graphic device. The current rw_verify_area() rejects a negative position only
when count >= -pos. Therefore, count=8 with pos=-210456 is accepted.
The graphic driver then computes framebuffer + pos outside the framebuffer
and copies to or from that address.

An unprivileged process can:

  1. Open /dev/fb0.
  2. Obtain the framebuffer kernel address through
    RTGRAPHIC_CTRL_GET_INFO.
  3. Set pos = kernel_target - framebuffer.
  4. Use pread64() or pwrite64() to read or write an 8-byte kernel qword.

The code path is:

sys_pwrite64
  -> pwrite
  -> dfs_file_pwrite
  -> dfs_devfs_write
  -> rt_device_write
  -> _graphic_write
  -> memcpy(framebuffer + pos, ...)

_graphic_read() has the same missing offset validation.

Steps to reproduce

A. Pico 2 W board-level reproduction

This run uses a ready-to-flash RP2350 ARM Secure image (pico2_w, UF2 family
0xe48bff59, SHA-256
957a42259ff8d13767076f4ff8bc46dab2454e7c0b6065a4f550121a0ff94457).
The firmware registers a framebuffer through the official
rt_graphic_device_register() API and calls rt_device_write() with the
framebuffer base and a target in the same RAM:

  1. Flash the UF2 to a Pico 2 W.
  2. Open its USB CDC port at 115200 baud.
  3. Send gfx_poc.

Expected: the negative framebuffer offset is rejected.

Actual:

gfx_poc: framebuffer=0x20002bc8 target=0x20001bc8 delta=-4096
gfx_poc: target before=0x00000000
gfx_poc: write ret=4 target_after=0x11223344
gfx_poc: CONFIRMED negative framebuffer offset write

The Pico is a 32-bit target, so its rt_ubase_t value occupies four bytes.
This hardware run proves the official graphic driver and device-write path
accepts a negative offset on a real RP2350.

B. RT-Smart user-to-kernel trust-boundary reproduction

  1. Build bsp/qemu-virt64-aarch64 with RT_USING_GRAPHIC=y.
  2. Boot QEMU with the stock -device ramfb model and a root filesystem
    containing an unprivileged RT-Smart user ELF.
  3. Open /dev/fb0 and query RTGRAPHIC_CTRL_GET_INFO.
  4. Compute a negative offset that points to a chosen mapped kernel address.
  5. Call pwrite64() and pread64() with that offset.

Control with offset 0:

framebuffer=0xffff000000249000
delta=0
pread ret=8 old=0
pwrite ret=8
readback=0x1122334455667788
target restored; kernel alive

Attack targeting the graphic component's fbcon_map[0]:

framebuffer=0xffff000000249000
kernel_target=0xffff0000002159e8
delta=0xfffffffffffcc9e8
pread ret=8 old=0
pwrite ret=8
readback=0x1122334455667788
target restored; kernel alive

The PoC restores the target after verification.

Evidence

Three independent QEMU attack runs produced identical output. Their SHA-256 is:

87fbab834199e97b92a4c81e5187ae3e97937e4c95a7a2acd00c299c827af30a

GDB stopped in _graphic_write() with:

dev=0xffff000000248640 pos=-210456 buffer=0xffff000000475910 size=8

#0 _graphic_write
#1 rt_device_write
#2 dfs_devfs_write
#3 dfs_file_pwrite
#4 pwrite
#5 sys_pwrite64
#6 _SVC_Handler

A hardware watchpoint on fbcon_map showed:

Old value = 0
New value = 0x1122334455667788

The hardware reproduction is also linked from section A above. The RT-Smart
boundary proof uses 64-bit qwords on AArch64.

Impact

An unprivileged RT-Smart process can read and write kernel memory before the
framebuffer mapping. The primitive can be repeated to modify hooks, scheduler
state, object metadata, or other mapped kernel data.

Other additional context

  • CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H = 8.8
  • CWE-787, CWE-125, CWE-823
  • Prerequisite: the build enables the graphic subsystem and exposes
    /dev/fb0. This is normal for display products. The QEMU reproduction uses
    the stock ramfb device.
  • Related issue: [Security] pread() with a large negative offset bypasses rw_verify_area() causing OOB heap read in tmpfs (info leak / crash) #11737 reports the same negative-offset rule in the tmpfs
    read path. This report is an incomplete-fix sibling in the graphic device
    sink, not a claim that the DFS issue itself is newly discovered.
  • No existing issue or pull request was found for _graphic_read,
    _graphic_write, negative framebuffer offsets, or fb0 arbitrary access.
  • Suggested structural fix: reject negative offsets in the graphic read/write
    operations and enforce device-specific offset bounds at devfs/device
    dispatch.

Activity

  1. github-actions commented on Nov 2, 2026

    @github-actions

    如需认领并处理此 Issue,请评论 /claim。

    To claim and work on this issue, comment /claim.

  2. github-actions commented on Nov 2, 2026

    @github-actions

    以下 Issue 可能与当前问题相关;这是自动检索结果,尚未确认重复:

    The following issues may be related. These automated suggestions are not confirmed duplicates:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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