β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-0083

OOB write into cpu_topology_nodes[MAXCPU] during boot topology construction on high-CPU-count systems

Field Value
ID DF-0083
Status new
Severity Medium
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE CWE-787 Out-of-bounds Write
File sys/kern/subr_cpu_topology.c
Lines 115-154
Area kern (CPU topology / boot)
Confidence likely
Discovered 2026-06-30
Reported pending

Summary

build_topology_tree() unconditionally increments a global cpu_node_t *last_free_node cursor through the fixed-size static array cpu_topology_nodes[MAXCPU] (MAXCPU = 256 on amd64, param.h:71-72). The cursor starts at root+1 (:185) and is bumped once per allocated node at :140-141:

node->child_node[i] = *last_free_node;
(*last_free_node)++;               /* sys/kern/subr_cpu_topology.c:140-141 */

There is no upper-bound check against &cpu_topology_nodes[MAXCPU] anywhere.

The tree needs 1 + chips + chipsΓ—cores + N nodes (root + package + core + thread levels). For a single-socket SMT system, total = 2 + 3Γ—cores. For N = 256 logical CPUs (128 cores Γ— 2 threads) the tree needs 386 nodes, exceeding the 256-slot array by 130 cpu_node_t entries (~270 KB of BSS). The overflow threshold is ~170 logical CPUs (85 cores Γ— 2 threads β†’ 257 nodes).

Reachability: boot-time SYSINIT (SI_BOOT2_CPU_TOPOLOGY, :823-824). The topology parameters come from CPUID on each CPU β€” firmware/hypervisor controlled. A malicious hypervisor presenting β‰₯170 vCPUs in a single package triggers it deterministically every boot; bare-metal modern many-core silicon (AMD EPYC-class) with β‰₯256 threads likewise.

Impact: large kernel BSS corruption at boot β€” the node-struct writes at :125-132,143,152 stomp adjacent BSS with topology pointers. Boot crash or potentially exploitable corruption. Not runtime-exploitable by a local user.

Bound the cursor:

