⬢ DragonFlyBSD Kernel Audit
DF-4178 / fix.diff
← back to finding ↓ download raw
fix for DF-4178: twe_immediate_request timeout path releases a request that is
still posted to the controller and still linked on the busy queue.

Two hunks:
 1. twe_immediate_request: if the 5-second poll times out, unmap, remove the
    request from the busy queue and mark it TWE_CMD_ERROR before returning,
    so the caller's twe_release_request() cannot double-link the request
    (busy + free) and a later completion cannot process a reused request.
 2. twe_done: if a response arrives for a request that is not busy (the
    "completion event for nonbusy command" case - e.g. a late completion of
    a timed-out, already-released request), consume the response and skip
    all processing instead of falling through to twe_remove_busy() on a
    request that is no longer on the busy queue (TAILQ corruption).

Residual (documented, not fixed here): after a timeout the caller still
frees the 512-byte parameter buffer that the controller may late-write;
fully closing that requires aborting the command on the controller
(TWE_OP_ABORT) and waiting for its completion before freeing.
--- a/sys/dev/raid/twe/twe.c
+++ b/sys/dev/raid/twe/twe.c
@@ -1007,6 +1007,19 @@
     if (usetmp && (tr->tr_data != NULL))
 	bcopy(sc->twe_immediate, tr->tr_data, tr->tr_length);
 
+    /*
+     * Timed out.  The command may still be posted to the controller and
+     * linked on the busy queue; take it off the queue and mark it failed
+     * so that the caller may safely release the request.  If the
+     * controller completes the command after we have given up, twe_done()
+     * will observe a nonbusy request and drop the stale response instead
+     * of corrupting the queue linkage.
+     */
+    if (tr->tr_status == TWE_CMD_BUSY) {
+	twe_unmap_request(tr);
+	twe_remove_busy(tr);
+	tr->tr_status = TWE_CMD_ERROR;
+    }
     return(tr->tr_status != TWE_CMD_COMPLETE);
 }
 
@@ -1200,8 +1213,12 @@
 #ifdef TWE_DEBUG
 	    cmd = TWE_FIND_COMMAND(tr);
 #endif
-	    if (tr->tr_status != TWE_CMD_BUSY)
+	    if (tr->tr_status != TWE_CMD_BUSY) {
 		twe_printf(sc, "completion event for nonbusy command\n");
+		/* stale response for a request that timed out and was
+		 * released/reused: consume it, never touch the request */
+		continue;
+	    }
 	    tr->tr_status = TWE_CMD_COMPLETE;
 	    debug(3, "completed request id %d with status %d", 
 		  cmd->generic.request_id, cmd->generic.status);