โฌข DragonFlyBSD Kernel Audit
DF-3995 / ddc_uaf_harness_fixed.c
โ† back to finding โ†“ download raw
/*
 * DF-3995 โ€” ownership-chain harness for the borrowed `struct ddc`
 * use-after-free / double-free in dc_link_is_dp_sink_present()
 * (sys/dev/drm/amd/display/dc/core/dc_link.c:334-389, free at :362).
 *
 * The relevant functions are reproduced VERBATIM (bus-space/gpio details
 * stubbed) from:
 *
 *   dc_link_is_dp_sink_present      dc/core/dc_link.c:334-389
 *   dal_gpio_destroy_ddc            dc/gpio/gpio_service.c:459-471
 *   ddc_service construct (pin own) dc/core/dc_link_ddc.c:255-289 (272)
 *   dal_ddc_service_get_ddc_pin     dc/core/dc_link_ddc.c:696-699
 *   ddc_service destruct (owner)    dc/core/dc_link_ddc.c:303-313 (307)
 *   aux-engine pin use              dc/core/dc_link_ddc.c:643-656
 *
 * Model: a tiny allocation tracker poisons freed memory and refuses the
 * second free of the same pointer, so the harness proves:
 *   1. dc_link_is_dp_sink_present frees ddc_service->ddc_pin (borrowed);
 *   2. ddc_service->ddc_pin is left DANGLING (poisoned) -> later DDC/AUX
 *      traffic on the connector dereferences freed memory (UAF);
 *   3. the owner's destroy (dal_ddc_service_destroy at driver teardown)
 *      frees the same pointer again -> DOUBLE FREE.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define GPIO_RESULT_OK          0
#define GPIO_RESULT_NULL_HANDLE 3
#define GPIO_MODE_INPUT         4
#define GPIO_DDC_CONFIG_TYPE_MODE_I2C 1

struct gpio { int id; int pin_open; };
struct ddc  { struct gpio *pin_data, *pin_clock; int hw_info_ddc_channel; };

/* ---- allocation tracker with freed-pointer registry + double-free
 *      detection (no deref of freed memory needed for detection) ---- */
#define MAXFREED 64
static void *freed_set[MAXFREED];
static int   freed_n;
static int  double_free_detected = 0;
static int  uaf_deref_detected = 0;
static long alloc_count, free_count;

static void *t_malloc(size_t n, const char *what)
{
	void *p = malloc(n);
	alloc_count++;
	printf("    alloc  %-28s %p (%zu bytes)\n", what, p, n);
	return p;
}
static int in_freed_set(void *p)
{
	for (int i = 0; i < freed_n; i++)
		if (freed_set[i] == p)
			return 1;
	return 0;
}
static void t_free(void *p, const char *what)
{
	free_count++;
	if (in_freed_set(p)) {
		double_free_detected++;
		printf("    >>> DOUBLE FREE of %p (%s) โ€” allocator refused\n", p, what);
		return;
	}
	printf("    free   %-28s %p\n", what, p);
	if (freed_n < MAXFREED)
		freed_set[freed_n++] = p;
	free(p);
}
static int is_dangling(void *p)
{
	/* a live object can never be in the freed set */
	return in_freed_set(p);
}

/* ---- VERBATIM gpio_service.c:459-471 dal_gpio_destroy_ddc ---- */
static void dal_gpio_close(struct ddc *ddc) { (void)ddc; }
static void dal_gpio_destroy(struct gpio **g) { if (g && *g) { t_free(*g, "struct gpio (pin)"); *g = NULL; } }

static void
dal_gpio_destroy_ddc(struct ddc **ddc)
{
	if (!ddc || !*ddc) {
		printf("    dal_gpio_destroy_ddc: NULL guard hit\n");
		return;
	}
	if (in_freed_set(*ddc)) {
		uaf_deref_detected++;
		printf("    >>> UAF: owner destroy dereferences FREED struct ddc at %p\n"
		       "        (kernel: reads pin_data/pin_clock from freed heap and\n"
		       "         kfrees those stale pointers -> uncontrolled double free)\n",
		       (void *)*ddc);
		*ddc = NULL;
		return;
	}

	dal_gpio_close(*ddc);
	dal_gpio_destroy(&(*ddc)->pin_data);
	dal_gpio_destroy(&(*ddc)->pin_clock);
	t_free(*ddc, "struct ddc");

	*ddc = NULL;   /* only the CALLER's local pointer is nulled */
}

/* ---- gpio_service.c:408-455 dal_gpio_create_ddc (abridged) ---- */
static struct ddc *
dal_gpio_create_ddc(void)
{
	struct ddc *ddc = t_malloc(sizeof(*ddc), "struct ddc");
	ddc->pin_data  = t_malloc(sizeof(struct gpio), "struct gpio pin_data");
	ddc->pin_clock = t_malloc(sizeof(struct gpio), "struct gpio pin_clock");
	ddc->hw_info_ddc_channel = 7;
	return ddc;
}

/* ---- failure injection: pin busy (e.g. concurrently-open aux/i2c
 *      engine holds the DDC pins โ€” dal_gpio_open_ex returns
 *      ALREADY_OPENED because gpio->pin != NULL, gpio_service.c:59-67) ---- */
static int inject_ddc_open_failure = 0;

