⬢ DragonFlyBSD Kernel Audit
DF-4503 / trigger_DF4503.c
← back to finding ↓ download raw
/*
 * DF-4503 trigger (post-condition half).
 *
 * Bug: ahci_pm_read()/ahci_pm_write() call ahci_ata_put_xfer() on ANY
 * non-ATA_S_COMPLETE return. When ahci_poll()'s timeout handler defers
 * ("Deferred timeout until its safe", ahci.c:3881) because OTHER commands
 * are still active on the port, ahci_ata_cmd() returns with
 * xa->state == ATA_S_ONCHIP -- the command is STILL PROGRAMMED ON THE
 * CHIP -- and the PM register accessors then recycle the CCB into the
 * free pool while the chip still owns the slot. The eventual completion
 * (or deferred-expiry processing) operates on a CCB that is marked
 * ATA_S_PUT or has been re-issued for an unrelated command:
 *
 *   - ahci_ata_cmd_done(): KKASSERT(xa->state != ATA_S_ONCHIP &&
 *     xa->state != ATA_S_PUT)  ahci.c:3775  -> panic on INVARIANTS kernels
 *   - or the deferred-expiry loop (ahci.c:3894-3910) overwrites the state
 *     of a CCB that has already been re-issued -> cross-command completion
 *     corruption.
 *
 * Preconditions (hardware): SATA port multiplier with FBS enabled
 * (AP_F_FBSS_ENABLED; ahci_pm_port_probe enables it when controller+PM
 * support it) so that more than one standard command can be co-active
 * (ahci.c:2426-2433 limit>1), concurrent disk I/O on another PM target,
 * and a PM register access (e.g. the EINFO hotplug bitmap read,
 * ahci.c:841, or ahci_pm_check_good, ahci.c:891) that times out (1s)
 * while the co-issued I/O is still outstanding. PM register commands are
 * issued from the port state machine OUTSIDE beg/end_exclusive_access,
 * which is exactly the window.
 *
 * This program is the load half: keep the disks behind the PM busy while
 * poking the CAM bus so hotplug/notify processing keeps issuing PM
 * register reads. Run as root alongside an aggressive rescan loop
 * (run.sh).
 *
 * Usage: ./trigger_DF4503 <disk-device> [seconds]
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <err.h>

int
main(int argc, char **argv)
{
	char buf[65536];
	const char *dev = (argc > 1) ? argv[1] : "/dev/da1";
	long secs = (argc > 2) ? strtol(argv[2], NULL, 0) : 60;
	struct timespec start, now;
	int fd;
	long n = 0;

	fd = open(dev, O_RDONLY);
	if (fd < 0)
		err(1, "open %s", dev);

	if (clock_gettime(CLOCK_MONOTONIC, &start) < 0)
		err(1, "clock_gettime");

	for (;;) {
		if (pread(fd, buf, sizeof(buf), (off_t)(n % 1024) * 65536) < 0)
			err(1, "pread");
		n++;
		clock_gettime(CLOCK_MONOTONIC, &now);
		if (now.tv_sec - start.tv_sec >= secs)
			break;
	}
	printf("done: %ld reads on %s\n", n, dev);
	return (0);
}