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

NULL-deref kernel panic in dm_table_load_ioctl via missing/non-array cmd_data

Summary

dm_ioctl.c:707 cmd_array = prop_dictionary_get(dm_dict, DM_IOCTL_CMD_DATA) returns NULL when key absent. 708 iter = prop_array_iterator(cmd_array) called with NULL. prop_array_iterator (prop_array.c:538) does _PROP_RWLOCK_RDLOCK(pa->pa_rwlock) without NULL check -> derefs NULL+offsetof. Trigger: operator group user (0640 root:operator) issues NETBSD_DM_IOCTL command=reload without cmd_data key. No device needed. Immediate kernel panic. Fix: check cmd_array!=NULL && type==PROP_TYPE_ARRAY.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1639 Β· 14 files
FileTypeDescriptionSize
dm_poc.c trigger-source unified dm ioctl PoC; case 1639 sends reload with no cmd_data 10.5 KB view raw
build.sh build-script cc -o dm_poc dm_poc.c 117 B view raw
run.sh run-script ./dm_poc 1639 250 B view raw
README.md readme per-finding summary + mechanism + reproduce 2.5 KB ↓ raw
VERDICT.md verdict full analysis: REPRODUCED, NULL-deref read-only primitive, fix validated 2.5 KB ↓ raw
build.log build-log PoC compile output 98 B view raw
run.log run-log baseline panic + fix clean return 527 B view raw
fix_build.log fix-build-log patched dm.ko module build output 1.0 KB view raw
fix_run.log fix-run-log patched-module test: EINVAL, no panic 199 B view raw
panic.txt panic-signature Fatal trap 12 at prop_array_iterator+0x1b (fault addr 0x40) 390 B view raw
fix.diff suggested-fix NULL-check cmd_array before prop_array_iterator 496 B view raw
env.txt environment uname, cc, module list, test user 809 B view 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 per-finding summary + mechanism + reproduce
↓ download raw

DF-1639 β€” NULL-deref panic in dm_table_load_ioctl via missing cmd_data key

Summary

dm_ioctl.c:707 cmd_array = prop_dictionary_get(dm_dict, DM_IOCTL_CMD_DATA) returns NULL when the cmd_data key is absent from the prop_dictionary. Line 708 iter = prop_array_iterator(cmd_array) is then called with NULL, which dereferences NULL->pa_rwlock (offset 0x40) β†’ Fatal trap 12: page fault at prop_array_iterator+0x1b. Immediate kernel panic; no device or table needed.

Severity / impact

  • Severity filed: High
  • Verified impact: panic (local DoS). Read-only NULL-deref at fixed address 0x40 β€” there is no write primitive and thus no escalation path. This is a valid hard blocker (read-only primitive at a fixed low address).
  • Trigger credential: operator group (default /dev/mapper/control is 0640 root:operator; operator opens O_RDONLY and the ioctl is accepted).
  • Precondition: admin has loaded the dm KLD module (kldload dm). This is a realistic admin action (LVM2 / dm-crypt infrastructure). The bug trigger itself is issued by the unprivileged operator-group user.

Reproduce

# admin one-time setup
kldload dm
pw groupmod operator -m <user>     # give operator-group users access

# as the operator-group user
./build.sh
./run.sh
# expected (BUG): kernel panics, guest dies, "Fatal trap 12 ... prop_array_iterator"
# expected (FIXED): ioctl returns EINVAL, guest stays up

PoC

dm_poc.c case 1639: sends command=reload with version [4,1,0] but no cmd_data array in the plist dictionary.

Mechanism (line-accurate)

  1. device-mapper.c:267 prop_dictionary_copyin_ioctl deserializes the user plist.
  2. device-mapper.c:270 dm_check_version passes (version is [4,1,0], kernel wants major==4, minor<=16).
  3. device-mapper.c:271 dm_cmd_to_fun dispatches "reload" β†’ dm_table_load_ioctl (dm_ioctl.c:676).
  4. dm_ioctl.c:707 cmd_array = prop_dictionary_get(dm_dict, "cmd_data") β†’ NULL (key absent).
  5. dm_ioctl.c:708 iter = prop_array_iterator(NULL) β†’ _PROP_RWLOCK_RDLOCK(NULL->pa_rwlock) β†’ lock cmpxchg at addr 0x40 β†’ page fault.

