Missing feature-arg-count validation in _init_features allows kernel panic via crafted dmsetup table string
- File:
sys/dev/disk/dm/flakey/dm_target_flakey.c - Lines: 131β137 (count overwrite at 131, weak guard at 132, OOB loop at 137)
- Severity: Medium
- CVSS 3.1:
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H - CWE: CWE-125 Out-of-bounds Read
- Confidence: certain
- Status: new
Summary
_init_features overwrites its argc parameter (the actual count of remaining
argv entries) with a user-supplied count parsed via atoi64 from the dmsetup
table params string. The only validation is argc > 6, which fails to catch
(a) counts that exceed the actual number of provided argv entries, and
(b) negative values produced by uint64_t β int truncation of large inputs
(e.g. "2147483648" β INT_MIN).
The subsequent while-loop reads past the valid argv slots, dereferencing
NULL pointers (zero-filled slots) or heap memory past the allocation, causing
an immediate kernel panic.
Root cause
In _init_features (dm_target_flakey.c:122-206), the parameter int argc
holds the actual count of remaining feature args (argc-4 from the caller at
:108).
At line 131, this is blindly overwritten:
argc = atoi64(*argv++); /* # of args for features */
atoi64 (device-mapper.c:584) returns uint64_t; the assignment to int
truncates and reinterprets as signed.
The guard at :132 is:
if (argc > 6) {
kprintf("Invalid # of feature args %d\n", argc);
return EINVAL;
}
This only rejects 7..INT_MAX. It does NOT reject:
-
Negative values:
atoi64("2147483648") = 0x80000000truncated toint = INT_MIN = -2147483648, andINT_MIN > 6is false.atoi64("4294967295") = 0xFFFFFFFF β int = -1,-1 > 6is false. -
Counts exceeding actual args: the original
argc(actual remaining slots) is destroyed by the overwrite, so the loop at:137(while (argc) { argc--; arg = *argv++; ...}) has no way to stop before walking off the end of the argv array.
The argv array is allocated in dm_table_init (dm_ioctl.c:824) as
kmalloc(sizeof(*argv) * n, M_DM, M_WAITOK | M_ZERO) with n=20 (flakey
never sets max_argc), so slots beyond the actual token count are NULL.
The first OOB read hits a NULL slot; strcmp(NULL, ...) at :142/:148 or
atoi64(NULL) at :157/:180/:190 immediately page-faults.
Contrast with dm_target_striped.c:92 which correctly cross-validates
argc != (2 + n * 2).
Threat model
Attacker position: write access to /dev/mapper/control, which is mode
0640 UID_ROOT GID_OPERATOR (device-mapper.c:181). On DragonFlyBSD, members
of the operator group (a common semi-privileged role for backup/shutdown
operators, not full root) can open the device for writing and issue
NETBSD_DM_IOCTL (device-mapper.c:258).
Trigger: send a DM_TABLE_LOAD command (dm_table_load_ioctl,
dm_ioctl.c:673) with type="flakey" and a params string whose
feature-arg-count field exceeds the actual tokens provided.
The kernel panics during table load β before any I/O is submitted β so the trigger is instantaneous and deterministic.
Impact: denial of service (system crash) requiring operator-group
membership, which is a meaningful privilege boundary on multi-user systems.
Proof of concept
Prerequisites: the attacker can open /dev/mapper/control for writing (root or
operator group). A valid block device must exist for argv[0]
(dm_pdev_insert must succeed before _init_features is reached).
Vector 1 β NULL deref (simplest, most reliable)
Params string: /dev/ada0 0 1 1 6
This produces argv = ["/dev/ada0","0","1","1","6"], argc=5 in flakey_init.
_init_features is called with (tfc, 1, &argv[4]).
At :131, argc = atoi64("6") = 6 (overwriting the actual count of 1).
At :132, 6 > 6 is false (passes).
Loop at :137: argc=5, arg = *argv++ = argv[5] = NULL (zero-filled slot).
strcmp(NULL, "drop_writes") at :142 β page fault β kernel panic.
Vector 2 β signed-wraparound bypass of the >6 guard
Params string: /dev/ada0 0 1 1 2147483648
At :131, argc = (int)atoi64("2147483648") = (int)0x80000000 = INT_MIN =
-2147483648. At :132, INT_MIN > 6 is false (bypasses guard). Loop runs
identically to Vector 1 and panics on the first OOB read.
Using dmsetup (if available from pkgsrc)
dmsetup create vuln << 'EOF'
0 100 flakey /dev/ada0 0 1 1 6
EOF
Using a minimal C program (portable to DragonFlyBSD)
#include <sys/ioctl.h>
#include <dev/disk/dm/netbsd-dm.h>
#include <prop/proplib.h>
/* Open /dev/mapper/control, build proplib dict with:
DM_IOCTL_COMMAND = "table", DM_IOCTL_NAME = "vuln",
cmd_data array with { type="flakey", start=0, length=100,
params="/dev/ada0 0 1 1 6" }
Issue NETBSD_DM_IOCTL twice: first "create" then "table"/"reload". */
/* The kernel panics during the table-load ioctl. */
Success criterion: immediate kernel panic
(Fatal trap 12: page fault while in kernel mode, fault va=0x0).
No memory corruption, no info leak, no privilege escalation β pure DoS.
Recommended fix
Preserve the actual remaining argc, validate the user-supplied count against
both sane bounds AND the actual number of available tokens, and reject negative
values from uint64_t β int truncation.
--- a/sys/dev/disk/dm/flakey/dm_target_flakey.c
+++ b/sys/dev/disk/dm/flakey/dm_target_flakey.c
@@ -122,11 +122,16 @@ static int _flakey_corrupt_buf(dm_target_flakey_config_t*, struct bio*);
static int
_init_features(dm_target_flakey_config_t *tfc, int argc, char **argv)
{
char *arg;
- unsigned int value;
+ unsigned int value;
+ int num_features;
if (argc == 0)
return 0;
- argc = atoi64(*argv++); /* # of args for features */
- if (argc > 6) {
- kprintf("Invalid # of feature args %d\n", argc);
+ num_features = (int)atoi64(*argv++); /* # of args for features */
+ argc--; /* consumed the count token */
+
+ /* Reject negative (uint64_t->int truncation), out-of-range, or
+ * counts exceeding the actual number of remaining argv entries. */
+ if (num_features < 0 || num_features > 6 || num_features > argc) {
+ kprintf("Invalid # of feature args %d (have %d available)\n",
+ num_features, argc);
return EINVAL;
}
- while (argc) {
+ argc = num_features;
+ while (argc) {
argc--;
arg = *argv++;
This mirrors the validation pattern already used by dm_target_striped.c:80-96,
where the user-supplied count n is cross-checked against the actual argc.
References
sys/dev/disk/dm/flakey/dm_target_flakey.c:131βargcoverwrite viaatoi64sys/dev/disk/dm/flakey/dm_target_flakey.c:132-135β weak>6guard misses negatives and over-countssys/dev/disk/dm/flakey/dm_target_flakey.c:137-139β OOB loop reads past argvsys/dev/disk/dm/dm_ioctl.c:824-830β argv allocation (20 slots, M_ZERO β NULL slots)sys/dev/disk/dm/device-mapper.c:181β/dev/mapper/controlmode 0640 root:operatorsys/dev/disk/dm/dm_target_striped.c:80-96β correct cross-validation pattern
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1981 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | readme | PoC trigger description | 442 B | β raw |
| VERDICT.md | verdict | verification narrative | 1012 B | β raw |
| fix.diff | suggested-fix | git-apply-able fix | 964 B | view raw |
| fix_build_summary.txt | build-log | combined 16-finding kernel build rc=0 | 826 B | view raw |
DF-1981 PoC β NULL-deref panic in _init_features via crafted flakey table
Preconditions
Attacker can open /dev/mapper/control for writing (mode 0640 root:operator β
so root or any member of the operator group). A valid block device must
exist at argv[0] (e.g. /dev/ada0).
Trigger
```sh
Vector 1: simplest -- feature count 6 but only 1 feature token provided
dmsetup create vuln << 'EOF' 0 100 flakey /dev/ada0 0 1 1 6
DF-1981 Verification
Verdict
SOURCE-CONFIRMED, INCONCLUSIVE-RUNTIME (module/root gated).
The cited defect exists in the audited source at sys/dev/disk/dm/flakey/dm_target_flakey.c:122-135.
dm flakey target is loaded as part of the dm module and requires root for
dmsetup ioctls (requires /dev/mapper/control write access).
Mechanism (source-only confirmation)
_init_features (L131) overwrites its argc param (int) with atoi64(argv++) which returns uint64_t. Assignment to int truncates; atoi64("2147483648") = 0x80000000 β int = INT_MIN. The guard at L132 "if (argc > 6)" misses negative values (INT_MIN > 6 is false β bypassed). The while(argc) loop with negative argc runs indefinitely, doing argv++ past the end of the argv array β OOB read β kernel panic (NULL deref).
Recommended fix
Store atoi64 result in an int64_t, validate range [0..6] BEFORE truncating to int, then assign the validated value to argc.
The full git apply-able diff lives in fix.diff in this folder.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- d
- i
- s
- k
- /
- d
- m
- /
- f
- l
- a
- k
- e
- y
- /
- d
- m
- _
- t
- a
- r
- g
- e
- t
- _
- f
- l
- a
- k
- e
- y
- .
- c
- :
- 1
- 3
- 1
- -
- 1
- 3
- 5
Detail
Exploit chain
none (module/root gated: OOB read -> panic requires root dmsetup access)
Evidence (decisive lines)
Combined kernel build: 16 fix.diffs applied, make -j6 nativekernel => rc=0, 0 warnings, 0 errors.
PoC changes
Created VERDICT.md, fix.diff (validate int64_t before truncation), manifest.json, env.txt, build.sh, run.sh.
Verified recommended fix
Store atoi64 result in int64_t, validate [0..6] BEFORE truncating to int. Supersedes finding proposal.
Verdict
SOURCE-CONFIRMED (module/root gated). _init_features (dm_target_flakey.c:131) overwrites its argc param (int) with atoi64(argv++) which returns uint64_t; assignment truncates. atoi64('2147483648')=0x80000000->int=INT_MIN. Guard at L132 'if (argc > 6)' misses negative values (INT_MIN>6 false -> bypassed). while(argc) with negative argc runs indefinitely, argv++ past array end -> OOB read -> panic. Confirmed by source trace. Not runnable: dm module, root-only ioctls.
No comments yet.