[Bug] libdl/dlmodule: no bounds validation on ELF relocation metadata - malformed .mo gives arbitrary kernel-mode memory write with controlled value during load
Maintainer antworten meist innerhalb von 1 Tag
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 5/5
- Geschätzter Aufwand
- Über eine Woche
- Anfängerfreundlichkeit
- 25/100
- Issue-Typ
- Bug
- Klarheit
- Klar beschrieben
- Aktivitätsstatus
- Aktiv
- Tech-Stack
- c
- Bereich
- operating-systems, security
Rechercherichtung
Start with components/libc/posix/libdl/dlelf.c and the architecture relocation handlers in components/libc/posix/libdl/arch/arm.c, arch/riscv.c, and arch/x86.c. Trace the ELF metadata and relocation offsets from dlmodule_load() into dlmodule_relocate(); the issue reports a crafted .mo causing an out-of-bounds kernel write. Done means malformed ELF ranges and indices are rejected safely across the listed metadata paths and architectures.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
RT-Thread Version
master @ 5d1fb66d (v5.4.0-dev; components/libc/posix/libdl/dlelf.c verified byte-identical to origin/master as of 2026-10-19). Affected since v3.1.0; introduced by commit efe45231 ("[Kernel] move module to libc/libdl", 2018-08-30) which added dlelf.c/arch/arm.c with the unchecked relocation write. Verified present in every release tag since (checked by reading arch/arm.c/dlelf.c at v3.1.0, v4.0.0, v5.3.0 and master).
Affected area
Components / libc (libdl dynamic module loader, RT_USING_MODULE)
Hardware/BSP vendor
Not applicable / Other
Architecture
ARM Cortex-A/R/M 32-bit (reproduced), RISC-V 32, x86 — every architecture with a dlmodule_relocate port (arch/arm.c, arch/riscv.c, arch/x86.c share the same pattern). Not applicable to aarch64 (no port).
Board and hardware details
QEMU vexpress-a9 (Cortex-A9), stock bsp/qemu-vexpress-a9 defconfig + RT_USING_MODULE=y, FAT16 SD image holding the module file. Any board with the dynamic-module feature enabled is affected.
Develop Toolchain
arm-none-eabi-gcc 10.3.1 (gcc / scons)
Describe the bug
The dynamic module loader (dlopen()/msh auto-exec of .mo files) performs no bounds validation whatsoever on the ELF relocation metadata it parses. The write target of every relocation is computed directly from the attacker-controlled 32-bit Elf32_Rel.r_offset in the module file:
// components/libc/posix/libdl/arch/arm.c:33-37 (arch/riscv.c & arch/x86.c: same pattern)
where = (Elf32_Addr *)((rt_uint8_t *)module->mem_space
+ rel->r_offset // <-- file-supplied, never checked
- module->vstart_addr);
...
case R_ARM_GLOB_DAT:
case R_ARM_JUMP_SLOT:
*where = (Elf32_Addr)sym_val; // arm.c:65-66
and the value sym_val is the address of any symbol the module names from the kernel export table (dlmodule_symbol_find(strtab + sym->st_name), dlelf.c:193). On 32-bit targets mem_space + r_offset wraps modulo 2^32, so a malformed .mo obtains an arbitrary-address, attacker-valued 4-byte kernel-mode write, executed during dlmodule_load() — before any module code runs, so this is not "modules are trusted code": the corruption comes purely from the loader parsing malformed input.
The whole metadata chain feeding that write is likewise unchecked (same root cause, independent read primitives): r_info symbol index (24-bit, symtab[ELF32_R_SYM()] OOB), sh_link/sh_link->sh_link (unbounded shdr[] indexing → wild rel/symtab/strtab pointers), e_shoff/e_phoff/e_shstrndx (never compared against the file length; dlmodule_load() only checks magic + ELF class), p_offset+p_filesz / sh_offset+sh_size (heap over-read as memcpy source), and st_value (wild read feeding module->priority/stack_size). e_machine is not validated either, so cross-architecture ELF files are also interpreted.
Reproduction (full chain: FAT file → dfs → msh evil → dlmodule_exec → loader; QEMU + gdb-multiarch):
- Build
bsp/qemu-vexpress-a9withRT_USING_MODULE=y; attach a FAT16 SD image containing a craftedevil.mo(minimal ET_DYN ARM ELF, one.rel.textentry:R_ARM_GLOB_DATon symbolrt_kprintf,r_offset = 0xfffe4074). - Type
evilin the msh shell (msh auto-triesevil.mo, msh.c:312dlmodule_exec). - Result:
*(_ptr_t)(mem_space + r_offset - vstart) = &rt_kprintflands at kernel.bss0x600ec26c (_rt_module_symtab_end, 0x1bf8c bytes before the module allocation) — hardware watchpoint capturesOld = 0x600c1a40 → New = 0x600a7870 (&rt_kprintf)with backtracedlmodule_relocate ← dlmodule_load_shared_object (dlelf.c:201) ← dlmodule_load ← dlmodule_exec ← msh_exec_module (msh.c:312) ← msh_exec ← finsh_thread_entry,rel->r_offset = 0xfffe4074vsmodule->mem_size = 0x2000. The kernel keeps running — silent, persistent corruption. - Crash variant (
r_offset = 0x9fef7e48, write to the null page):data abort,pc=0x600163f0(strindlmodule_relocate),r3=0x40(computed target),r2=0x600a7870(value), same 6-frame backtrace, kernel shuts down. In the same session a well-formed module built with the stock toolchain (hello.mo, singleR_ARM_JUMP_SLOTat a legitimate in-module offset0x10338 < 0x1033c) loads, runs and exits cleanly — the only difference between the benign and malicious files isr_offset. - Value control: pointing the same primitive at another address with symbol
rt_strlenwrites0x6005e984there (watchpointNew = &rt_strlen).
Generator, control module, GDB scripts and full serial/GDB logs: see attached (gen_mo.py produces the malformed module; hello.c is the control).
Expected behavior
The loader must validate, against module->mem_size and the file buffer length: every relocation target (vstart_addr <= r_offset within the mapped image), the symbol index in r_info, all section-header indices (sh_link, sh_info, e_shstrndx), the table base offsets (e_shoff/e_phoff + count*sizeof within the file), copy sources (p_offset+p_filesz, sh_offset+sh_size within the file), and e_machine against the running target. Anything outside bounds must fail the load with an error, not write.
Other additional context
- Attack preconditions (explicit): attacker can place a file on a filesystem the system loads modules from, and the module gets loaded via the documented entry points — msh executing
<name>.mo//bin/<name>.mo(components/finsh/msh.c:255-322) or an application callingdlopen(). Typical exposure: module/plugin hot-updates from SD cards or network downloads, field-deployed kiosks/devices with a shell. The corruption happens inside the loader, before any module code executes — a robustness defect in trusted kernel code parsing untrusted input, not a "malicious module runs code" scenario. - Impact: arbitrary kernel-mode memory write with attacker-chosen value (any
RTM_EXPORTed symbol address), plusR_ARM_ABS32/R_ARM_RELATIVEadd-write and read-modify-write variants → kernel control-flow hijack (overwrite any function pointer/vtable in kernel .data/.bss/heap) or deterministic kernel crash (DoS). CVSS 3.1: 7.1 (AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H), CWE-122/CWE-129 (read-side variants CWE-125). - Relationship to prior reports: none — the only recent libdl changes are #11247 (2026-03, unrelated
static unsolvedreset) and #11062 (2025-12, loop cleanup); neither touches bounds validation. Not related to #11882 (lwp sigaction) — different component, different root cause. - Fix suggestion (one systematic pass, mirrors the checks Zephyr added for LLEXT): centralize a
dlmodule_check_range()and apply it at every file-derived index/offset listed above.
- Vorherrschende Sprache
- C
- Sterne
- 12.3k
- Forks
- 5.5k
- Ø Merge
- 7 T. 22 Std.
- Gemergte PRs (30 T.)
- 30
Entwicklungsumgebung
Startet den Dev-Container des Projekts im Browser, mit Ihrem eigenen GitHub-Konto.
- Kein Dockerfile und keine Docker-Compose-Datei
- Hat eine Pull-Request-Vorlage
- Beitragsleitfaden lesen
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus RT-Thread/rt-thread
-
bug Component component: net
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
RT-Thread/rt-thread#11913 · 1 Kommentar ·
Maintainer antworten meist innerhalb von 1 Tag
-
[Bug] dfs_v2 tmpfs: ftruncate(fd, 0) frees the file buffer but reports success — use-after-free read/write and double free from a legal POSIX callEvtl. vergeben @remukisama hat das vor 1 Tag übernommen. Offenin progress
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
RT-Thread/rt-thread#11902 · 4 Kommentare · 1 zugewiesene Person ·
Maintainer antworten meist innerhalb von 1 Tag
-
[bsp][stm32][bluepill] README「快速上手」缺少重新生成 MDK 工程这一步,按文档操作无法编译通过Evtl. vergeben @moment-NEW hat das vor 18 Tagen übernommen. Offenin progress
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
RT-Thread/rt-thread#11818 · 4 Kommentare · 1 zugewiesene Person ·
Maintainer antworten meist innerhalb von 1 Tag
-
BSP BSP: Loongson bug RT-Smart
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 72/100
RT-Thread/rt-thread#11717 · 2 Kommentare ·
Maintainer antworten meist innerhalb von 1 Tag
-
Arch: RISC-V BSP BSP: HPMicro bug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 72/100
RT-Thread/rt-thread#11687 · 3 Kommentare ·
Maintainer antworten meist innerhalb von 1 Tag
Alle Issues in RT-Thread/rt-thread
Ähnliche Issues
-
v9.6.0: CMake incorrectly enforces presence of libevdevEvtl. vergeben Ein verknüpfter Pull Request ist offen oder bereits gemergt. Offen
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 67/100
Maintainer antworten meist innerhalb von 1 Tag
-
[Bug] Unhandled ValueError terminates watchdog thread when GISRC value contains multiple colonsOffenbug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 76/100
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 72/100
contiki-ng/contiki-ng#3289 ·
Maintainer antworten meist innerhalb von 3 Tagen
-
area:docs help wanted type:enhancement
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 76/100
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 83/100
semantic-reasoning/wirelog#2229 ·
Maintainer antworten meist innerhalb von 1 Tag