Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

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

未关闭
#11,879 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
1-2 天
新手友好度
48/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
c

调研方向

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.

由索引模型根据 Issue 内容生成。

描述

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.
主要语言
C
星标
12.3k
派生
5.5k
平均合并
7 天 9 小时
30 天内合并 PR
34

环境准备

在 Codespaces 中打开

在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

RT-Thread/rt-thread 的其他 Issue

查看 RT-Thread/rt-thread 的全部 Issue

相似的 Issue

更多 C Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。