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

VIA/JMicron metadata writeback persists low 32 bits of kernel device_t pointers into on-disk metadata

Summary

ata_raid_jmicron_write_meta stores (u_int32_t)(uintptr_t)rdp->disks[disk].dev into meta->disks[] when a member has no serial (true for every ioctl-created array, since ata_raid_create M_ZEROes the softc and never sets serials), and ata_raid_via_write_meta does it unconditionally for all members. The value is inside the checksummed block written to each member disk and re-parsed later - a durable, reboot-surviving disclosure of live kernel pointer bits. Vendor-gated (JMicron/VIA PCI IDs) so not runnable on this guest: inconclusive, source certain (straight-line dataflow).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3694 Β· 6 files
FileTypeDescriptionSize
README.md β€” 1.6 KB ↓ raw
VERDICT.md β€” 2.0 KB ↓ raw
analysis.txt β€” 1.8 KB view raw
fix.diff β€” 3.6 KB view raw
verdict.json β€” 1.7 KB view raw
manifest.json β€” 2.1 KB view raw

DF-3694 β€” VIA / JMicron metadata writeback persists kernel pointer bits on disk

Root cause (source)

Both vendor write paths store truncated kernel pointers as on-disk disk IDs:

2736 (jmicron):  meta->disks[disk] = (u_int32_t)(uintptr_t)rdp->disks[disk].dev;
3986 (via):      meta->disks[disk] = (u_int32_t)(uintptr_t)rdp->disks[disk].dev;
  • JMicron (:2732-2737): used when a member disk has no stored serial β€” i.e. for every ioctl-created array (ata_raid_create never populates serials).
  • VIA (:3985-3986): used unconditionally for every member.

The metadata block (checksummed) is then written to each member disk (:2753-2756, :4005-4008). Anyone who can later read raw sectors of the member disk (operator group on the raw /dev/adX node, physical access, cloud image inspection) recovers the low 32 bits of live kernel device_t pointers β€” defeating heap/KASLR randomization to that extent (CWE-200/524). Writeback happens on array state changes (see DF-3693 triggers), including the boot-time 0β†’READY transition and atacontrol-driven events.

Guest testability

VIA and JMicron formats are only parsed on matching PCI vendors (ata_raid_read_metadata:1433-1435, :1460-1462); this guest has Intel IDE only → inconclusive (cannot instantiate the format on this hardware). Source analysis is certain: the pointer→u32→disk dataflow has no conditional to argue about.

Fix

fix.diff replaces both with karc4random() (no kernel-derived value is persisted; the on-disk ID is only matched against itself on re-parse).

Files

analysis.txt, fix.diff, verdict.json

VERDICT.md
↓ download raw

DF-3694 VERDICT

Status: inconclusive (runtime) / certain (source). impact: leak (partial pointer disclosure; not demonstrated at runtime).

Why inconclusive at runtime

VIA and JMicron formats are vendor-gated (ata-raid.c:1433-1435, :1460-1462). The guest has no VIA/JMicron controller, so neither write path can execute here. The JMicron/VIA write functions are only dispatched from ata_raid_write_metadata for arrays of those formats β€” no other caller.

Why the source claim is certain

  • :2736 meta->disks[disk] = (u_int32_t)(uintptr_t)rdp->disks[disk].dev; (JMicron, executed when serial[0]==0 β€” true for every ioctl-created array since ata_raid_create kmallocs with M_ZERO and never sets serials) and :3986 (VIA, unconditional for all members).
  • The value lands inside the checksummed metadata block that ata_raid_rw(..., ATA_R_WRITE|ATA_R_DIRECT) persists to each member disk (:2753-2756, :4005-4008).
  • On-disk it is directly recoverable: read the metadata sector, subtract the known template fields β†’ low 32 bits of a live kernel device_t.
  • No conditional logic is involved; the dataflow is straight-line.

Impact ceiling

32 bits of a kernel heap pointer per array writeback β€” meaningful KASLR / heap-randomization erosion on systems where the pointer's high bits are otherwise known, and a durable (survives reboot) leak into disk images. Read access to member disk raw sectors is required (root / devfs-permissive operator / physical / image inspection). Low severity overall.

Fix

Replace both assignments with karc4random() (in fix.diff; not runtime validated on this guest for the hardware reason above β€” fix_status = not_testable).

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

constant substitution included in fix.diff; not runtime-validatable on this guest

fix.diff hunks '@@ -2733,7 +2760,7 @@' and '@@ -3983,7 +4010,7 @@'
↓ fix.diffper-fix-DF-3694

Confirmed kernel references

Detail

Evidence (decisive lines)

analysis.txt (full source trace incl. round-trip: leaked value is checksummed and re-parsed)

PoC changes

n/a

Verified recommended fix

store karc4random() instead of a truncated kernel pointer (fix.diff hunks 8+9)

Verdict

ata_raid_jmicron_write_meta (:2736, fires whenever a member has no serial - true for every ioctl-created array) and ata_raid_via_write_meta (:3986, unconditional) store (u_int32_t)(uintptr_t)rdp->disks[disk].dev - low 32 bits of live kernel device_t pointers - into checksummed on-disk metadata that is persisted on every config writeback. Straight-line dataflow, no conditionals; source-certain. Runtime test impossible here: both formats are PCI-vendor-gated (JMicron/VIA) and the guest has Intel IDE only.