โฌข DragonFlyBSD Kernel Audit
โ† dashboard
DF-0023

Missing return after EINVAL in sys_read/sys_write/sys_extpwrite bypasses nbyte>SSIZE_MAX guard

Field Value
ID DF-0023
Status new
Severity Info
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:N
CWE CWE-754 Improper Check for Exceptional Conditions; CWE-696 Incorrect Behavior Order
File sys/kern/sys_generic.c
Lines 130-131, 161-162, 336-337, 368-369
Area kern
Confidence certain
Discovered 2026-06-29
Reported pending

Summary

In sys_read, sys_write, and sys_extpwrite, the guard if ((ssize_t)uap->nbyte < 0) error = EINVAL; assigns to a local error but does not return, so the dead-stored EINVAL is overwritten by the subsequent kern_preadv/kern_pwritev call and the ssize_t-range validation of the user-supplied size_t nbyte becomes a no-op. The sibling sys_extpread (:161-162) implements the same check correctly as return(EINVAL);, proving intent. A caller passing nbyte > SSIZE_MAX (bit 63 set) now proceeds into kern_preadv/kern_pwritev with auio.uio_resid set to that huge value. No kernel memory corruption or info leak is produced (downstream uiomove caps per-call counts and copyout/ copyin enforce the user address range), so this is a validation-correctness / defense-in-depth defect: the documented SSIZE_MAX contract is silently broken for every downstream file-ops implementation.

Root cause

sys/kern/sys_generic.c:

/* sys_read :130-131, sys_write :336-337, sys_extpwrite :368-369 */
if ((ssize_t)uap->nbyte < 0)
    error = EINVAL;          /* missing return */

aiov.iov_base = uap->buf;
aiov.iov_len  = uap->nbyte;
...
auio.uio_resid = uap->nbyte;     /* huge value reaches kern_preadv/pwritev */

Contrast the correct sibling sys_extpread :161-162:

if ((ssize_t)uap->nbyte < 0)
    return(EINVAL);

nbyte is size_t (sys/sys/sysproto.h); auio.uio_resid is size_t (sys/sys/_uio.h:69), so the huge value propagates verbatim.

Threat model & preconditions

  • Attacker position: any local unprivileged user.
  • Privileges gained or impact: none demonstrated. read/write/ extpwrite with nbyte > SSIZE_MAX proceed (loop until a natural boundary โ€” EOF / empty socket buffer / EFAULT from the exhausted user buffer) instead of returning EINVAL. The ssize_t return value's range contract is broken, which could confuse a caller; downstream uiomove/copyout/copyin bound the actual transfer, so no kernel memory-safety impact was reproduced. Recorded as a correctness/defense-in-depth fix.
  • Required config or capabilities: none; default kernel.
  • Reachability: read(2)/write(2)/extpwrite(2) with nbyte > SSIZE_MAX.

Proof of concept

PoC source: findings/poc/DF-0023/einval_noop.c

Build & run (unprivileged)

cc -o einval_noop findings/poc/DF-0023/einval_noop.c
./einval_noop

Expected output (bug present)

read(fd,buf,SIZE_MAX) = 0, errno=0 (NOT EINVAL)
write(fd,buf,SSIZE_MAX+1) = 0, errno=0 (NOT EINVAL)

(Fixed: both -1, errno=EINVAL.)

Impact

Correctness / defense-in-depth. The SSIZE_MAX invariant relied on by the ssize_t return value and by any driver doing signed resid math is broken; no memory-safety impact was demonstrated because uiomove/copyout bound the transfer. Rated Info.

Add the missing return (mirroring sys_extpread):

--- a/sys/kern/sys_generic.c
+++ b/sys/kern/sys_generic.c
@@ -130,7 +130,7 @@
    if ((ssize_t)uap->nbyte < 0)
-       error = EINVAL;
+       return (EINVAL);
@@ -336,7 +336,7 @@
    if ((ssize_t)uap->nbyte < 0)
-       error = EINVAL;
+       return (EINVAL);
@@ -368,7 +368,7 @@
    if ((ssize_t)uap->nbyte < 0)
-       error = EINVAL;
+       return (EINVAL);

References

Timeline

  • 2026-06-29 Discovered during automated file-by-file audit of sys/kern/sys_generic.c.
  • pending Reported to DragonFlyBSD security contact.