⬢ DragonFlyBSD Kernel Audit
DF-4614 / doublefree_harness.c
← back to finding ↓ download raw
/*
 * DF-4614 harness: control-flow proof of the ips command-queue double-free.
 *
 * This mirrors, statement for statement, the DRAMAS-relevant control flow of
 * sys/dev/raid/ips/ips.c:
 *
 *   ips_adapter_init()                       [ips.c:374-464]
 *     sc->max_cmds = 1;                      [ips.c:413]
 *     ips_cmdqueue_init(sc);                 [ips.c:414]  <-- RETURN IGNORED
 *       (bus_dmamem_alloc fails)             [ips.c:164-167 -> goto error]
 *       ips_cmdqueue_free(sc);               [ips.c:190]
 *         kfree(sc->commandarray)            [ips.c:140]  <-- #1, pointer NOT nulled
 *         return ENOMEM                      (discarded by caller)
 *     ips_adapter_reinit(sc, 0)              [ips.c:416]  (card works; succeeds)
 *     ips_ffdc_reset(sc)                     [ips.c:421]  -> ips_get_free_cmd sees
 *                                                          IPS_OFFLINE -> EIO
 *       -> goto error                        [ips.c:424]
 *   error:
 *     ips_adapter_free(sc)                   [ips.c:462]
 *       ips_cmdqueue_free(sc)                [ips.c:533]
 *         reads sc->commandarray[0].command_phys_addr   [ips.c:125] <-- UAF read
 *         kfree(sc->commandarray)            [ips.c:140]  <-- #2 DOUBLE FREE
 *
 * The bus_dma* calls are stubbed; bus_dmamem_alloc() is forced to fail the way
 * it does under DMA-map exhaustion. The allocator aborts on double free so the
 * demonstration is unambiguous.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

/* ---------- stubs of DragonFly primitives ---------- */
static int  kfree_calls;
static void *last_freed;

static void *kmalloc_(size_t n) { void *p = calloc(1, n); if (!p) abort(); return p; }
static void  kfree_(void *p)
{
	kfree_calls++;
	if (p == last_freed) {
		printf("[!] DOUBLE FREE: kfree(#%d) on %p already freed at kfree(#%d)\n",
		       kfree_calls, p, kfree_calls - 1);
		exit(42); /* allocator would corrupt its free lists here */
	}
	last_freed = p;
	free(p);
}

static int bus_dmamem_alloc_fail; /* flip to force the failure mode */

/* ---------- mirrored driver types (ips.h) ---------- */
struct ips_command {
	uint8_t command_number, id, timeout;
	void   *sc;
	void   *command_dmamap;
	void   *command_buffer;
	uint32_t command_phys_addr;
	void   *data_dmatag, *data_dmamap;
	uint32_t status;
	struct ips_command *next;
	void   *data_buffer, *arg, *callback;
	int     completed;
};
struct ips_softc {
	int state;
	int max_cmds;
	int used_commands;
	struct ips_command *commandarray;
	struct ips_command *staticcmd;
};

#define IPS_OFFLINE 0x04

/* ---------- ips_cmdqueue_init, ips.c:152-192 (logic mirrored) ---------- */
static void ips_cmdqueue_free_sc(struct ips_softc *sc);	/* ips.c free, below */

static int ips_cmdqueue_init(struct ips_softc *sc)
{
	int i;
	struct ips_command *command;

	sc->commandarray = kmalloc_(sizeof(sc->commandarray[0]) * sc->max_cmds);
	for (i = 0; i < sc->max_cmds; i++) {
		command = &sc->commandarray[i];
		command->id = i;
		/* bus_dmamem_alloc(sc->command_dmatag, ..., BUS_DMA_NOWAIT) */
		if (bus_dmamem_alloc_fail)
			goto error;		/* ips.c:167 */
		command->command_phys_addr = 0xdead0000 + i;
		if (i == 0)
			sc->staticcmd = command;
	}
	sc->state &= ~IPS_OFFLINE;
	return 0;
error:
	ips_cmdqueue_free_sc(sc);	/* ips.c:190 */
	return 12; /* ENOMEM */
}

/* ips.c:116-143, logic mirrored (forward-declared above) */
static void ips_cmdqueue_free_sc(struct ips_softc *sc)
{
	int i;
	struct ips_command *command;

	if (sc->used_commands == 0) {
		for (i = 0; i < sc->max_cmds; i++) {
			command = &sc->commandarray[i];   /* ips.c:124 */
			if (command->command_phys_addr == 0)	/* ips.c:125 UAF READ on 2nd call */
				continue;
			/* bus_dmamem_unload / bus_dmamem_free / bus_dmamap_destroy on
			 * stale pointers would run here */
		}
		sc->state |= IPS_OFFLINE;		/* ips.c:137 */
	}
	sc->staticcmd = NULL;
	kfree_(sc->commandarray);			/* ips.c:140: unconditional */
}

/* ---------- ips_adapter_free, ips.c:525-548 (relevant part) ---------- */
static int ips_adapter_free(struct ips_softc *sc)
{
	ips_cmdqueue_free_sc(sc);			/* ips.c:533 */
	return 0;
}

/* ---------- ips_adapter_init, ips.c:374-464 (relevant part) ---------- */
static int ips_adapter_init(struct ips_softc *sc)
{
	sc->max_cmds = 1;				/* ips.c:413 */
	ips_cmdqueue_init(sc);				/* ips.c:414: RETURN IGNORED */
	/*
	 * sc->ips_adapter_reinit(sc, 0): the card itself is healthy, register
	 * POST checks pass -- this succeeds.  [ips.c:416]
	 */
	/*
	 * ips_ffdc_reset -> ips_get_free_cmd: state has IPS_OFFLINE (set inside
	 * the failed init), so it returns EIO -> "failed to send ffdc reset"
	 * -> goto error.  [ips.c:421-425]
	 */
	ips_adapter_free(sc);				/* ips.c:462 */
	return 6; /* ENXIO */
}

int main(void)
{
	static struct ips_softc sc;	/* zeroed, like device_get_softc */

	bus_dmamem_alloc_fail = 1;	/* force the NOWAIT allocation failure */

	ips_adapter_init(&sc);
	printf("unreachable: allocator corruption exit expected\n");
	return 0;
}