/* DF-3886 model: drm_dp_sideband_msg_build() accumulates curlen into
 * msg->msg[256] without any bound.
 *
 * Kernel code (sys/dev/drm/drm_dp_mst_topology.c:371-377):
 *
 *   if (msg->curchunk_idx >= msg->curchunk_len) {
 *           crc4 = drm_dp_msg_data_crc4(msg->chunk, msg->curchunk_len - 1);
 *           memcpy(&msg->msg[msg->curlen], msg->chunk, msg->curchunk_len - 1);
 *           msg->curlen += msg->curchunk_len - 1;
 *   }
 *
 * curchunk_len is the hub-controlled 6-bit msg_len from the sideband header
 * (up to 63 => up to 62 bytes appended per completed chunk). curlen is a u8
 * and is NEVER validated against sizeof(msg->msg) == 256. A reply delivered
 * in >= 5 chunks (e.g. a crafted LINK_ADDRESS with 15 ports: 18 + 15*21 = 333
 * bytes of body) walks the append offset past 256 and writes up to 61
 * hub-controlled bytes past msg[256] -- over curchunk_len/curlen/have_somt/
 * have_eomt/initial_hdr and into the adjacent up_req_recv of the same
 * topology manager (struct drm_dp_mst_topology_mgr,
 * drm_dp_mst_helper.h:464-470).
 *
 * Distinct sink from DF-1264 (which is the chunk[48] array in the non-hdr
 * continuation branch). This model reproduces the accumulation arithmetic.
 */
#include <stdio.h>
#include <string.h>
#include <stdint.h>

struct rx_model {               /* mirrors struct drm_dp_sideband_msg_rx */
	uint8_t chunk[48];
	uint8_t msg[256];
	uint8_t curchunk_len;
	uint8_t curchunk_idx;
	uint8_t curchunk_hdrlen;
	uint8_t curlen;
	uint8_t have_somt;
	uint8_t have_eomt;
	uint8_t initial_hdr[24];
	uint8_t up_req_recv_chunk0;  /* first byte of the adjacent up_req_recv */
};

int main(void)
{
	struct rx_model m;
	int chunk_no, max_spill = 0, first_oob_chunk = -1;
	int n_chunks = 8;           /* a >256-byte reply needs >= 5 chunks */
	uint8_t fill = 0xA5;        /* hub-controlled reply bytes */

	memset(&m, 0, sizeof(m));

	for (chunk_no = 0; chunk_no < n_chunks; chunk_no++) {
		int curchunk_len = 63; /* max 6-bit msg_len (hdr->msg_len) */
		int append = curchunk_len - 1;
		int dst = m.curlen;
		int end = dst + append;

		if (dst < 256 && end > 256) {
			int spill = end - 256;
			if (spill > max_spill)
				max_spill = spill;
			if (first_oob_chunk < 0)
				first_oob_chunk = chunk_no + 1;
			printf("chunk %d: memcpy(&msg[%d], chunk, %d) writes %d bytes "
			       "past msg[256] (offsets 256..%d)\n",
			       chunk_no + 1, dst, append, spill, end - 1);
			/* emulate the write to show what gets clobbered */
			memset(((uint8_t *)&m) + __builtin_offsetof(struct rx_model, msg) + dst,
			       fill, append > sizeof(m) ? sizeof(m) : append);
		} else if (dst >= 256) {
			printf("chunk %d: append at &msg[%d] with curlen already wrapped\n",
			       chunk_no + 1, dst);
		} else {
			printf("chunk %d: append %d bytes at &msg[%d] (in bounds, end=%d)\n",
			       chunk_no + 1, append, dst, end);
			memset(m.msg + dst, fill, append);
		}
		m.curlen = (uint8_t)(m.curlen + append);   /* u8 wrap, as in kernel */
	}

	printf("\nresult: first OOB append at chunk %d; max spill %d bytes past msg[256]\n",
	       first_oob_chunk, max_spill);
	printf("clobbered after msg[256]: curchunk_len=%02x curchunk_idx=%02x curlen=%02x "
	       "have_somt=%02x have_eomt=%02x initial_hdr[0]=%02x up_req_recv.chunk[0]=%02x\n",
	       m.curchunk_len, m.curchunk_idx, m.curlen, m.have_somt, m.have_eomt,
	       m.initial_hdr[0], m.up_req_recv_chunk0);
	return 0;
}
