Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue 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)

Đang mở
#11,879 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
1-2 ngày
Mức phù hợp với người mới
48/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
c

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

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.
Ngôn ngữ chính
C
Star
12.3k
Fork
5.5k
Merge trung bình
7 ngày 9 giờ
Pull request đã merge (30 ngày)
34

Chuẩn bị môi trường

Mở trong Codespaces

Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của RT-Thread/rt-thread

Tất cả issue của RT-Thread/rt-thread

Issue tương tự

Thêm issue về C

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.