/*
 * DF-3730 harness — ciss_cam_complete_fixup() (sys/dev/raid/ciss/ciss.c:
 * 3323-3325) rewrites the INQUIRY result of logical volumes:
 *
 *   padstr(inq->vendor,  "COMPAQ", 8);                                  ok (8-byte field)
 *   padstr(inq->product, ciss_name_ldrive_org(...), 8);                 ok (16-byte field)
 *   padstr(inq->revision, ciss_name_ldrive_status(...), 16);   <-- 16 bytes into the
 *                                                                     4-byte revision
 *                                                                     field
 * (scsi_all.h:739: char revision[SID_REVISION_SIZE=4], at buffer offset 32)
 *
 * The only gate is csio->dxfer_len >= SHORT_INQUIRY_LENGTH (36) (ciss.c:3305),
 * so a CAM client that issues a standard 36-byte INQUIRY (e.g. via the CAM
 * pass-through: camcontrol) gets 12 bytes written past its 36-byte data
 * buffer — kernel heap overflow with (limited) string content.
 *
 * Build: cc -O2 -Wall -o harness harness.c
 * Fixed: cc -DFIXED ... uses SID_REVISION_SIZE (4) for the revision padstr.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

/* ---- real kernel constants (scsi_all.h) ---- */
#define SHORT_INQUIRY_LENGTH	36			/* scsi_all.h:652 */
#define SID_VENDOR_SIZE		8			/* scsi_all.h:734 */
#define SID_PRODUCT_SIZE	16			/* scsi_all.h:736 */
#define SID_REVISION_SIZE	4			/* scsi_all.h:738 */
/* struct scsi_inquiry_data prefix: device(1) reserved(1) version(1)
 * ... flags area ... vendor@8 product@16 revision@32 — per SPC/SPC-2 layout
 * used by scsi_all.h. */

/* cissvar.h:374-384 — exact replica of padstr() */
static int padstr_checking(char *targ_base, size_t targ_off, size_t targ_len,
			   const char *src, int len, size_t bufsize)
{
	size_t i;
	int overflow = 0;

	for (i = 0; i < (size_t)len; i++) {
		char c = (*src != 0) ? *src++ : ' ';
		size_t at = targ_off + i;
		if (at >= bufsize) {
			if (!overflow)
				fprintf(stderr, "    write at buffer+%zu"
					" (>= %zu)\n", at, bufsize);
			overflow++;
		} else if (at >= targ_off + targ_len) {
			fprintf(stderr, "    write at %zu past field end %zu"
				" (still inside buffer)\n", at,
				targ_off + targ_len);
		}
		((volatile char *)targ_base)[at] = c;	/* the actual store */
	}
	return overflow;
}

static const char *
status_name(int s)					/* ciss_name_ldrive_status */
{
	return s == 0 ? "OK" : s == 1 ? "failed" : "unknown status";
}

int main(void)
{
	char *buf;					/* the CAM data buffer */
	size_t dxfer_len = SHORT_INQUIRY_LENGTH;	/* 36-byte INQUIRY */
	size_t alloc = 64;				/* 36 + 28 canary */
	int over = 0, i;

	printf("== DF-3730 ciss_cam_complete_fixup padstr overflow ==\n");
	printf("CAM client INQUIRY: dxfer_len=%zu (passes the ciss.c:3305 gate"
	       " >= %d)\n\n", dxfer_len, SHORT_INQUIRY_LENGTH);

	buf = malloc(alloc);
	memset(buf, 0, alloc);
	for (i = dxfer_len; i < (int)alloc; i++)	/* canary past buffer */
		buf[i] = (char)(0xC0 + (i - dxfer_len));

	/* ciss.c:3323-3325 — the three padstr calls, offsets per
	 * struct scsi_inquiry_data */
	over += padstr_checking(buf, 8, SID_VENDOR_SIZE, "COMPAQ", 8, dxfer_len);
	over += padstr_checking(buf, 16, SID_PRODUCT_SIZE, "RAID 5", 8, dxfer_len);
	over += padstr_checking(buf, 32, SID_REVISION_SIZE, status_name(1),
#ifdef FIXED
				SID_REVISION_SIZE,
#else
				16,			/* ciss.c:3325 — the bug */
#endif
				dxfer_len);

	/* show the smashed canary */
	printf("    buffer[32..47] = ");
	for (i = 32; i < 48; i++)
		printf("%02x ", (unsigned char)buf[i]);
	printf("\n    canary  [36..47] was 0xc0..0xcb\n\n");

	if (over > 0) {
		printf("[BUG REPRODUCED] %d bytes written past the end of the"
		       " %zu-byte INQUIRY data\n"
		       "buffer (revision field is 4 bytes at offset 32; padstr"
		       " writes 16).\nKernel impact: 12-byte heap overflow past"
		       " a kmalloc'd CAM buffer with\nstatus-string content"
		       " (root/operator via CAM pass-through).\n",
		       over, dxfer_len);
		return 0;
	}
	printf("[FIXED] revision padstr bounded to SID_REVISION_SIZE (4):"
	       " no bytes past the buffer.\n");
	return 0;
}
