[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)
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 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:
- 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.
- 主要言語
- C
- スター
- 12.3k
- フォーク
- 5.5k
- 平均マージ
- 7日 22時間
- マージ済み PR(30日)
- 30
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
RT-Thread/rt-thread のほかの issue
-
[bsp][stm32][bluepill] README「快速上手」缺少重新生成 MDK 工程这一步,按文档操作无法编译通过対応中かも @moment-NEW が 16 日前に担当しました。 オープンin progress
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
RT-Thread/rt-thread#11818 · コメント 4 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
BSP BSP: Loongson bug RT-Smart
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
RT-Thread/rt-thread#11717 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
Arch: RISC-V BSP BSP: HPMicro bug
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
RT-Thread/rt-thread#11687 · コメント 3 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
RT-Thread/rt-thread#11472 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 5/5 1週間以上 初心者へのやさしさ 25/100
RT-Thread/rt-thread#11898 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
RT-Thread/rt-thread の issue をすべて見る
似ている issue
-
enhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
EchoTools/nevr-runtime#698 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
FujiNetWIFI/fujinet-firmware#2079 ·
メンテナーはふだん 1 日以内に返信
-
area:ci area:runtime-vm bug
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
InauguralSystems/EigenScript#1859 ·
メンテナーはふだん 1 日以内に返信
-
bug Official Image release/25.12 Supported Device target/kirkwood
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信
-
area/archive bug
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
DarkFlippers/qUnleashed#374 ·
メンテナーはふだん 1 日以内に返信