/*
 * DF-4208 — legacyEepromDiag() HAL_DIAG_EEPROM_EXP_* union type confusion.
 *
 * On cards with a v5/eepMap==2 EEPROM (AR2413/AR5413 radios) the HAL_EEPROM
 * union ee_u holds RAW_DATA_STRUCT_2413 datasets, but legacyEepromDiag()
 * (ah_eeprom_v3.c:1729-1739) unconditionally interprets it as
 * EEPROM_POWER_EXPN_5112: it returns pe->pChannels — which is now the first
 * four uint16 channel-frequency words of the raw dataset reinterpreted as a
 * pointer — and a size computed from pe->numChannels (raw pChannels[4]).
 *
 * ath_ioctl_diag() (if_ath_ioctl.c:214-219) then copyout()s resultsize
 * bytes FROM that pointer to userspace.  With >= 4 calibration piers the
 * bogus pointer has nonzero bits in 48..63 -> NON-CANONICAL -> kernel page
 * fault -> panic, from an unprivileged SIOCGATHDIAG on kernels built with
 * options ATH_DIAGAPI (no priv_check on that ioctl in DragonFly).
 *
 * This harness extracts legacyEepromDiag() and the full HAL_EEPROM type
 * VERBATIM from the guest's /usr/src tree (sha256-gated) and emulates the
 * copyout canonicity check the CPU would perform.
 *
 * Exit 42 == type confusion demonstrated (wild/non-canonical result).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

/* ---- kernel shims ---- */
typedef int HAL_BOOL;
typedef int HAL_STATUS;
#define AH_TRUE  1
#define AH_FALSE 0
#define AH_NULL  NULL
#define roundup(x, y) ((((uintptr_t)(x)) + ((uintptr_t)(y) - 1)) & ~((uintptr_t)(y) - 1))

/* diag request codes — verbatim values from ah_diagcodes.h:39-42 */
enum {
	HAL_DIAG_EEPROM		= 1,
	HAL_DIAG_EEPROM_EXP_11A	= 2,
	HAL_DIAG_EEPROM_EXP_11B	= 3,
	HAL_DIAG_EEPROM_EXP_11G	= 4,
};
/* ah_eeprom.h version constants (verbatim values) */
#define AR_EEPROM_VER4_0	0x4000
#define AR_EEPROM_VER5_0	0x5000

/* stand-in for AH_PRIVATE(ah)->ah_eeprom (declared after HAL_EEPROM below) */
#define AH_PRIVATE(ah)	(&ah_priv)
struct ath_hal { int pad; };
static struct ath_hal ah_obj;

/* ==== VERBATIM STRUCTS FROM ah_eeprom_v3.h INSERTED BY build.sh ==== */
/* ==== VERBATIM legacyEepromDiag() FROM ah_eeprom_v3.c INSERTED BY build.sh ==== */

static HAL_EEPROM ee;

static void
emulate_copyout(const char *tag, void *kptr, uint32_t len)
{
	uint64_t p = (uint64_t)(uintptr_t)kptr;
	int canonical = ((p >> 47) == 0) || ((p >> 47) == 0x1ffff);

	printf("[%s] *result   = 0x%016llx  *resultsize = %u\n", tag,
	    (unsigned long long)p, len);
	if (kptr == NULL) {
		printf("[%s]     -> NULL, copyout skipped (resultsize 0)\n", tag);
		return;
	}
	if (!canonical) {
		printf("[%s]     -> NON-CANONICAL kernel pointer: copyout() "
		    "in ath_ioctl_diag() takes a page fault -> PANIC\n", tag);
		return;
	}
	printf("[%s]     -> canonical; kernel copies %u bytes from %p to "
		    "user (arbitrary kernel-memory read if mapped)\n", tag, len, kptr);
}

