Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

[Bug] libdl/dlmodule: no bounds validation on ELF relocation metadata - malformed .mo gives arbitrary kernel-mode memory write with controlled value during load

Aperta
#11,898 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
25/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
c

Direzione di ricerca

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.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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):

  1. Build bsp/qemu-vexpress-a9 with RT_USING_MODULE=y; attach a FAT16 SD image containing a crafted evil.mo (minimal ET_DYN ARM ELF, one .rel.text entry: R_ARM_GLOB_DAT on symbol rt_kprintf, r_offset = 0xfffe4074).
  2. Type evil in the msh shell (msh auto-tries evil.mo, msh.c:312 dlmodule_exec).
  3. Result: *(_ptr_t)(mem_space + r_offset - vstart) = &rt_kprintf lands at kernel .bss 0x600ec26c (_rt_module_symtab_end, 0x1bf8c bytes before the module allocation) — hardware watchpoint captures Old = 0x600c1a40 → New = 0x600a7870 (&rt_kprintf) with backtrace dlmodule_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 = 0xfffe4074 vs module->mem_size = 0x2000. The kernel keeps running — silent, persistent corruption.
  4. Crash variant (r_offset = 0x9fef7e48, write to the null page): data abort, pc=0x600163f0 (str in dlmodule_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, single R_ARM_JUMP_SLOT at a legitimate in-module offset 0x10338 < 0x1033c) loads, runs and exits cleanly — the only difference between the benign and malicious files is r_offset.
  5. Value control: pointing the same primitive at another address with symbol rt_strlen writes 0x6005e984 there (watchpoint New = &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 calling dlopen(). 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), plus R_ARM_ABS32/R_ARM_RELATIVE add-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 unsolved reset) 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.
Lingua principale
C
Stelle
12.3k
Fork
5.5k
Merge medio
7g 22h
PR unite (30g)
30

Preparare l'ambiente

Apri in Codespaces

Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di RT-Thread/rt-thread

Tutte le issue di RT-Thread/rt-thread

Issue simili

Altre issue su C

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.