/*
 * DF-4027 — sili deferred-timeout ccb ownership leak (illustrative model).
 *
 * NOT a runnable guest PoC: the trigger requires a SiLion Image 3124/3132
 * HBA (sili(4)) with a device/port-multiplier target that lets a POLLED
 * command time out while another command is still active on the same
 * port.  The audit guest has no sili controller, so this finding is
 * verified by kernel-source trace (see VERDICT.md).
 *
 * The program below models the exact kernel state-machine interleaving
 * that sili.c permits, using the same state names, so the lifetimes can
 * be inspected without hardware.  Run it to print the double-lifecycle:
 *
 *     cc -O2 -o sili_model sili_model.c && ./sili_model
 */
#include <stdio.h>

enum {
	ATA_S_PUT, ATA_S_SETUP, ATA_S_PENDING, ATA_S_ONCHIP,
	ATA_S_COMPLETE, ATA_S_ERROR, ATA_S_TIMEOUT,
};
static const char *st[] = { "PUT", "SETUP", "PENDING", "ONCHIP",
	"COMPLETE", "ERROR", "TIMEOUT" };

/* mirrors sili_poll() + sili_core_timeout() (sili.c:1311-1360, 2451-2520) */
static const char *
sili_poll_model(int other_cmd_active, int *out_state)
{
	/* ccb on chip; device stalls past the poll timeout */
	*out_state = ATA_S_ONCHIP;
	/*
	 * sili_core_timeout(ccb, 0):
	 *   ap_expired |= 1<<slot;                       sili.c:2505
	 *   if (ap_active != ap_expired) return 1;       sili.c:2507-2511
	 */
	if (other_cmd_active)
		return ("DEFERRED (returned 1; state stays ONCHIP)");
	*out_state = ATA_S_TIMEOUT;
	return ("reinit path");
}

int
main(void)
{
	int state;

	printf("sili_ata_cmd(POLL) -> sili_poll():\n");
	printf("  probed ccb state: %s\n", st[ATA_S_ONCHIP]);
	printf("  poll timeout -> sili_ata_cmd_timeout -> sili_core_timeout:\n");
	printf("  %s\n", sili_poll_model(1, &state));
	printf("  sili_poll returns xa->state == %s to the caller\n",
	    st[state]);
	printf("  sili_cam.c:315 'if (sili_ata_cmd(xa) != ATA_S_COMPLETE)'\n");
	printf("    -> sili_ata_put_xfer(xa): sili_put_ccb() puts the ccb\n");
	printf("       on ap_ccb_free and bumps serial      (sili.c:2020-2030)\n");
	printf("    PORT STILL OWNS IT: ap_active bit set, ap_expired bit set\n\n");

	printf("next command reuses the freed ccb:\n");
	printf("  sili_get_ccb -> state==PUT check passes (sili.c:2010)\n");
	printf("  sili_ata_cmd -> sili_load_prb loads a NEW dmamap\n");
	printf("  sili_issue_pending_commands: ap_active |= 1<<slot AGAIN\n");
	printf("  -> ap_active_cnt inflated; two logical commands on one slot\n\n");

	printf("device finally completes / port drains:\n");
	printf("  sili_port_reinit expired loop (sili.c:403-413):\n");
	printf("    ccb->ccb_xa.state = ATA_S_TIMEOUT;\n");
	printf("    ccb->ccb_done(ccb);          <- sili_ata_cmd_done:\n");
	printf("       sili_unload_prb()         <- unloads the NEW command's\n");
	printf("                                   ACTIVE dmamap (DMA-writeback\n");
	printf("                                   onto reused memory)\n");
	printf("    ccb->ccb_xa.complete(...)    <- for a CAM command this is\n");
	printf("       sili_ata_complete_disk_rw on the REISSUED ccb: xpt_done\n");
	printf("       on a CAM ccb that is already owned by the new request\n");
	printf("       -> double CAM completion / UAF in the CAM layer\n");
	printf("  KKASSERT(ap->ap_active_cnt == 0) in sili_port_hardstop can\n");
	printf("  also fire (underflow) on INVARIANTS kernels.\n");
	return (0);
}
