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

Buddy allocator unbounded merge in ___sym_mfree causes heap OOB write past h[] array

Summary

___sym_mfree at sym_hipd.c:476-496 merge loop has no upper bound on merge index i. h[] declared at :408 as h[MEMO_CLUSTER_SHIFT-MEMO_SHIFT+1]=h[9] (indices 0-8 on x86_64). When two buddy-adjacent 4096-byte pages freed, merge at i=8 (s=4096) succeeds, i becomes 9, h[9] accessed - 8-byte OOB write past m_pool_s into adjacent kernel heap. MEMO_FREE_UNUSED guard at :477 is #if 0 (disabled by default at :366). Practical trigger: squeue+dqueue each exactly 4096 bytes freed consecutively in sym_pci_free (:8913-8916); buddy-adjacent if contiguous DMA allocations. Triggered by driver attach/detach cycles (hot-pluggable PCI SCSI HBA or kldunload). Fix: add unconditional guard when s>=MEMO_CLUSTER_SIZE.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1120 Β· 11 files
FileTypeDescriptionSize
harness.c trigger-source allocator replica demonstrating h[9] OOB write via buddy merge 4.9 KB view raw
fix.diff suggested-fix add if (s >= MEMO_CLUSTER_SIZE) break; unconditional guard 751 B view raw
build.sh build-script cc -O2 -o harness harness.c 95 B view raw
run.sh run-script timeout 10 ./harness 67 B view raw
build.log build-log final successful build 13 B view raw
run.log run-log decisive run incl canary-corruption marker 520 B view raw
env.txt environment uname, cc version, pciconf (no SCSI sym HW) 403 B view raw
VERDICT.md verdict full narrative + INVARIANTS-on GENERIC impact discussion 3.3 KB ↓ raw
README.md readme finding summary + build/run/expected 1.4 KB ↓ raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme finding summary + build/run/expected
↓ download raw

DF-1120 β€” Buddy allocator unbounded merge OOB write in ___sym_mfree (SCSI sym)

Finding

___sym_mfree at sys/dev/disk/sym/sym_hipd.c:476-497 has a merge loop whose index i climbs without bound. The h[] array is declared at :408 as h[MEMO_CLUSTER_SHIFT - MEMO_SHIFT + 1] = h[9] on x86_64 (indices 0–8). The MEMO_FREE_UNUSED guard at :477 is wrapped in #if 0 ... #endif at :366-368, so it does NOT compile in. When two buddy-adjacent 4096-byte (MEMO_CLUSTER_SIZE) allocations are freed, the merge succeeds at i=8 (s becomes 8192), and the next loop top does q = &h[9] β†’ 8-byte OOB write past m_pool_s.h[] into adjacent kernel heap.

Reachability on this guest

NOT reachable. No SCSI sym HBA is present (pciconf -lv shows only virtio devices + PIIX3). sym.ko is loadable but never attaches. The bug is a latent heap-corruption primitive.

A userspace harness replicates the allocator bookkeeping with the same layout and demonstrates the OOB write into a canary.

Build / Run / Expected

cc -O2 -o harness harness.c     # build.sh
./harness                        # run.sh
# Expected: "*** OOB WRITE at h[9] -- canary corrupted ***"

Files

  • harness.c β€” allocator bookkeeping replica demonstrating the OOB at h[9].
  • fix.diff β€” adds if (s >= MEMO_CLUSTER_SIZE) break; unconditional guard at the top of the merge loop.
  • build.log / run.log / env.txt β€” captured outputs.
VERDICT.md verdict full narrative + INVARIANTS-on GENERIC impact discussion
↓ download raw

VERDICT β€” DF-1120

Verdict: REPRODUCED (primitive) / NOT REACHABLE on guest (HW-gated)

The cited bug is real and confirmed by source trace + userspace allocator replica. It is a latent heap-corruption primitive: reachable only when a SCSI sym HBA is attached and two buddy-adjacent 4096-byte allocations are freed in succession, which this audit guest has no HW to trigger.

Mechanism (confirmed path:line)

  1. ___sym_mfree (sys/dev/disk/sym/sym_hipd.c:454-497) frees a block. The block size walks up via while (size > s) { s <<= 1; ++i; } to find its buddy bucket. For a 4096-byte block: i reaches 8, s = 4096 = MEMO_CLUSTER_SIZE.
  2. The merge loop at :476-497 is while (1) with no upper bound on i. The MEMO_FREE_UNUSED early-exit (:477-481) is wrapped in #if 0 ... #endif at :366-368, so it does not compile in.
  3. When a buddy is found at level i, the loop unlinks it, sets a = a & b; s <<= 1; ++i; and continues. For two buddy-adjacent 4096-byte blocks: merge at i=8 succeeds (s becomes 8192, i=9).
  4. Next loop top: q = &h[i] = &h[9]. But h[] is declared at :408 as h[MEMO_CLUSTER_SHIFT - MEMO_SHIFT + 1] = h[9] (indices 0–8 valid). h[9] is an 8-byte OOB write past m_pool_s.h[] into whatever follows in kernel heap.

Reproduction (userspace harness)

The harness allocates two page-aligned buddy-adjacent 4096-byte regions, replicates the exact merge-loop bookkeeping with h[9] + canary layout, and demonstrates the OOB write:

*** OOB WRITE at h[9] -- canary corrupted ***
    canary before = 0xdeadbeefcafebabe
    canary after  = 0x00000008004ae000  (OVERWRITTEN)

Impact ceiling

  • Per-trigger: 8-byte heap write at a fixed offset past m_pool_s. Content is a kernel heap address (the merged buddy pointer) β€” partially attacker-influenced via prior allocation patterns.
  • Slab-bucket: m_pool_s is kmalloc'd from M_DEVBUF. The 8 bytes after h[8] land in the adjacent heap object β€” type depends on what was allocated next. On the default GENERIC kernel (INVARIANTS ON), the WEIRD_ADDR (0xdeadc0de) poisoning and slab magic checks would likely catch cross-type corruption and panic before exploitation lands; this is a DoS on GENERIC, not a clean uid=0.
  • Privilege: requires the ability to cause sym driver attach/detach cycles (hot-pluggable PCI SCSI HBA or kldunload sym). The unprivileged path is narrow (requires operator on the device node).
  • Realistic: SCSI sym (SYM53C8XX) is legacy hardware.

Fix

fix.diff adds an unconditional upper-bound guard at the top of the merge loop:

if (s >= MEMO_CLUSTER_SIZE)
    break;

This fires before any h[i] access when i would exceed the array, regardless of whether MEMO_FREE_UNUSED is compiled in. Minimal, targeted, one logical change.

Validated: sym.ko builds with RC=0 after applying the fix.

Fix validation

  • Patch applies cleanly: Hunk #1 succeeded at 474.
  • make in sys/dev/disk/sym/ β†’ sym.ko linked, RC=0.
  • Cannot boot-test (no SCSI sym HBA); fix_status: not_testable.

PoC changes

  • harness.c written from scratch. Uses posix_memalign for real page-aligned buddy addresses so the linked-list manipulation is valid; places a canary after h[8] to demonstrate the OOB write.

Fix verification

not_testable

compile validated

module build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source+harness. sym_hipd buddy merge loop no bound -> h[9] 8B OOB write. No SCSI sym HW.