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)
PoC verification
Evidence pack
findings/poc/DF-1120 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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β addsif (s >= MEMO_CLUSTER_SIZE) break;unconditional guard at the top of the merge loop.build.log/run.log/env.txtβ captured outputs.
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)
___sym_mfree(sys/dev/disk/sym/sym_hipd.c:454-497) frees a block. The block size walks up viawhile (size > s) { s <<= 1; ++i; }to find its buddy bucket. For a 4096-byte block:ireaches 8,s= 4096 =MEMO_CLUSTER_SIZE.- The merge loop at
:476-497iswhile (1)with no upper bound oni. TheMEMO_FREE_UNUSEDearly-exit (:477-481) is wrapped in#if 0 ... #endifat:366-368, so it does not compile in. - When a buddy is found at level
i, the loop unlinks it, setsa = a & b; s <<= 1; ++i;and continues. For two buddy-adjacent 4096-byte blocks: merge ati=8succeeds (sbecomes 8192,i=9). - Next loop top:
q = &h[i]=&h[9]. Buth[]is declared at:408ash[MEMO_CLUSTER_SHIFT - MEMO_SHIFT + 1]=h[9](indices 0β8 valid).h[9]is an 8-byte OOB write pastm_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_siskmalloc'd fromM_DEVBUF. The 8 bytes afterh[8]land in the adjacent heap object β type depends on what was allocated next. On the default GENERIC kernel (INVARIANTS ON), theWEIRD_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 cleanuid=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. makeinsys/dev/disk/sym/βsym.kolinked,RC=0.- Cannot boot-test (no SCSI sym HBA);
fix_status: not_testable.
PoC changes
harness.cwritten from scratch. Usesposix_memalignfor real page-aligned buddy addresses so the linked-list manipulation is valid; places a canary afterh[8]to demonstrate the OOB write.
Fix verification
not_testablecompile 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.
No comments yet.