#!/bin/sh
# DF-4636 — radeon_acpi ATIF/ATCS return-buffer parsers: verbatim-extraction
# control-flow proof that a firmware-returned ACPI_OBJECT of the wrong type
# (or a truncated/lying buffer) causes an out-of-bounds or wild-pointer read
# in the shipped kernel source, and that fix.diff makes all cases return
# -EINVAL cleanly.
#
# Method (mirrors the accepted DF-4605 methodology):
#   1. verify the guest's /usr/src radeon_acpi.c/.h are byte-identical to the
#      audited tree (sha256 gate),
#   2. sed/awk-extract the four parsers + their helpers VERBATIM,
#   3. compile them against a faithful ACPICA type shim + mock
#      AcpiEvaluateObject that returns firmware-chosen objects,
#   4. drive 6 fixtures x 4 iterations in forked children with a SIGSEGV
#      tracer (prints exact si_addr),
#   5. re-extract from a patched copy (fix.diff) and show every fixture
#      returns cleanly.
set -u

SRC=/usr/src/sys/dev/drm/radeon/radeon_acpi.c
HDR=/usr/src/sys/dev/drm/radeon/radeon_acpi.h
EXP_SRC=794cb6c29cb9e9fd17d4c826e86389ecb44a778e1f80ace15f7cc3e1a3410cb1
EXP_HDR=e177532a3afbe84ab4d15060b2b0f21e83e3080e5c4607c8f7afc004d4513b23
HERE=$(cd "$(dirname "$0")" && pwd)

GOT=$(sha256 -r "$SRC" | cut -d' ' -f1)
if [ "$GOT" != "$EXP_SRC" ]; then
	echo "FATAL: guest $SRC sha $GOT != audited $EXP_SRC"; exit 9
fi
GOTH=$(sha256 -r "$HDR" | cut -d' ' -f1)
if [ "$GOTH" != "$EXP_HDR" ]; then
	echo "FATAL: guest $HDR sha $GOTH != audited $EXP_HDR"; exit 9
fi
echo "SRC-SHA-OK  $GOT"
echo "HDR-SHA-OK  $GOTH"

WORK=$(mktemp -d /tmp/df4636.XXXXXX)
cd "$WORK" || exit 9
cp "$SRC" orig.c
# apply the audited fix to a copy (never to /usr/src)
if patch -s -p1 -o fixed.c orig.c "$HERE/fix.diff"; then
	echo "FIX-APPLIED-CLEAN"
else
	echo "FATAL: fix.diff did not apply to guest source copy"; exit 9
fi

# ---- verbatim extraction ------------------------------------------------
# constants + __packed structs from the audited file (lines 36-89, untouched
# by fix.diff; taken from orig.c for BOTH variants)
sed -n '36,89p' orig.c > consts.inc

extract() { # $1 = source file, $2 = output
	: > "$2"
	for fn in radeon_atif_call radeon_atif_parse_notification \
		  radeon_atif_parse_functions radeon_atif_verify_interface \
		  radeon_atif_get_notification_params \
		  radeon_atif_get_sbios_requests radeon_atcs_call \
		  radeon_atcs_parse_functions radeon_atcs_verify_interface; do
		awk -v fn="$fn" '
		{
			if (p) {
				print
				if ($0 ~ /^}/) exit
			} else {
				if ($0 ~ ("^" fn "\\(")) { print prev; print; p = 1 }
				else if ($0 ~ ("^[A-Za-z_].*" fn "\\(")) { print; p = 1 }
				else prev = $0
			}
		}' "$1" >> "$2"
		echo "" >> "$2"
	done
}
extract orig.c ext_base.inc
extract fixed.c ext_fixed.inc
echo "EXTRACTED base=$(wc -l < ext_base.inc) lines fixed=$(wc -l < ext_fixed.inc) lines"

# ATIF/ATCS function-id and capability-mask constants from the audited header
# (note: some "#  define" lines in radeon_acpi.h are indented)
grep -E "^[[:space:]]*#[[:space:]]*define[[:space:]]+(ATIF|ATCS)_" "$HDR" > hdrdefines.inc

# ---- harness prelude ----------------------------------------------------
cat > prelude.inc <<'EOF'
/* DF-4636 harness: faithful ACPICA/radeon type shim + mocks (userland). */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <unistd.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/wait.h>

