/*
 * DF-3955 — radeon kv_dpm.c double-free of rdev->pm.dpm.ps on the
 *           kzalloc-failure path of kv_parse_power_table()
 * =====================================================================
 * Target: sys/dev/drm/radeon/kv_dpm.c (pass 2)
 *
 * kv_parse_power_table() kv_dpm.c:2673-2677:
 *      ps = kzalloc(sizeof(struct kv_ps), GFP_KERNEL);
 *      if (ps == NULL) {
 *              kfree(rdev->pm.dpm.ps);      <-- freed, pointer NOT NULLed
 *              return -ENOMEM;              <-- num_ps (set only at :2700)
 *      }                                        stays 0; ps_priv of states
 *                                               0..i-1 LEAK (num_ps == 0)
 *
 * kv_dpm_init() propagates the error (:2787-2789), but radeon_pm_init_dpm()
 * radeon_pm.c:1448 DISCARDS kv_dpm_init()'s return value, so the device lives
 * on with a dangling rdev->pm.dpm.ps until driver detach/suspend:
 *   radeon_pm_fini -> radeon_pm_fini_dpm (radeon_pm.c:1705) -> radeon_dpm_fini
 *   -> kv_dpm_fini() kv_dpm.c:2861-2871:
 *          for (i = 0; i < rdev->pm.dpm.num_ps; i++)   // num_ps == 0: leaks
 *                  kfree(rdev->pm.dpm.ps[i].ps_priv);
 *          kfree(rdev->pm.dpm.ps);                    // SECOND free => heap
 *                                                      // corruption
 *
 * Kernel impact: double free of a ~sizeof(struct radeon_ps)*ucNumEntries
 * block (kmalloc bucket), potentially adjacent to other radeon objects.
 * Trigger window: kzalloc failure during boot-time VBIOS power-table parse
 * on KV/BT/APU hardware (memory-pressure gated; rare by nature).
 *
 * This harness replays the identical alloc/free SEQUENCE in userland; the C
 * library's double-free detection standing in for the kernel heap's
 * (uma/kmem) corruption. Arithmetic/sequence demonstration, NOT a kernel
 * reproduction.
 *
 * Build: cc -O2 -Wall -o trigger_df3955 trigger_df3955.c
 * Run:   ./trigger_df3955 / ./trigger_df3955 crash
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

struct radeon_ps { unsigned long pad[6]; void *ps_priv; };
struct kv_ps     { unsigned long pad[8]; };

int main(int argc, char **argv)
{
	int crash = (argc > 1 && !strcmp(argv[1], "crash"));

	printf("DF-3955 radeon kv_dpm double-free on parse-OOM path (simulation)\n");
	printf("kv_parse_power_table :2673-2677 kfree(dpm.ps), no NULL, num_ps stays 0\n");
	printf("kv_dpm_fini          :2868      kfree(dpm.ps) AGAIN + ps_priv leak\n");
	printf("gate: kzalloc(sizeof(struct kv_ps)) fails mid-loop (boot, KV APU)\n");

	/* Replay: 3-state table; the kzalloc for state #2's ps_priv is forced to
	 * fail exactly like the GFP_KERNEL allocation under memory pressure. */
	const int num_entries = 3;
	const int fail_at = 2;
	struct radeon_ps *dpm_ps = calloc(num_entries, sizeof(*dpm_ps));
	if (!dpm_ps) { perror("calloc"); return 1; }
	int i = 0;
	for (i = 0; i < num_entries; i++) {
		struct kv_ps *ps;
		if (i == fail_at) {
			ps = NULL;		/* injected kzalloc failure */
		} else {
			ps = malloc(sizeof(struct kv_ps));
		}
		if (!ps) {
			/* verbatim error path */
			printf("state %d: kzalloc(kv_ps) failed -> kfree(dpm.ps); return -ENOMEM\n", i);
			free(dpm_ps);		/* dangling on purpose */
			break;
		}
		dpm_ps[i].ps_priv = ps;
	}
	if (i == num_entries) {
		printf("setup deviation: allocation unexpectedly succeeded\n");
		return 1;
	}
	printf("dangling dpm_ps = %p ; leaked ps_priv count = %d ; num_ps = 0\n",
	       (void *)dpm_ps, i);

	if (!crash) {
		printf("NON-CRASH MODE: stopping before the second free "
		       "(leaked %d kv_ps objects on purpose)\n", i);
		return 0;
	}

	/* kv_dpm_fini(): num_ps==0 so the ps_priv loop is a no-op (leak), then */
	free(dpm_ps);			/* <-- the double free */
	/* DragonFly userland malloc does NOT abort here (no glibc-style check)
	 * and neither does the kernel heap: kmalloc/kmem would silently corrupt
	 * its free list / adjacent objects. Demonstrates the sequence executes
	 * to completion with a dangling pointer handed to the allocator twice. */
	printf("SECOND FREE COMPLETED SILENTLY (userland no-abort; kernel: silent heap corruption)\n");
	return 0;
}