Fix

fix.diff adds a NULL check for cmd_array before the prop_array_iterator call, returning EINVAL cleanly when cmd_data is missing.

Fix validation

Built patched dm.ko module (all three dm_ioctl.c fixes combined), kldload-ed it, re-ran the PoC: returns EINVAL (errno 22), no panic, guest stays up. Baseline (unpatched module) reproduced the panic every time.

VERDICT.md verdict full analysis: REPRODUCED, NULL-deref read-only primitive, fix validated
↓ download raw

DF-1639 β€” VERDICT

Verdict: REPRODUCED (panic / local DoS)

Root cause

sys/dev/disk/dm/dm_ioctl.c:707-708:

cmd_array = prop_dictionary_get(dm_dict, DM_IOCTL_CMD_DATA);
iter = prop_array_iterator(cmd_array);

When the user-supplied prop_dictionary omits the cmd_data key, prop_dictionary_get returns NULL. prop_array_iterator(NULL) then executes _PROP_RWLOCK_RDLOCK(pa->pa_rwlock) with pa==NULL, i.e. a lock cmpxchg at address NULL + offsetof(pa_rwlock) = 0x40 β€” an unmapped page β†’ page fault β†’ panic.

Evidence (baseline, unpatched dm.ko)

Fatal trap 12: page fault while in kernel mode
cpuid = 3; lapic id = 3
fault virtual address     = 0x40
instruction pointer        = 0x8:0xffffffff809d9a7b
current process           = 1050
Stopped at      prop_array_iterator+0x1b:       lock cmpxchgl   %edx,0x40(%rdi)
db>

Guest went down (DDB). Triggered by operator-group user maxx (uid 1001) via a single NETBSD_DM_IOCTL (command=reload) with no cmd_data key. No device, no table, no setup beyond kldload dm + pw groupmod operator -m maxx.

Exploit-chain assessment

This is a read-only NULL-deref at a fixed low address (0x40). There is no write primitive β€” the fault is a read of NULL->pa_rwlock. On DragonFly the NULL page cannot be mapped by userspace (vm.mmap_min_addr enforced), so there is no way to control the dereferenced data. Valid hard blocker: read-only primitive, no escalation path. Impact ceiling = local DoS (panic).

PoC changes

Authored dm_poc.c from scratch β€” the finding folder was empty. The PoC constructs the prop_dictionary plist XML by hand (no libprop dependency) so the malformed input (missing cmd_data) can be crafted precisely, and dispatches it via the standard plistref ioctl transport.

Fix (fix.diff)

 cmd_array = prop_dictionary_get(dm_dict, DM_IOCTL_CMD_DATA);
+if (cmd_array == NULL) {
+    dmdebug("dm_table_load_ioctl: missing cmd_data array\n");
+    return EINVAL;
+}
 iter = prop_array_iterator(cmd_array);

Matches the finding's proposed fix (check cmd_array!=NULL).

Fix validation

Built patched dm.ko (DF-1639 + DF-1640 + DF-1642 fixes combined), kldload-ed, re-ran PoC:

DF-1639 reload(no cmd_data): rc=-1 errno=22 (Invalid argument)
EXIT=0

Guest stayed up. Baseline (unpatched) panicked every time. fix_status = fixed.

Validation method: dm is a KLD module (not in GENERIC), so the fix was validated by rebuilding dm.ko and reloading it β€” equivalent to a single-fix kernel rebuild for module-only bugs, and dramatically faster.

Fix verification

fixed

validated

baseline panic; patched returns EINVAL/ENOTSUP, guest up
↓ fix.diffdm.ko/dm_target_crypt.ko module rebuild atop 6.5-DEVELOPMENT #0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

REPRODUCED (live panic). dm_table_load prop_dictionary_get returns NULL -> prop_array_iterator(NULL) -> fault 0x40. Operator-group.