[Bug] libdl/dlmodule: no bounds validation on ELF relocation metadata - malformed .mo gives arbitrary kernel-mode memory write with controlled value during load
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
- Área
- operating-systems, security
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):
- 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.
- Lenguaje dominante
- C
- Estrellas
- 12.3k
- Forks
- 5.5k
- Merge medio
- 7 d 22 h
- PR fusionados (30 d)
- 30
Preparar el entorno
Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de RT-Thread/rt-thread
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
RT-Thread/rt-thread#11902 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
[bsp][stm32][bluepill] README「快速上手」缺少重新生成 MDK 工程这一步,按文档操作无法编译通过Posiblemente ocupada @moment-NEW la tomó hace 17 días. Abiertoin progress
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
RT-Thread/rt-thread#11818 · 4 comentarios · 1 asignado ·
Los mantenedores suelen responder en 1 día
-
BSP BSP: Loongson bug RT-Smart
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
RT-Thread/rt-thread#11717 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
Arch: RISC-V BSP BSP: HPMicro bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
RT-Thread/rt-thread#11687 · 3 comentarios ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
RT-Thread/rt-thread#11472 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
Todos los issues de RT-Thread/rt-thread
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 71/100
awslabs/aws-checksums#121 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 86/100
Los mantenedores suelen responder en 3 días
-
Bad tag for language cardAbierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 68/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
-
bug low priority rt_lib wasm
Dificultad 2/5 1-3 horas Aptitud para principiantes 77/100
Los mantenedores suelen responder en 1 día