/*
 * DF-3731 harness — XPT_PATH_INQ reports cpi->max_target = CISS_MAX_LOGICAL
 * (sys/dev/raid/ciss/ciss.c:2956) but ciss_logical[bus] rows are allocated
 * with exactly CISS_MAX_LOGICAL (15) entries (ciss.c:1377-1379, indices
 * 0..14). CAM's bus scan iterates targets 0..max_target INCLUSIVE
 * (sys/bus/cam/cam_xpt.c:5382: `for (i = 0; i <= max_target; i++)`), so
 * target 15 is probed on every rescan and
 *   sc->ciss_logical[bus][15].cl_status      (ciss_cam_emulate, ciss.c:3159)
 *   sc->ciss_logical[bus][15] in XPT_CALC_GEOMETRY (ciss.c:2923)
 * read one struct ciss_ldrive PAST the row allocation — a recurring heap
 * OOB read at every bus scan/boot. If the adjacent heap happens to hold
 * CISS_LD_ONLINE (1) at the cl_status offset, the probe proceeds and
 * ciss_cam_complete_fixup (ciss.c:3321-3325) then dereferences the garbage
 * cl_ldrive/cl_lstatus pointers.
 *
 * Build: cc -O2 -Wall -o harness harness.c
 * Fixed: cc -DFIXED ... max_target = CISS_MAX_LOGICAL - 1.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CISS_MAX_LOGICAL	15			/* cissvar.h:49 */
#define CISS_LD_ONLINE		1

struct ld_status_rep {				/* cl_status offset proxy */
	unsigned long	pad1[2];		/* cl_address, cl_controller */
	int		cl_status;
	int		pad2;
};

static size_t offsetof_cl_status(void);

int main(void)
{
	struct ld_status_rep *row;
	size_t row_bytes = CISS_MAX_LOGICAL * sizeof(*row);
#ifdef FIXED
	int max_target = CISS_MAX_LOGICAL - 1;	/* the fix (ciss.c:2956) */
#else
	int max_target = CISS_MAX_LOGICAL;	/* ciss.c:2956 — off by one */
#endif
	int t, oob = 0;

	printf("== DF-3731 ciss max_target off-by-one vs ciss_logical rows ==\n");
	printf("ciss_logical[bus] row = %zu bytes (%d entries), cpi->max_target"
	       " = %d\n", row_bytes, CISS_MAX_LOGICAL, max_target);
	printf("CAM scan: for (i = 0; i <= max_target; i++)  (cam_xpt.c:5382)"
	       "\n\n");

	row = calloc(1, row_bytes + 16);	/* + canary */
	memset((char *)row + row_bytes, 0x5A, 16);
	/* one genuine ONLINE drive at target 3 */
	row[3].cl_status = CISS_LD_ONLINE;

	for (t = 0; t <= max_target; t++) {
		int in_row = t < CISS_MAX_LOGICAL;
		int status;

		/* ciss_cam_emulate: ciss.c:3159 — read of
		 * sc->ciss_logical[bus][t].cl_status */
		status = in_row ? row[t].cl_status :
			*(int *)((char *)row + row_bytes +
				 offsetof_cl_status());
		printf("  probe target %2d: read ciss_logical[0][%2d].cl_status"
		       " = %-3d %s\n", t, t, status,
		       in_row ? "" : "<== OOB (past row alloc)");
		if (!in_row)
			oob++;
	}

	printf("\n");
	if (oob) {
		printf("[BUG REPRODUCED] %d OOB read(s) of"
		       " ciss_logical[bus][15] — one struct past the row\n"
		       "allocation, executed on every CAM bus scan. Deeper"
		       " impact (garbage ONLINE ->\ncl_ldrive/cl_lstatus wild"
		       " deref in ciss_cam_complete_fixup) is"
		       " heap-layout-dependent.\n", oob);
		return 0;
	}
	printf("[FIXED] max_target = CISS_MAX_LOGICAL - 1: all probes in"
	       " bounds.\n");
	return 0;
}

static size_t offsetof_cl_status(void)
{
	struct ld_status_rep r;
	return (size_t)((char *)&r.cl_status - (char *)&r);
}
