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)
PoC verification
Evidence pack
findings/poc/DF-4139 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | file | 2.8 KB | β raw | |
| manifest.json | file | 443 B | view raw | |
| verdict.json | file | 1.4 KB | view 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_MAXis 1024 (vinumconfig.c:90) but the destination isstruct _ioctl_reply { int error; char msg[MAX_IOCTL_REPLY]; }withMAX_IOCTL_REPLY256 in non-VINUMDEBUG builds (vinumio.h:42-45).- For
VINUM_CREATEthe reply struct overlays the 1024-byte ioctl buffer (BUFSIZE, vinumio.h:113,116), sostrcpywrites atdata+4and a maximally long message (e.g.throw_rude_remark(EINVAL, "Invalid configuration information: %s", token[0])with a ~988-byte first token) writes4 + 1024 = 1028bytes 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 staticioctl_replyonly on the normal path (vinumconfig.c:1683). Whenparse_configlongjmps out viathrow_rude_remark, and after everyremove()call (vinumconfig.c:1693 never clears it), the static keeps pointing at the ioctl buffer, whichsys_ioctlfrees on return. A laterthrow_rude_remarkreached from a different ioctl while another thread holds the config lock (VINUM_ATTACH/DETACH/MOVE paths callgive_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_testableConfirmed 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.
No comments yet.