[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)
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
- Lĩnh vực
- operating-systems, security
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:
- 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 atnp_stack[66]is seen half-overwritten with thedepthvalue
(0x00000020402270b0), the walk state derails (depth jumps backwards), the
tree is mis-linked, and boot dies downstream (/cpuslookup assertion). - 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):
- Dump QEMU's own DTB for the exact machine config
(-machine virt,acpi=on,its=on,gic-version=2,dumpdtb=base.dtb). - 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> {}; ... }; }; - 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: master5d1fb66d13(#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 thanRT_USING_OFW_BUS_RANGES_NUMBERdistinct
rangesbuses (io.c:364-367,RT_ASSERT-only guard); (b)
rt_ofw_find_node_by_phandle()indexes_phandle_hashwith any referenced
phandle value without checking the table's actual coverage, and the
RT_ALIGN(max, 8)added by #11845 wraps to 0 formax = 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
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.
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- 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.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của RT-Thread/rt-thread
-
[bsp][stm32][bluepill] README「快速上手」缺少重新生成 MDK 工程这一步,按文档操作无法编译通过Có thể đã có người làm @moment-NEW đã nhận 14 ngày trước. Đang mởin progress
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
RT-Thread/rt-thread#11818 · 4 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
BSP BSP: Loongson bug RT-Smart
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
RT-Thread/rt-thread#11717 · 2 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Arch: RISC-V BSP BSP: HPMicro bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
RT-Thread/rt-thread#11687 · 3 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
RT-Thread/rt-thread#11472 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 35/100
RT-Thread/rt-thread#11878 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của RT-Thread/rt-thread
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 67/100
dosemu2/dosemu2#3178 · 1 bình luận · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
-
backlog bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
EchoTools/nevr-runtime#653 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
InfiniTimeOrg/InfiniTime#2492 ·
-
Build fails when `memset_s` is unavailable: `Undefined symbols for architecture ppc: "_memset_s"`Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
-
XML_GetCurrentByteIndex64() returns -1 instead of a meaningful offset when m_eventPtr is NULLĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
Maintainer thường phản hồi trong vòng 1 ngày