/*
 * DF-3942 logic replica — bios_parser.c unchecked GET_IMAGE() NULL derefs
 *
 * Target: sys/dev/drm/amd/display/dc/bios/bios_parser.c
 *   get_ss_info_from_internal_ss_info_tbl_V2_1        GET_IMAGE at :1041, deref at :1046
 *   get_ss_info_from_ss_info_table                    GET_IMAGE at :1114, deref at :1140
 *   get_ss_entry_number_from_ss_info_tbl              GET_IMAGE at :1645, deref at :1671
 *   get_ss_entry_number_from_internal_ss_info_tbl_v2_1 GET_IMAGE at :1723, deref at :1726
 *   get_ss_entry_number_from_internal_ss_info_tbl_V3_1 GET_IMAGE at :1759, deref at :1761
 *
 * These functions call GET_IMAGE(type, DATA_TABLES(ASIC_InternalSS_Info | SS_Info))
 * and dereference the result WITHOUT a NULL check, unlike every other parser in
 * the file (e.g. get_gpio_pin_info :1802, get_embedded_panel_info_v1_2 :1233).
 *
 * GET_IMAGE(type, offset) == (type *)bios_get_image(bp, offset, sizeof(type))
 * (bios_parser_helper.h / bios_parser_helper.c:36-44):
 *      if (bp->bios && offset + size < bp->bios_size)
 *              return bp->bios + offset;
 *      return NULL;
 *
 * Reachability with a crafted VBIOS: the SS table pointer
 * (master_data_tbl->ListOfDataTables.AsicInternalSS_Info) is a VBIOS u16.
 * Point it at the last bytes of the image:
 *   - the revision probe GET_IMAGE(ATOM_COMMON_TABLE_HEADER, off) (4 bytes,
 *     bios_parser.c:952-954 / :1111) still succeeds and reports rev 2.1,
 *     which gates entry into get_ss_info_from_internal_ss_info_tbl_V2_1;
 *   - the struct fetch GET_IMAGE(ATOM_ASIC_INTERNAL_SS_INFO_V2, off)
 *     (4 + sizeof(ATOM_ASIC_SS_ASSIGNMENT_V2)=4+14=18 bytes) FAILS -> NULL;
 *   - line 1046 reads header->sHeader.usStructureSize => NULL deref =>
 *     kernel panic during amdgpu/dc attach.
 *
 * This program replicates that exact decision path in userland (same
 * bounds semantics, same order of probes) against a synthetic VBIOS image.
 * It is a LOGIC REPLICA, not the in-kernel path: no AMD GPU with a crafted
 * ROM is available in the audit guest (documented in VERDICT.md / run.log).
 *
 * Expected output: the struct fetch returns NULL (as in the kernel) and the
 * replica takes the deref path -> controlled SIGSEGV, proving the reachable
 * NULL.  In the kernel this address is the NULL page -> panic.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>

/* --- exact copies of the kernel-side bounds/parse logic --------------- */

static uint8_t *g_bios;
static uint32_t g_bios_size;

static void *bios_get_image(uint32_t offset, uint32_t size)	/* bios_parser_helper.c:36 */
{
	if (g_bios && offset + size < g_bios_size)
		return g_bios + offset;
	return NULL;
}

#define GET_IMAGE(type, offset) ((type *)bios_get_image(offset, sizeof(type)))

typedef struct { uint16_t usStructureSize; uint8_t ucTableFormatRevision, ucTableContentRevision; } __attribute__((packed)) ATOM_COMMON_TABLE_HEADER;
typedef struct { uint32_t ulTargetClockRange; uint16_t usSpreadSpectrumPercentage, usSpreadRateIn10Hz; uint8_t ucClockIndication, ucSpreadSpectrumMode, ucReserved[2]; } __attribute__((packed)) ATOM_ASIC_SS_ASSIGNMENT_V2;
typedef struct { ATOM_COMMON_TABLE_HEADER sHeader; ATOM_ASIC_SS_ASSIGNMENT_V2 asSpreadSpectrum[1]; } __attribute__((packed)) ATOM_ASIC_INTERNAL_SS_INFO_V2;

/* replica of get_ss_info_from_internal_ss_info_tbl_V2_1 (bios_parser.c:1028) */
static __attribute__((noinline)) uint32_t get_ss_info_V2_1(uint32_t table_offset)
{
	ATOM_ASIC_INTERNAL_SS_INFO_V2 *header;
	uint32_t tbl_size;

	header = GET_IMAGE(ATOM_ASIC_INTERNAL_SS_INFO_V2, table_offset);	/* :1041 */
	/* [no NULL check here either -- this is the bug] */
	tbl_size = (header->sHeader.usStructureSize			/* :1046 DEREF */
		    - sizeof(ATOM_COMMON_TABLE_HEADER))
		   / sizeof(ATOM_ASIC_SS_ASSIGNMENT_V2);
	return tbl_size;	/* keep the load live so the NULL deref executes */
}

int
main(void)
{
	uint32_t off_hdr, off_tbl;
	ATOM_COMMON_TABLE_HEADER *hdr;

	g_bios_size = 128 * 1024;			/* typical VBIOS image */
	g_bios = calloc(1, g_bios_size);

	/*
	 * Crafted VBIOS: AsicInternalSS_Info points 10 bytes before the end of
	 * the image.  4-byte header probe fits; 18-byte struct fetch does not.
	 */
	off_tbl = g_bios_size - 10;
	hdr = GET_IMAGE(ATOM_COMMON_TABLE_HEADER, off_tbl);	/* replica of :952 */
	if (hdr == NULL) {
		printf("[-] header probe failed -- offset not reachable\n");
		return 1;
	}
	/* claim revision 2.1 so the kernel takes the V2_1 branch */
	hdr->ucTableFormatRevision = 2;
	hdr->ucTableContentRevision = 1;
	printf("[+] 4-byte revision probe at off=%u succeeded (rev %u.%u) --"
	    " kernel gates into get_ss_info_from_internal_ss_info_tbl_V2_1\n",
	    off_tbl, hdr->ucTableFormatRevision, hdr->ucTableContentRevision);

	off_hdr = offsetof(ATOM_ASIC_INTERNAL_SS_INFO_V2, asSpreadSpectrum);
	printf("[+] sizeof(ATOM_ASIC_INTERNAL_SS_INFO_V2)=%zu > %u bytes left"
	    " -> kernel GET_IMAGE at :1041 returns NULL\n",
	    sizeof(ATOM_ASIC_INTERNAL_SS_INFO_V2), g_bios_size - off_tbl);

	if (bios_get_image(off_tbl, sizeof(ATOM_ASIC_INTERNAL_SS_INFO_V2)) == NULL)
		printf("[+] kernel takes the deref at bios_parser.c:1046"
		    " (header->sHeader.usStructureSize) -> NULL page -> panic\n");

	/* now take the same path in the replica: this SIGSEGVs on the NULL */
	printf("[*] entering replica of bios_parser.c:1041-1046 ...\n");
	fflush(stdout);
	printf("[*] replica computed tbl_size=%u\n", get_ss_info_V2_1(off_tbl));
	printf("[-] not reached\n");
	return 0;
}