typedef uint8_t  u8;  typedef uint16_t u16;
typedef uint32_t u32; typedef uint64_t u64;
typedef uint64_t UINT64;
#ifndef EIO
#define EIO 5
#endif
#ifndef EINVAL
#define EINVAL 22
#endif

/* ---- actypes.h-faithful ACPICA shim (layout verified by offsetof below) */
typedef uint32_t ACPI_STATUS;
typedef void *ACPI_HANDLE;
typedef uint32_t ACPI_OBJECT_TYPE;
typedef unsigned long ACPI_SIZE;
#define ACPI_TYPE_INTEGER 1
#define ACPI_TYPE_STRING  2
#define ACPI_TYPE_BUFFER  3
#define ACPI_TYPE_PACKAGE 4
#define ACPI_FAILURE(a) ((a) != 0)
typedef union acpi_object {
	ACPI_OBJECT_TYPE Type;
	struct { ACPI_OBJECT_TYPE Type; UINT64 Value; } Integer;
	struct { ACPI_OBJECT_TYPE Type; u32 Length; char *Pointer; } String;
	struct { ACPI_OBJECT_TYPE Type; u32 Length; u8 *Pointer; } Buffer;
	struct { ACPI_OBJECT_TYPE Type; u32 Count; union acpi_object *Elements; } Package;
} ACPI_OBJECT;
typedef struct { u32 Count; ACPI_OBJECT *Pointer; } ACPI_OBJECT_LIST;
typedef struct { ACPI_SIZE Length; void *Pointer; } ACPI_BUFFER;
#define ACPI_ALLOCATE_BUFFER ((ACPI_SIZE)-1)
#define AE_OK (0)
#define AE_NOT_FOUND (0x8005)

/* ---- mocks: AcpiEvaluateObject returns a firmware-chosen object ---- */
static ACPI_OBJECT g_mock_obj;
static ACPI_OBJECT *g_mock_obj_ptr = &g_mock_obj;
static ACPI_STATUS g_mock_status = AE_OK;
static ACPI_STATUS AcpiEvaluateObject(ACPI_HANDLE h, const char *m,
    ACPI_OBJECT_LIST *a, ACPI_BUFFER *ret)
{
	(void)h; (void)m; (void)a;
	/* model ACPICA ACPI_ALLOCATE_BUFFER: fresh allocation on success */
	ret->Pointer = malloc(sizeof(ACPI_OBJECT));
	memcpy(ret->Pointer, g_mock_obj_ptr, sizeof(ACPI_OBJECT));
	return g_mock_status;
}
static void AcpiOsFree(void *p) { free(p); }
static const char *AcpiFormatException(ACPI_STATUS s) { (void)s; return "MOCK"; }

#define DRM_INFO(fmt, ...)        fprintf(stderr, "[DRM_INFO] " fmt, ##__VA_ARGS__)
#define DRM_DEBUG_DRIVER(fmt, ...) fprintf(stderr, "[DRMDBG ] " fmt, ##__VA_ARGS__)
#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif
static int hweight32(u32 x)
{
	int n = 0;
	while (x) { n += (int)(x & 1); x >>= 1; }
	return n;
}
EOF