--- a/sys/kern/subr_cpu_topology.c
+++ b/sys/kern/subr_cpu_topology.c
@@ for (i = 0; i < node->child_no; i++) {
+   if (*last_free_node >= &cpu_topology_nodes[MAXCPU])
+       panic("cpu_topology_nodes overflow");
    node->child_node[i] = *last_free_node;
    (*last_free_node)++;

Root cause: cpu_topology_nodes[] is sized for MAXCPU leaves but needs interior nodes too. Either size the array to 2*MAXCPU + LEVEL_NO + 1 (covers the worst-case single-socket C + CK ≀ 2N), or fall back to a flat topology when the projected node count would exceed the array.

Timeline

  • 2026-06-30 Discovered during automated file-by-file audit of sys/kern/subr_cpu_topology.c.
  • pending Reported to DragonFlyBSD security contact.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0083 Β· 10 files
FileTypeDescriptionSize
topo_oob.c trigger-source faithful userspace replica of build_topology_tree cursor proving the unbounded OOB 5.0 KB view raw
build.sh build-script cc -O2 -o topo_oob topo_oob.c 110 B view raw
run.sh run-script ./topo_oob 198 B view raw
build.log build-log harness compile 0 B ↓ download
run.log run-log overflow table: threshold N=170, 130 OOB writes for N=256 (~268KB BSS) 1.2 KB view raw
env.txt environment uname + guest topology (6 vCPU, dormant) 250 B view raw
VERDICT.md verdict full analysis: boot-time BSS corruption, firmware-controlled, not local-user exploitable 4.1 KB ↓ raw
README.md readme reproduce instructions 1.2 KB ↓ raw
fix.diff suggested-fix bound cursor in build_topology_tree; graceful truncation on overflow 758 B view raw
fix_build.log build-log fix compiles+links (part of a combined kernel build, rc=0); runtime not_testable (needs >=170 CPUs) 5.6 MB ↓ download
README.md readme reproduce instructions
↓ download raw

DF-0083 β€” OOB write into cpu_topology_nodes[MAXCPU] at boot

Verdict REPRODUCED (harness/code-level) β€” boot-time, high-CPU-only
Impact boot-time BSS corruption on >=170-CPU systems (not runtime on this 6-vCPU guest)
File sys/kern/subr_cpu_topology.c:140-141 (array :63, SYSINIT :823)

Build

cc -O2 -o topo_oob topo_oob.c

Run

./topo_oob

Expected (bug present, proven by harness)

170      1pkg SMT2  257  OVERFLOW   1
256      1pkg SMT2  386  OVERFLOW   130
RESULT: OOB_CONFIRMED

The unbounded cursor at subr_cpu_topology.c:140-141 overruns cpu_topology_nodes[MAXCPU=256] for topologies needing >256 nodes (threshold N=170 logical CPUs). On N=256 that is 130 OOB writes (~268 KB of BSS).

Why not a live runtime test

The trigger is the boot-time CPU topology (CPUID, firmware/hypervisor controlled). This guest has 6 vCPUs (needs ~14 nodes), so the bug is dormant here. The harness faithfully reproduces the cursor arithmetic and proves the OOB deterministically. Not a local-user syscall bug β€” no uid=0 chain applies.

Fix

fix.diff bounds the cursor with graceful topology truncation (instead of the finding's panic). See VERDICT.md.

VERDICT.md verdict full analysis: boot-time BSS corruption, firmware-controlled, not local-user exploitable
↓ download raw

DF-0083 β€” OOB write into cpu_topology_nodes[MAXCPU] at boot

Verdict: REPRODUCED at the harness / code level (boot-time, high-CPU-only)

Mechanism

build_topology_tree() (sys/kern/subr_cpu_topology.c:115-154) bumps a global cursor *last_free_node once per allocated child node with no upper-bound check against the fixed static array cpu_topology_nodes[MAXCPU] (:63, MAXCPU=256):

/* subr_cpu_topology.c:139-141 */
for (i = 0; i < node->child_no; i++) {
    node->child_node[i] = *last_free_node;
    (*last_free_node)++;                /* <-- unbounded */

The cursor starts at root+1 (:185). A topology tree needs 1 + packages + packages*cores + N nodes (root + package + core + leaf levels). For a single-package SMT2 system that is 2 + 3*cores = 2 + 3N/2, exceeding 256 at N >= 170 logical CPUs (386 nodes for N=256). Each node is 2112 bytes (sizeof(cpu_node_t), incl. child_node[MAXCPU]), so N=256 overruns by 130 nodes (~268 KB of kernel BSS).

Reachability: boot-time SYSINIT(cpu_topology, SI_BOOT2_CPU_TOPOLOGY, ...) (:823) β†’ build_cpu_topology() (:819) β†’ build_topology_tree(). The parameters come from CPUID on each CPU β€” firmware/hypervisor controlled. A malicious hypervisor presenting >=170 vCPUs in one package, or bare-metal many-core silicon (>=256 threads), triggers it deterministically every boot. Writes at :125-132,143,152 then stomp adjacent BSS with topology pointers.

Proof (harness)

This guest has only 6 vCPUs, so the bug cannot fire at runtime here (the guest topology needs just 11-14 nodes). A faithful userspace replica of build_topology_tree's cursor logic (same struct layout, same increment β€” see topo_oob.c) deterministically reports the overflow:

N(cpu)   topology   nodes_needed   vs MAXCPU(256)   OOB_writes
6        1pkg SMT2  11             ok               0
169      1pkg SMT2  255            ok               0
170      1pkg SMT2  257            OVERFLOW         1
256      1pkg SMT2  386            OVERFLOW         130
512      1pkg SMT2  770            OVERFLOW         514

Threshold: nodes_needed > 256 first at N=170 (2 + 3*85 = 257).
For N=256: 386 nodes -> 130 OOB writes (~268 KB BSS corruption at boot).
This guest: hw.ncpu=6 -> tree needs 14 nodes (no OOB); dormant here.
RESULT: OOB_CONFIRMED

This matches the finding's threshold (>=170) exactly.

Impact / realism & escalation assessment

  • Not runtime-exploitable by an unprivileged local user. The trigger is the boot-time CPU topology (CPUID, firmware/hypervisor controlled), not any syscall surface. There is no privilege boundary for a local user to cross here, so no uid=0 chain applies (this is a valid hard-blocker per the "root-only/firmware-only reachability" category β€” the attacker is the hypervisor/firmware, not a local process).
  • Realistic impact: on a >=170-CPU system the kernel corrupts its own BSS at boot β€” boot crash or potentially exploitable corruption of adjacent static data (the overrun is into whatever follows cpu_topology_nodes in BSS). Medium severity because it needs unusual hardware/a malicious hypervisor.
  • On THIS guest (6 vCPUs) it is dormant β€” no runtime effect, hence guest_dirty=false.

Fix

fix.diff: bound the cursor in build_topology_tree β€” when *last_free_node >= &cpu_topology_nodes[MAXCPU], print a warning and truncate the topology (node->child_no = i; return;) instead of overflowing. This is safer than the finding's suggested panic() (a boot panic on a legitimate high-CPU box would be worse than a partial topology) and more robust than enlarging the array to a guessed worst-case (the true worst case on multi-level topologies can exceed 2*MAXCPU, so a fixed bound check is the correct fix). Supersedes the finding's proposal (graceful truncation instead of panic).

Reproduce

cc -O2 -o topo_oob topo_oob.c     # build.sh
./topo_oob                         # run.sh β€” prints the overflow table

(The harness proves the OOB cursor primitive; runtime firing needs >=170 CPUs, absent on this 6-vCPU guest.)

Fix verification

not_testable

compile validated

see evidence pack

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

REPRODUCED (harness). build_topology_tree cursor no bounds vs cpu_topology_nodes[256] -> BSS OOB at >=170 CPUs. Boot-time. 6 vCPUs on guest.