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

[Bug] OFW fdt_unflatten_single: unbounded device-tree nesting depth overflows the 64-entry np_stack kernel stack array at boot (components/drivers/ofw/fdt.c)

Abierto
#11,879 1 comentario 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
4/5
Tiempo estimado
1-2 días
Aptitud para principiantes
48/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
c

Línea de trabajo

Read components/drivers/ofw/fdt.c, especially fdt_unflatten_single(), and components/drivers/ofw/ofw_internal.h for OFW_NODE_MAX_DEPTH; the report also traces the call from libcpu/aarch64/common/setup.c. Reproduce with the QEMU AArch64 BSP and a DTB containing more than 64 nested nodes. Done when unflattening rejects excessive depth safely and boot completes with that input.

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

Descripción

RT-Thread Version

master 8d8dc96b67 (2026-10-17; the vulnerable code is present — verified by
reading the latest tree). Introduced with the DD2.0/DM OFW series in July 2023
(8aa4366cb2, 2f5e4ac27e); first affected release is v5.0.2 (2023-10-08,
verified by reading components/drivers/ofw/fdt.c at that tag). All releases
v5.0.2–v5.3.0 and master are affected.

Affected area

Device drivers

Hardware/BSP vendor

Not applicable / Other

Architecture

ARM / AArch64 (also RISC-V; reproduced on AArch64 QEMU, component-level bug)

Board and hardware details

QEMU virt (bsp/qemu-virt64-aarch64), stock BSP config: RT_USING_OFW=y,
RT_USING_BUILTIN_FDT not set, so the device tree consumed by the kernel is
the externally supplied DTB (passed by the boot loader / QEMU in x0).

Develop Toolchain

GCC (aarch64-none-elf 13.2)

Describe the bug

fdt_unflatten_single() in components/drivers/ofw/fdt.c flattens the device
tree using a fixed 64-entry stack array:

static rt_err_t fdt_unflatten_single(struct rt_ofw_node *np, int node_off)
{
    int depth = 0;
    ...
    struct rt_ofw_node *np_stack[OFW_NODE_MAX_DEPTH], *parent = RT_NULL; /* 64 */
    ...
        node_off = fdt_first_subnode(_fdt, node_off);
        if (node_off >= 0)
        {
            parent = np;

            np_stack[depth++] = np;      /* fdt.c:1015 — no depth bound check */

            np->child = rt_calloc(1, sizeof(struct rt_ofw_node));
            ...
        }
    ...
        np = np_stack[--depth];          /* OOB read on unwind */

OFW_NODE_MAX_DEPTH is 64 (ofw_internal.h:21). The nesting depth of the tree
is entirely controlled by whoever supplies the DTB. A DTB nesting more than 64
levels makes the loop store heap node pointers at np_stack[64..N-1] — i.e. at
&np_stack[0] + 8*depth for attacker-chosen, unbounded depth — walking up the
boot kernel stack: over the frame's live locals, then the saved FP/LR of
fdt_unflatten_single and every caller frame above it.

The function runs from rt_hw_common_setup()
(libcpu/aarch64/common/setup.c:463 → rt_fdt_unflatten()) on the main boot
stack, before the scheduler starts, directly on the bootloader-supplied blob.

Two impact levels, both reproduced:

  1. Guaranteed memory corruption at boot. The out-of-bounds zone is also the
    compiler's spill area for other locals — under GDB, a previously stored
    pointer at np_stack[66] is seen half-overwritten with the depth value
    (0x00000020402270b0), the walk state derails (depth jumps backwards), the
    tree is mis-linked, and boot dies downstream (/cpus lookup assertion).
  2. Control-flow hijack primitive. Because the write offset is chosen by
    DTB nesting depth and the loop runs once per level, a deep-enough tree
    overwrites saved return addresses along the boot stack with heap pointers.

Steps to reproduce (full chain under QEMU, no host-side shortcuts; scripts
available on request):

  1. Dump QEMU's own DTB for the exact machine config
    (-machine virt,acpi=on,its=on,gic-version=2,dumpdtb=base.dtb).
  2. Insert only a chain of N empty nested nodes under / (nothing else —
    no properties, no devices; DTS snippet, compiled with dtc):
    dep0 { dep1 { ... dep<N-1> {}; ... }; };
  3. Boot bsp/qemu-virt64-aarch64 (stock config, rtthread.bin) with
    -dtb crafted.dtb.

Results (identical base DTB, only nesting depth varies):

Nesting depth Result
none / 60 / 64 / 65 boots normally to msh />
70 / 80 / 90 / 100 boot-time kernel crash: (ofw_node_cpus != RT_NULL) assertion failed at function:rt_fdt_unflatten, line number:896 + backtrace

GDB store-level evidence (breakpoint on fdt.c:1015, skipping the first 64
in-bounds hits):

=== FIRST OUT-OF-BOUNDS STORE: np_stack[64] ===
depth          = 64
np             = (struct rt_ofw_node *) 0x40226f90
&np_stack[0]   = 0x401c7c38
&np_stack[63]  = 0x401c7e30        <- array ends at 0x401c7e37
0x401c7e38:     0x0000000040226f90 <- np_stack[64] (OOB) holds heap node ptr
...
0x401c7e48:     0x00000020402270b0 <- [66] upper half clobbered by depth (0x20)

Expected behavior: the unflattening walk must reject trees deeper than the
fixed stack (or grow the stack dynamically / use a documented depth limit), the
same way other fixed arrays in the boot path are bounded.

Actual behavior: an unbounded number of 8-byte kernel-stack writes past a
64-entry array, with the offset chosen by the supplied DTB.

Other additional context

  • Severity: CVSS 3.1 AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (8.4 High),
    CWE-787/CWE-788. Premise stated explicitly: the DTB is an unverified boot
    input
    on this code path (no BUILTIN_FDT), so any product where the DTB can
    be supplied or tampered with independently of the verified kernel image
    (SD/eMMC boot media, unsigned dtb partition, OTA packages that only verify
    the kernel blob, boot loaders that pass the blob unchecked) is exposed. If a
    given product fully trusts its DTB, this degrades to a boot-time robustness
    bug (denial of service). Note the project already treats DTB-originated
    memory corruption as fix-worthy: master 5d1fb66d13 (#11845) fixed the
    phandle-hash buffer overflow in this same file family.
  • Two sibling findings in the same component, reported separately later if
    useful: (a) ofw_bus_ranges() writes past the fixed _bus_ranges[8] static
    array once a DTB contains more than RT_USING_OFW_BUS_RANGES_NUMBER distinct
    ranges buses (io.c:364-367, RT_ASSERT-only guard); (b)
    rt_ofw_find_node_by_phandle() indexes _phandle_hash with any referenced
    phandle value without checking the table's actual coverage, and the
    RT_ALIGN(max, 8) added by #11845 wraps to 0 for max = 0xFFFFFFFE
    (incomplete fix) — reproduced as a boot data-abort.
  • Suggested fix: bound the descent in fdt_unflatten_single
    (if (depth >= OFW_NODE_MAX_DEPTH) { err = -RT_EINVAL; break; } and free the
    partial tree on error), or document + enforce a maximum DTB depth at
    rt_fdt_prefetch() time.
Lenguaje dominante
C
Estrellas
12.3k
Forks
5.5k
Merge medio
7 d 23 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.