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

throw_rude_remark strcpy of up-to-1024-byte message into ioctl_reply->msg[256]: up to 4 bytes past the VINUM_CREATE ioctl allocation, plus dangling static ioctl_reply (UAF write)

Summary

kvasnprintf caps at 1024 but msg is char[256]; on VINUM_CREATE the reply overlays the 1024-byte ioctl buffer so a maximal error message writes 3 chosen chars + NUL past the kmalloc(1024). Separately, parse_user_config clears the static ioctl_reply only on the normal path and remove() never clears it: after a longjmp'd CREATE or any REMOVE the static dangles at the freed ioctl buffer, and a later throw from an unlocked ioctl strcpy's message text into freed memory (cross-thread lock race). strlcpy + setjmp-safe-clear fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4139 Β· 3 files
FileTypeDescriptionSize
README.md file 2.8 KB ↓ raw
manifest.json file 443 B view raw
verdict.json file 1.4 KB view raw
README.md file
↓ download raw

DF-4139 β€” throw_rude_remark() strcpy of a up-to-1024-byte message into

ioctl_reply->msg[256] (up to 4 bytes past the VINUM_CREATE ioctl

buffer), plus a dangling static ioctl_reply after longjmp/remove

Root cause

sys/dev/raid/vinum/vinumconfig.c:108-117:

    if ((ioctl_reply != NULL)
    &&(!(vinum_conf.flags & VF_READING_CONFIG))) {
        kvasnprintf(&text, MSG_MAX, msg, ap);   /* up to 1024 bytes */
        strcpy(ioctl_reply->msg, text);     /* msg is char[256]! */
        ioctl_reply->error = error;
  • MSG_MAX is 1024 (vinumconfig.c:90) but the destination is struct _ioctl_reply { int error; char msg[MAX_IOCTL_REPLY]; } with MAX_IOCTL_REPLY 256 in non-VINUMDEBUG builds (vinumio.h:42-45).
  • For VINUM_CREATE the reply struct overlays the 1024-byte ioctl buffer (BUFSIZE, vinumio.h:113,116), so strcpy writes at data+4 and a maximally long message (e.g. throw_rude_remark(EINVAL, "Invalid configuration information: %s", token[0]) with a ~988-byte first token) writes 4 + 1024 = 1028 bytes into the kmalloc(1024) buffer β€” 4 bytes past the end of the kernel heap allocation (sys/kern/sys_generic.c:675 allocates exactly 1024 for this ioctl). The last 3 attacker-controlled characters plus the NUL land in the adjacent kmalloc object.
  • Secondary lifetime defect: parse_user_config() clears the static ioctl_reply only on the normal path (vinumconfig.c:1683). When parse_config longjmps out via throw_rude_remark, and after every remove() call (vinumconfig.c:1693 never clears it), the static keeps pointing at the ioctl buffer, which sys_ioctl frees on return. A later throw_rude_remark reached from a different ioctl while another thread holds the config lock (VINUM_ATTACH/DETACH/MOVE paths call give_sd_to_plex/give_sd_to_drive, which throw) then performs a use-after-free write of the message text into the freed 260/1024- byte chunk. (Without the lock held it panics first β€” see DF-4136.)

Threat

Root/operator via /dev/vinum/control. The 4-byte overflow is small but is a genuine cross-object heap write with chosen ASCII content.

Fix

--- a/sys/dev/raid/vinum/vinumconfig.c
+++ b/sys/dev/raid/vinum/vinumconfig.c
@@ -112,7 +112,7 @@ throw_rude_remark(int error, char *msg,...)
     * it may contain our input parameters
     */
        kvasnprintf(&text, MSG_MAX, msg, ap);
-       strcpy(ioctl_reply->msg, text);
+       strlcpy(ioctl_reply->msg, text, sizeof(ioctl_reply->msg));

and clear the static ioctl_reply = NULL in a setjmp-safe place (e.g. at the top of the VINUM_CREATE error branch in vinumioctl.c and at the end of remove()).

Verification

Skipped live: Low severity (4-byte overflow, root-gated); the dangling static UAF additionally requires a cross-thread lock race. Bounds math is certain from source (MSG_MAX vs MAX_IOCTL_REPLY vs BUFSIZE).

Fix verification

not_testable
per-fix-DF-4139

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md bounds math (MSG_MAX vs MAX_IOCTL_REPLY vs BUFSIZE)

PoC changes

n/a - analysis only

Verified recommended fix

strlcpy bounded by sizeof(ioctl_reply->msg); clear the static ioctl_reply setjmp-safely.

Verdict

throw_rude_remark strcpy's a kvasnprintf buffer capped at MSG_MAX=1024 into ioctl_reply->msg which is char[256] in non-debug builds (vinumio.h:42-45); on VINUM_CREATE the reply overlays the 1024-byte ioctl buffer so a maximal message writes 4+1024=1028 bytes - up to 4 bytes (3 chosen chars + NUL) past the kmalloc(1024) allocation (sys_generic.c:675). Additionally the static ioctl_reply dangles after any longjmp out of parse_user_config or after remove(), enabling a message-text use-after-free write when another thread holds the config lock. Live test skipped: small overflow, root-gated; the dangling-pointer UAF needs a cross-thread race.