Short-buffer read() on /dev/udev silently drops the event and reports ENOMEM
| Field | Value |
|---|---|
| ID | DF-2807 |
| Status | new |
| Severity | Info |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:N |
| CWE | CWE-404 Improper Resource Shutdown (event loss) |
| File | sys/kern/kern_udev.c |
| Lines | 842-860 |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-08-31 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | base:kern |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
When uio_resid < strlen(xml)+1 the handler sets error = ENOMEM but still advances the reader marker past the event and reaps it: the event is consumed and destroyed without being delivered β silent event loss to a reader issuing a too-small read(), plus a misleading errno. Root-gated; consumers using small buffers would silently miss hotplug events. Fix: on the short-buffer path break WITHOUT moving the marker and return EMSGSIZE so the reader can retry with a larger buffer.
Timeline
- 2026-08-31 Discovered during pass-2 audit of kern_udev.c (GLM 5.3).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2807 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 714 B | β raw | |
| VERDICT.md | β | 1.6 KB | β raw | |
| build.sh | β | 69 B | view raw | |
| env.txt | β | 509 B | view raw | |
| fix.diff | β | 1.3 KB | view raw | |
| manifest.json | β | 558 B | view raw | |
| run.log | β | 544 B | view raw | |
| run.sh | β | 95 B | view raw | |
| udev_short.c | β | 2.5 KB | view raw | |
| verdict.json | β | 2.7 KB | view raw |
DF-2807 β udev short read drops event, reports ENOMEM (Info-tier hardening verification)
What
udev short read drops event, reports ENOMEM
Build
cc -Wall -O2 -o udev_short udev_short.c
Run
./udev_short (as root, timeout 45)
Expected
baseline (stock kernel): see verdict below fix kernel (all-41-fixes build): the bad behavior is gone
Files
README.md VERDICT.md build.sh env.txt fix.diff manifest.json run.log run.sh udev_short.c verdict.json
Shared harness: h41.c (combined KLD, sysctl debug.h41.cmd=N driven).
Full batch context: the fix kernel contains all 41 hardening fixes of
this batch compiled together (build RC=0) and passed the complete A/B
suite plus a clean reboot.
DF-2807 β VERDICT
status: reproduced | reproduced: 1 | impact: denial_of_service | confidence: certain
Mechanically reproduced as root on the stock guest with a single /dev/udev reader (O_NONBLOCK): after the mandatory initiation read arms the marker (kern_udev.c:826-829), two distinctive events were generated (vnconfig attach family, h41 make_dev). A 1-byte read returned -1/ENOMEM AND consumed the first (564-byte) event: the follow-up full read delivered only the 824-byte second event - the first event was silently destroyed without delivery (kern_udev.c:846-860 advances the marker and reaps even on the short-buffer error). On the fix kernel the 1-byte read returns -1/EMSGSIZE (errno 40) and the first full read delivers the SAME 564-byte event first (verified against a no-short-read control sequence). Fix iteration: v1 (skip marker advance only) panicked the guest with prop_object_release ocnt!=0 because udev_event_externalize consumes the event dict reference (kern_udev.c:571) - v3 makes externalize non-consuming and moves the reference drop to the delivery point; validated stable across the full A/B suite.
Fix-validation panic (v1)
Fix v1 (skip marker advance only) panicked the guest during A/B: panic: assertion "ocnt != 0" failed in prop_object_release udev_dev_read() ... udev_event_externalize consumes the event dict reference (kern_udev.c:571), so a retried read double-releases. v3 makes externalize non-consuming and drops the reference at the delivery point; validated stable.
Baseline vs fix kernel
baseline: see verdict above / run log fix: behavior changed as designed (see verdict)
Fix verification
fixedA/B: baseline ENOMEM + silent event loss; fix kernel EMSGSIZE + event preserved. v1 fix double-release (prop_object_release panic) caught by fix validation and reworked to a non-consuming externalize.
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log baseline: short read errno=12 + 564-byte event never delivered; fix kernel: errno=40 + 564-byte event delivered first (control: ab0 sequence shows 564B event is the queue head)
PoC changes
reader must be initiated first (first read arms the marker; udev_initiated_count gates event queueing); O_NONBLOCK to avoid blocking drain reads; exact-name XML extraction
Verified recommended fix
Short buffer: kfree xml, EMSGSIZE, no marker advance; move the event dict release to the delivery point
Verdict
Mechanically reproduced as root on the stock guest with a single /dev/udev reader (O_NONBLOCK): after the mandatory initiation read arms the marker (kern_udev.c:826-829), two distinctive events were generated (vnconfig attach family, h41 make_dev). A 1-byte read returned -1/ENOMEM AND consumed the first (564-byte) event: the follow-up full read delivered only the 824-byte second event - the first event was silently destroyed without delivery (kern_udev.c:846-860 advances the marker and reaps even on the short-buffer error). On the fix kernel the 1-byte read returns -1/EMSGSIZE (errno 40) and the first full read delivers the SAME 564-byte event first (verified against a no-short-read control sequence). Fix iteration: v1 (skip marker advance only) panicked the guest with prop_object_release ocnt!=0 because udev_event_externalize consumes the event dict reference (kern_udev.c:571) - v3 makes externalize non-consuming and moves the reference drop to the delivery point; validated stable across the full A/B suite.
No comments yet.