/* DF-3882 model: drm_dp_mst_handle_down_rep() txmsg use-after-free race.
 *
 * Kernel interleave (sys/dev/drm/drm_dp_mst_topology.c):
 *
 *   HPD/IRQ context (drm_dp_mst_handle_down_rep, :2336-2364):
 *     lock(qlock)
 *     txmsg = mstb->tx_slots[slot]              // :2337
 *     unlock(qlock)                             // :2339
 *     drm_dp_sideband_parse_reply(&down_rep_recv, &txmsg->reply)  // :2353
 *       (writes up to ~700 hub-controlled bytes into txmsg->reply)
 *     memset(down_rep_recv); put_mstb           // :2358-2359
 *     lock(qlock)
 *     txmsg->state = DRM_DP_SIDEBAND_TX_RX;     // :2362  <-- writes into txmsg
 *     mstb->tx_slots[slot] = NULL;              // :2363
 *     unlock(qlock)
 *
 *   sender thread (drm_dp_mst_wait_tx_reply, :802-838), same instant:
 *     wait_event_timeout(..., 4*HZ) expired     // ret == 0
 *     lock(qlock)
 *     if (state == SENT) mstb->tx_slots[seqno] = NULL   // :829-832
 *     unlock(qlock)
 *     return -EIO                               // caller does kfree(txmsg)
 *                                               // e.g. drm_dp_send_link_address:1649
 *
 * If the sender's 4s timeout expires between the handler's unlock(:2339) and
 * its re-lock(:2361), the handler performs the ~700-byte parse-reply write
 * AND the state write into kfree'd memory. The hub controls when the reply
 * arrives (it can park it 3.99s and release it at the deadline), so it can
 * retry the race indefinitely.
 *
 * This model reproduces the interleaving with two pthreads, using
 * mmap/munmap as alloc/free so that the post-free write actually SEGVs --
 * deterministic proof of the use-after-free (not of the kernel bug itself:
 * the guest has no DP MST hardware, see env.txt).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
#include <sys/mman.h>
#include <time.h>

#define TX_SENT 2
#define TX_RX   3

struct txmsg_model {            /* relevant slice of drm_dp_sideband_msg_tx */
	int seqno;
	int state;              /* written by BOTH sides -- the raced field */
	uint8_t reply[700];     /* drm_dp_sideband_msg_reply_body */
};

static pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
static struct txmsg_model *txmsg;          /* == mstb->tx_slots[0] */
static struct txmsg_model *slot0;
static uint64_t deadline_ns;
static volatile int race_hit;

static uint64_t now_ns(void)
{
	struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	return (uint64_t)ts.tv_sec * 1000000000ull + ts.tv_nsec;
}

static void segv_handler(int sig)
{
	static const char msg[] =
		"  SIGSEGV: handler wrote into txmsg AFTER sender kfree'd it"
		" -- use-after-free demonstrated\n";
	(void)sig;
	race_hit = 1;
	if (write(2, msg, sizeof(msg) - 1)) { /* stderr, unbuffered */ }
	_exit(42);
}

/* sender: drm_dp_send_link_address + drm_dp_mst_wait_tx_reply */
static void *sender(void *arg)
{
	(void)arg;
	uint64_t ret;

	/* wait_event_timeout(tx_waitq, state==RX||TIMEOUT, 4*HZ) -> expires */
	do {
		ret = deadline_ns - now_ns();
	} while ((int64_t)ret > 0);

	/* timeout path under qlock */
	pthread_mutex_lock(&qlock);
	if (txmsg->state == TX_SENT) {
		printf("sender: timeout, state==SENT -> tx_slots[0] = NULL\n");
		slot0 = NULL;              /* :831 */
	}
	pthread_mutex_unlock(&qlock);

	/* caller: kfree(txmsg)  (drm_dp_send_link_address:1649) */
	munmap(txmsg, sizeof(struct txmsg_model));
	printf("sender: kfree(txmsg) done\n");
	return NULL;
}

/* handler: drm_dp_mst_handle_down_rep */
static void *irq_handler(void *arg)
{
	struct txmsg_model *tx;
	(void)arg;

	/* hub timed the DOWN_REP_MSG_RDY to land 1ms before the deadline */
	while ((int64_t)(deadline_ns - 1000000ull - now_ns()) > 0)
		;

	pthread_mutex_lock(&qlock);
	tx = slot0;                     /* :2337 read the slot */
	pthread_mutex_unlock(&qlock);   /* :2339 */
	printf("handler: read txmsg=%p from slot, dropped qlock, parsing...\n", (void *)tx);

	/* drm_dp_sideband_parse_reply writes ~700 bytes into tx->reply */
	memset(tx->reply, 0xA5, sizeof(tx->reply));
	usleep(2000);

	/* :2362 -- the 4-byte state write after the unlocked window */
	tx->state = TX_RX;
	printf("handler: txmsg->state = TX_RX (no fault: sender had not freed)\n");
	return NULL;
}

int main(void)
{
	pthread_t th_sender, th_irq;

	signal(SIGSEGV, segv_handler);

	txmsg = mmap(NULL, sizeof(struct txmsg_model), PROT_READ | PROT_WRITE,
		     MAP_PRIVATE | MAP_ANON, -1, 0);
	if (txmsg == MAP_FAILED) { perror("mmap"); return 1; }
	txmsg->seqno = 0;
	txmsg->state = TX_SENT;         /* fully sent, parked in tx_slots[0] */
	slot0 = txmsg;

	deadline_ns = now_ns() + 200 * 1000000ull;  /* '4 HZ' compressed to 200ms */

	pthread_create(&th_sender, NULL, sender, NULL);
	pthread_create(&th_irq, NULL, irq_handler, NULL);
	pthread_join(th_irq, NULL);
	pthread_join(th_sender, NULL);

	printf("interleaving not hit this run (handler finished before kfree)\n");
	return 0;
}
