Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

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

Abierto
#11,898 2 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
5/5
Tiempo estimado
Más de una semana
Aptitud para principiantes
25/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
c

Línea de trabajo

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.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.
Lenguaje dominante
C
Estrellas
12.3k
Forks
5.5k
Merge medio
7 d 22 h
PR fusionados (30 d)
30

Preparar el entorno

Abrir en Codespaces

Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de RT-Thread/rt-thread

Todos los issues de RT-Thread/rt-thread

Issues similares

Más issues de C

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.