int
main(void)
{
	RAW_DATA_STRUCT_2413 *raw = &ee.ee_u.eu_rawDataset2413[0];
	unsigned i;

	/* card state: AR5413 (2413 radio), v5.0 EEPROM, EEP_MAP 2 */
	ee.ee_version = AR_EEPROM_VER5_0;
	ee.ee_eepMap = 2;
	ah_priv.ah_eeprom = &ee;

	printf("sizeof(HAL_EEPROM) = %zu (dumped wholesale by HAL_DIAG_EEPROM, "
	    "incl. 6 kernel heap pointers in ee_u)\n\n", sizeof(HAL_EEPROM));

	/*
	 * Populate the 2413 raw dataset exactly as
	 * ar2413SetupRawDataset()/ar2413EepromToRawDataset() would for a
	 * 4-pier 11a calibration: pChannels[i] = fbin2freq() = 4800+5*fbin.
	 */
	raw->numChannels = 4;
	raw->xpd_mask = 0x9;
	raw->pChannels[0] = 4800 + 5 * 76;	/* 5180 = 0x143C */
	raw->pChannels[1] = 4800 + 5 * 88;	/* 5240 = 0x1478 */
	raw->pChannels[2] = 4800 + 5 * 104;	/* 5320 = 0x14C8 */
	raw->pChannels[3] = 4800 + 5 * 189;	/* 5745 = 0x166D */
	/* pChannels[4..9] remain 0 (kernel ath_hal_malloc uses M_ZERO) */
	for (i = 0; i < raw->numChannels; i++)
		raw->pDataPerChannel[i].channelValue = raw->pChannels[i];

	printf("2413 raw dataset pChannels[0..3] =");
	for (i = 0; i < 4; i++)
		printf(" %04x", raw->pChannels[i]);
	printf("  (EEPROM-chosen pier frequencies)\n\n");

	/* ---- what the ioctl path does for each EXP diag code ---- */
	{
		void *result = NULL;
		uint32_t resultsize = 0;
		int rc;

		rc = legacyEepromDiag(&ah_obj, HAL_DIAG_EEPROM_EXP_11A, NULL, 0,
		    &result, &resultsize);
		printf("legacyEepromDiag(EXP_11A) returned %d on a "
		    "eepMap==2 (2413) card:\n", rc);
		emulate_copyout("EXP_11A", result, resultsize);
		/* show the provenance of the bogus pointer bytes */
		{
			uint16_t *w = (uint16_t *)&raw->pChannels[0];
			uint64_t p = 0;
			for (i = 0; i < 4; i++)
				p |= (uint64_t)w[i] << (16 * i);
			printf("[EXP_11A]     pointer == pChannels[0..3] as "
			    "u64: 0x%016llx (match: %s)\n\n",
			    (unsigned long long)p,
			    p == (uint64_t)(uintptr_t)result ? "YES" : "no");
		}
	}

	{
		void *result = (void *)(uintptr_t)1;
		uint32_t resultsize = 0;
		int rc = legacyEepromDiag(&ah_obj, HAL_DIAG_EEPROM, NULL, 0,
		    &result, &resultsize);
		printf("legacyEepromDiag(HAL_DIAG_EEPROM) returned %d: dumps all "
		    "%u bytes of HAL_EEPROM", rc, resultsize);
#ifdef DF4208_FIXED
		printf(" (pack-built fixed variant)");
#endif
		printf("\n-> bytes [offsetof(ee_u)..] include the live kernel "
		    "heap pointers of ee_u (info leak)\n");
	}

	/* 5-pier variant: resultsize blows up via pe->numChannels==pChannels[4] */
	{
		void *result = NULL;
		uint32_t resultsize = 0;
		raw->numChannels = 5;
		raw->pChannels[4] = 4800 + 5 * 200;	/* 5800 -> numChannels field */
		(void)legacyEepromDiag(&ah_obj, HAL_DIAG_EEPROM_EXP_11A, NULL, 0,
		    &result, &resultsize);
		printf("\n5-pier variant: pe->numChannels aliases pChannels[4] "
		    "= %u -> *resultsize = %u bytes from %p\n", raw->pChannels[4],
		    resultsize, result);
	}

#ifdef DF4208_FIXED
	printf("\nPASS (fixed): EXP diag codes refused on eepMap!=1 card; "
	    "no wild pointer produced\n");
	return 0;
#else
	{
		uint64_t p = 0;
		uint16_t *w = (uint16_t *)&raw->pChannels[0];
		for (i = 0; i < 4; i++)
			p |= (uint64_t)w[i] << (16 * i);
		if ((p >> 48) != 0 && (p >> 48) != 0xffff) {
			printf("\nFAIL: non-canonical wild pointer from "
			    "union confusion CONFIRMED\n");
			return 42;
		}
		printf("\nPASS: no non-canonical pointer produced\n");
		return 0;
	}
#endif
}