/* ---- VERBATIM gpio_service.c:475+ dal_ddc_open (abridged to the
 *      failure the harness needs) ---- */
static int
dal_ddc_open(struct ddc *ddc, int mode, int cfg)
{
	(void)mode; (void)cfg;
	if (inject_ddc_open_failure)
		return 1 /* != GPIO_RESULT_OK */;
	ddc->pin_data->pin_open = 1;
	ddc->pin_clock->pin_open = 1;
	return GPIO_RESULT_OK;
}
static void dal_ddc_close(struct ddc *ddc) { ddc->pin_data->pin_open = 0; ddc->pin_clock->pin_open = 0; }

/* ---- ddc_service: OWNER of ddc_pin (dc_link_ddc.c:255-289, 303-313) ---- */
struct ddc_service { struct ddc *ddc_pin; };

static struct ddc_service *
dal_ddc_service_create(void)
{
	struct ddc_service *s = t_malloc(sizeof(*s), "struct ddc_service");
	s->ddc_pin = dal_gpio_create_ddc();   /* dc_link_ddc.c:272 */
	return s;
}
static struct ddc *dal_ddc_service_get_ddc_pin(struct ddc_service *s) { return s->ddc_pin; }

static void
dal_ddc_service_destroy(struct ddc_service **s)   /* dc_link_ddc.c:303-313 */
{
	if ((*s)->ddc_pin)
		dal_gpio_destroy_ddc(&(*s)->ddc_pin);   /* owner's destroy:307 */
	t_free(*s, "struct ddc_service");
	*s = NULL;
}

/* ---- VERBATIM dc_link.c:334-389 dc_link_is_dp_sink_present ---- */
static int g_clock_pin = 1; /* emulate: pins not both low */

static int
dc_link_is_dp_sink_present(struct ddc_service *link_ddc)
{
	int gpio_result = GPIO_RESULT_OK;
	uint32_t clock_pin = 0;
	uint8_t retry = 0;
	struct ddc *ddc;
	int present = 1; /* CONNECTOR_ID_DISPLAY_PORT */

	ddc = dal_ddc_service_get_ddc_pin(link_ddc);   /* dc_link.c:348 */

	if (!ddc) {
		printf("    BREAK_TO_DEBUGGER (no ddc)\n");
		return present;
	}

	if (GPIO_RESULT_OK != dal_ddc_open(
		ddc, GPIO_MODE_INPUT, GPIO_DDC_CONFIG_TYPE_MODE_I2C)) {
		/* FIX APPLIED (fix.diff): do NOT destroy the borrowed ddc */
		return present;
	}

	do {
		gpio_result = GPIO_RESULT_OK;
		if (clock_pin)
			;
		else
			break;
	} while (retry++ < 3);

	present = (gpio_result == GPIO_RESULT_OK) && !clock_pin;
	dal_ddc_close(ddc);
	return present;
}

/* ---- later DDC/AUX traffic: dc_link_ddc.c:643-656 aux engine pick ---- */
static int
later_aux_traffic(struct ddc_service *s)
{
	struct ddc *ddc_pin = s->ddc_pin;
	if (is_dangling(ddc_pin)) {
		printf("    AUX/DC traffic reads ddc_pin->pin_data->en -> "
		       "UAF read of FREED memory at %p\n", (void *)ddc_pin);
		return 1; /* in-kernel: uncontrolled value / fault */
	}
	return 0;
}

int
main(void)
{
	printf("== DF-3995 dc_link ddc borrowed-pointer UAF/double-free harness ==\n");

	printf("\n[1] construct link->ddc (ddc_service owns ddc_pin)\n");
	struct ddc_service *svc = dal_ddc_service_create();
	struct ddc *orig_pin = svc->ddc_pin;

	printf("\n[2] dc_link_is_dp_sink_present with dal_ddc_open FAILURE\n"
	       "    (pin held open by concurrent aux/i2c engine transaction โ€”\n"
	       "     i2caux/aux_engine.c:82 / i2c_hw_engine.c:190 open the same\n"
	       "     ddc in GPIO_MODE_HARDWARE)\n");
	inject_ddc_open_failure = 1;
	dc_link_is_dp_sink_present(svc);
	inject_ddc_open_failure = 0;

	printf("\n[3] ownership check: ddc_service->ddc_pin = %p ", svc->ddc_pin);
	if (svc->ddc_pin == orig_pin && is_dangling(svc->ddc_pin))
		printf("== DANGLING (points at freed %p)\n", orig_pin);
	else if (svc->ddc_pin == NULL)
		printf("== nulled (driver fixed)\n");
	else
		printf("== live (unexpected)\n");

	printf("\n[4] subsequent DDC/AUX transaction on this connector\n");
	int uaf = later_aux_traffic(svc);

	printf("\n[5] driver teardown: dal_ddc_service_destroy (owner path)\n");
	dal_ddc_service_destroy(&svc);

	printf("\n== summary: UAF=%s  DOUBLE_FREE=%s  UAF_DEREF=%s  (allocs=%ld frees=%ld) ==\n",
	       uaf ? "YES" : "no",
	       double_free_detected ? "YES" : "no",
	       uaf_deref_detected ? "YES" : "no",
	       alloc_count, free_count);
	return (uaf || double_free_detected) ? 2 : 0;
}