⬢ DragonFlyBSD Kernel Audit
DF-3891 / double_free.c
← back to finding ↓ download raw
/*
 * DF-3891 harness — double free of adev->pm.dpm.ps on the kzalloc-failure
 * path of kv_parse_power_table.
 *
 * Mirrors sys/dev/drm/amd/amdgpu/kv_dpm.c:2736-2752 (kv_parse_power_table)
 * and :2912-2922 (kv_dpm_fini), with malloc/free standing in for
 * kzalloc/kfree. Sequence in the kernel:
 *
 *   kv_parse_power_table:
 *     adev->pm.dpm.ps = kcalloc(ucNumEntries, sizeof(struct amdgpu_ps));  :2736
 *     for (i = 0; i < ucNumEntries; i++) {
 *         ps = kzalloc(sizeof(struct kv_ps));                              :2748
 *         if (ps == NULL) {
 *             kfree(adev->pm.dpm.ps);      <-- freed, pointer NOT nulled   :2750
 *             return -ENOMEM;              <-- num_ps still 0              :2751
 *         }
 *         adev->pm.dpm.ps[i].ps_priv = ps;                                 :2753
 *     }
 *     adev->pm.dpm.num_ps = ucNumEntries;   <-- never reached             :2775
 *
 *   kv_dpm_sw_init dpm_failed -> kv_dpm_fini:
 *     for (i = 0; i < num_ps; i++)  -- num_ps is still 0, zero iterations,
 *                                     so the earlier ps_priv allocations leak
 *     kfree(adev->pm.dpm.ps);               <-- DOUBLE FREE               :2919
 *
 * Modes:
 *   ./t          accounting: prints the call sequence.
 *   ./t crash    performs the verbatim free(); free() sequence -> the C
 *                allocator detects the double free -> abort (SIGABRT).
 *                (kernel equivalent: kfree() of an already-freed block ->
 *                heap freelist corruption / "double free" panic.)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

struct amdgpu_ps { void *ps_priv; };

struct dpm {
	struct amdgpu_ps *ps;
	int num_ps;
};

static void kv_parse_power_table(struct dpm *pm, int fail_at)
{
	pm->ps = calloc(4, sizeof(struct amdgpu_ps));		/* :2736 */
	printf("kv_parse_power_table: ps=%p (kcalloc)\n", (void *)pm->ps);

	for (int i = 0; i < 4; i++) {
		void *ps = (i == fail_at) ? NULL : malloc(64);	/* :2748 kzalloc */
		if (ps == NULL) {
			printf("kv_parse_power_table: kzalloc failed at state %d\n", i);
			printf("kv_parse_power_table: kfree(ps) at :2750   (pointer left dangling; num_ps still %d)\n", pm->num_ps);
			free(pm->ps);				/* :2750 */
			return;					/* -ENOMEM */
		}
		pm->ps[i].ps_priv = ps;			/* :2753 */
	}
	pm->num_ps = 4;					/* :2775 (not reached) */
}

static void kv_dpm_fini(struct dpm *pm, int do_free)
{
	for (int i = 0; i < pm->num_ps; i++)		/* :2916 (0 iterations) */
		free(pm->ps[i].ps_priv);		/*   -> states 0..fail_at-1 LEAK */
	printf("kv_dpm_fini: loop skipped %d iterations (earlier ps_priv allocations leaked)\n", pm->num_ps);
	printf("kv_dpm_fini: kfree(ps) at :2919 on %p\n", (void *)pm->ps);
	if (do_free)
		free(pm->ps);				/* :2919 SECOND free */
}

int main(int argc, char **argv)
{
	int crash = (argc > 1 && strcmp(argv[1], "crash") == 0);
	struct dpm pm = { NULL, 0 };

	kv_parse_power_table(&pm, 2);	/* allocation fails at state #2 */
	fflush(stdout);
	kv_dpm_fini(&pm, crash);	/* sw_init dpm_failed path */
	if (!crash) {
		printf("accounting mode: second free would corrupt the allocator freelist\n");
		return 0;
	}

	/* Demonstrate the double-free consequence: the same block re-entering
	 * the freelist can be handed out twice -> aliasing. */
	printf("post-double-free allocations:\n");
	void *a[6];
	for (int k = 0; k < 6; k++) {
		a[k] = malloc(256);
		printf("  malloc(256) = %p\n", a[k]);
	}
	int dup = 0;
	for (int k = 0; k < 6; k++)
		for (int j = k + 1; j < 6; j++)
			if (a[k] == a[j] && a[k] != NULL) {
				printf("  ALIASED: a[%d] == a[%d] == %p  (two live allocations at one address)\n",
				       k, j, a[k]);
				dup++;
			}
	printf("%s (allocator did not detect the double free: second free() accepted silently%s)\n",
	       dup ? "freelist aliasing demonstrated" : "no aliasing observed this run",
	       dup ? "" : ", exit=0");
	return 0;
}