# radeon.h atif/atcs structs, verbatim (pattern-bounded: from the first
# atif struct through the close of struct radeon_atcs)
awk '/^struct radeon_atif_notification_cfg \{/ {p = 1}
     /^struct radeon_atcs \{/ {if (!p) next; print; getline a; print a; getline b; print b; exit}
     p {print}' /usr/src/sys/dev/drm/radeon/radeon.h > radeonstructs.inc
echo 'struct radeon_encoder { int opaque; };' >> radeonstructs.inc

# ---- runner -------------------------------------------------------------
cat > runner.inc <<'EOF'

/* ---- DF-4636 fixtures & runner ---- */
static struct radeon_atif g_atif;
static struct radeon_atcs g_atcs;

static void segv_handler(int sig, siginfo_t *si, void *uc)
{
	char buf[160];
	int n = snprintf(buf, sizeof(buf), "  >>> FAULT SIGSEGV si_addr=%p\n",
	    si->si_addr);
	(void)sig; (void)uc;
	write(2, buf, (size_t)n);
	_exit(77);
}

/* map 2 pages, 2nd PROT_NONE; return pointer to n bytes ending at boundary */
static u8 *guard_alloc(size_t n, u8 **mapped_end)
{
	size_t pg = 4096;
	u8 *m = mmap(NULL, 2 * pg, PROT_READ | PROT_WRITE,
	    MAP_PRIVATE | MAP_ANON, -1, 0);
	if (m == MAP_FAILED) { perror("mmap"); _exit(90); }
	if (mprotect(m + pg, pg, PROT_NONE) != 0) { perror("mprotect"); _exit(90); }
	*mapped_end = m + pg;
	return m + pg - n;
}

static void fx_integer_wild_atif(void)
{
	int err;
	g_mock_obj.Type = ACPI_TYPE_INTEGER;
	g_mock_obj.Integer.Value = 0x0000414141414100ULL; /* "SBIOS chose" (canonical so si_addr prints exactly) */
	err = radeon_atif_verify_interface((ACPI_HANDLE)1, &g_atif);
	fprintf(stderr, "  radeon_atif_verify_interface -> %d\n", err);
}
static void fx_integer_wild_atcs(void)
{
	int err;
	g_mock_obj.Type = ACPI_TYPE_INTEGER;
	g_mock_obj.Integer.Value = 0x0000424242424200ULL;
	err = radeon_atcs_verify_interface((ACPI_HANDLE)1, &g_atcs);
	fprintf(stderr, "  radeon_atcs_verify_interface -> %d\n", err);
}
static void fx_null_buffer(void)
{
	int err;
	g_mock_obj.Type = ACPI_TYPE_BUFFER;
	g_mock_obj.Buffer.Length = 0;
	g_mock_obj.Buffer.Pointer = NULL;
	err = radeon_atif_verify_interface((ACPI_HANDLE)1, &g_atif);
	fprintf(stderr, "  radeon_atif_verify_interface -> %d\n", err);
}
static void fx_shortbuf_hdr(void)
{
	struct atif_sbios_requests req;
	int cnt;
	u8 *end, *p = guard_alloc(1, &end);
	p[0] = 0x0C;			/* size header low byte; high byte is OOB */
	g_mock_obj.Type = ACPI_TYPE_BUFFER;
	g_mock_obj.Buffer.Length = 1;
	g_mock_obj.Buffer.Pointer = p;
	fprintf(stderr, "  buffer=%p mapped_end=%p (u16 size read spans %p..%p)\n",
	    (void *)p, (void *)end, (void *)p, (void *)(p + 1));
	cnt = radeon_atif_get_sbios_requests((ACPI_HANDLE)1, &req);
	fprintf(stderr, "  radeon_atif_get_sbios_requests -> %d\n", cnt);
}
static void fx_lying_size(void)
{
	int err;
	u8 *end, *p = guard_alloc(4, &end);
	p[0] = 0x0C; p[1] = 0x00; p[2] = 0; p[3] = 0; /* claims 12, has 4 */
	g_mock_obj.Type = ACPI_TYPE_BUFFER;
	g_mock_obj.Buffer.Length = 4;
	g_mock_obj.Buffer.Pointer = p;
	fprintf(stderr, "  buffer=%p mapped_end=%p (memcpy of min(12,12)=12 spans past %p)\n",
	    (void *)p, (void *)end, (void *)end);
	err = radeon_atif_verify_interface((ACPI_HANDLE)1, &g_atif);
	fprintf(stderr, "  radeon_atif_verify_interface -> %d\n", err);
}
static void fx_control_wellformed(void)
{
	int err;
	static u8 good[13];		/* ATIF VERIFY_INTERFACE output, size>=12 */
	good[0] = 13; good[1] = 0;	/* size = 13 */
	good[2] = 1; good[3] = 0;	/* version = 1 */
	/* notification_mask @4, function_bits @8 = 0x00000003 */
	good[12] = 0x03;
	g_mock_obj.Type = ACPI_TYPE_BUFFER;
	g_mock_obj.Buffer.Length = 13;
	g_mock_obj.Buffer.Pointer = good;
	err = radeon_atif_verify_interface((ACPI_HANDLE)1, &g_atif);
	fprintf(stderr, "  radeon_atif_verify_interface -> %d (expect 0, control)\n", err);
}

static int run_child(const char *name, void (*fn)(void))
{
	pid_t pid = fork();
	int st;
	if (pid == 0) {
		struct sigaction sa;
		memset(&sa, 0, sizeof(sa));
		sa.sa_sigaction = segv_handler;
		sa.sa_flags = SA_SIGINFO;
		sigaction(SIGSEGV, &sa, NULL);
		sigaction(SIGBUS, &sa, NULL);
		fn();
		fprintf(stderr, "  child returned normally (NO FAULT)\n");
		_exit(0);
	}
	waitpid(pid, &st, 0);
	if (WIFSIGNALED(st))
		printf("  RESULT %s: killed by raw signal %d (handler bypassed)\n",
		    name, WTERMSIG(st));
	else
		printf("  RESULT %s: child exit=%d\n", name, WEXITSTATUS(st));
	return WIFSIGNALED(st) ? 99 : WEXITSTATUS(st);
}

int main(void)
{
	int i, faults = 0, clean = 0;
	struct radeon_atif zero_atif;

	printf("== DF-4636 harness (%s variant) ==\n",
#ifdef DF4636_FIXED
	    "FIXED"
#else
	    "BASELINE"
#endif
	);
	printf("offsetof(Integer.Value)=%zu offsetof(Buffer.Pointer)=%zu (union aliasing: %s)\n",
	    offsetof(ACPI_OBJECT, Integer.Value),
	    offsetof(ACPI_OBJECT, Buffer.Pointer),
	    offsetof(ACPI_OBJECT, Integer.Value) ==
	    offsetof(ACPI_OBJECT, Buffer.Pointer) ? "YES" : "NO");

	/* control: well-formed buffer must parse (err=0) in BOTH variants */
	memset(&zero_atif, 0, sizeof(zero_atif));
	for (i = 0; i < 4; i++) {
		int r = run_child("control_wellformed", fx_control_wellformed);
		if (r == 0) clean++;
	}
	printf("control_wellformed: %d/4 returned cleanly\n\n", clean);

	faults = 0;
	for (i = 0; i < 4; i++) {
		int r1 = run_child("integer_wild_atif", fx_integer_wild_atif);
		int r2 = run_child("integer_wild_atcs", fx_integer_wild_atcs);
		int r3 = run_child("null_buffer", fx_null_buffer);
		int r4 = run_child("shortbuf_hdr", fx_shortbuf_hdr);
		int r5 = run_child("lying_size", fx_lying_size);
		if (r1 == 77 && r2 == 77 && r3 == 77 && r4 == 77 && r5 == 77)
			faults++;
	}
	printf("fault fixtures: %d/4 iterations with ALL 5 fixtures faulting\n", faults);
	printf("PROOF-RESULT: %s\n",
#ifdef DF4636_FIXED
	    (clean == 4 && faults == 0) ?
	    "FIXED-CLEAN (no faults, control still parses)" :
	    "FIXED-BAD (unexpected fault or broken control)"
#else
	    (clean == 4 && faults == 4) ?
	    "BASELINE-CORRUPTS (5/5 fixtures SIGSEGV 4/4 iterations, control parses)" :
	    "BASELINE-WEAK"
#endif
	);
	return 0;
}
EOF

# ---- build both variants --------------------------------------------------
cat hdrdefines.inc consts.inc radeonstructs.inc > common.inc
{ cat prelude.inc common.inc ext_base.inc  runner.inc; } > h_base.c
{ cat prelude.inc common.inc ext_fixed.inc runner.inc; } > h_fixed.c
echo "BUILD baseline:"; cc -O2 -Wall -o h_base h_base.c 2>&1
echo "BUILD fixed:";    cc -O2 -Wall -DFD4636_FIXED_DUMMY=1 -DDF4636_FIXED -o h_fixed h_fixed.c 2>&1
ls -l h_base h_fixed || { echo "FATAL: build failed"; exit 9; }

# ---- run ------------------------------------------------------------------
echo; echo "================ BASELINE (shipped kernel source) ================"
./h_base
echo; echo "================ FIXED (fix.diff applied) ================"
./h_fixed
echo; echo "================ REPEATS (stress, 3 more full rounds) ================"
i=0
while [ $i -lt 3 ]; do
	./h_base | tail -2
	./h_fixed | tail -2
	i=$((i + 1))
done

uname -a > env.txt
cc --version | head -1 >> env.txt
echo "guest: $(uname -m), radeon in kernel: $(sysctl -n debug.bootverbose 2>/dev/null || true)" >> env.txt
grep -i radeon /usr/src/sys/conf/X86_64_GENERIC 2>/dev/null >> env.txt || echo "(no radeon lines in X86_64_GENERIC)" >> env.txt
echo "DONE-WORKDIR=$WORK"
