{"repro_id":"REPRO-2026-00212","version":7,"title":"DirtyClone Linux kernel page-cache corruption privilege escalation","repro_type":"security","status":"published","severity":"critical","description":"CVE-2026-43503 (DirtyClone) is a local privilege escalation flaw in the Linux kernel networking stack. When socket-buffer (skb) fragment descriptors are transferred between skbs by __pskb_copy_fclone(), skb_shift(), skb_gro_receive(), skb_gro_receive_list(), tcp_clone_payload(), and skb_segment(), the kernel fails to propagate the SKBFL_SHARED_FRAG flag in skb_shinfo()->flags. A cloned skb can therefore keep a reference to file-backed page-cache memory while reporting skb_has_shared_frag() as false. This bypasses the XFRM/IPsec skb_cow_data() copy-on-write safeguard, allowing an unprivileged local attacker to write decrypted bytes into a root-owned read-only binary's page cache and ultimately gain root code execution.","root_cause":"# CVE-2026-43503 (DirtyClone) – Root Cause Analysis\n\n## Summary\n\nCVE-2026-43503, nicknamed **DirtyClone**, is a local privilege escalation flaw in the Linux kernel networking stack.  When a socket buffer (skb) carries file-backed page-cache fragments, the `SKBFL_SHARED_FRAG` flag in `skb_shinfo()->flags` tells the XFRM/IPsec receive path that it must copy the data before decrypting in-place.  Several skb fragment-transfer helpers (`__pskb_copy_fclone()`, `skb_shift()`, `skb_gro_receive()`, `skb_gro_receive_list()`, `tcp_clone_payload()`, and `skb_segment()`) fail to copy that flag to the new skb.  A netfilter `TEE` clone therefore keeps the original page-cache reference but no longer reports itself as shared/file-backed, so `esp_input()` decrypts directly into the page-cache page of a root-owned setuid binary.  By choosing the AES-CBC key/IV so the decrypted bytes are attacker-controlled shellcode, the attacker rewrites e.g. `/usr/bin/su` in RAM and obtains a root shell when the binary is executed.\n\n## Impact\n\n- **Package/component affected:** Linux kernel networking stack — specifically the skb fragment-copy helpers and the XFRM/IPsec `esp_input()` path.\n- **Affected versions:** Mainline Linux before v7.1-rc5; stable branches before 5.10.257, 5.15.208, 6.1.174, 6.6.141, 6.12.91, 6.18.33, and 7.0.10.\n- **Risk level and consequences:** Local privilege escalation.  An unprivileged local user with the ability to create user and network namespaces can gain root code execution by corrupting the page cache of a setuid root binary.  The bug is reachable from the normal networking paths used by XFRM/IPsec and netfilter, so no special hardware or third-party modules are required beyond the standard kernel configuration.\n\n## Impact Parity\n\n- **Disclosed/claimed maximum impact:** Local privilege escalation (`privilege_escalation`) — unprivileged attacker writes into a root-owned read-only binary's page cache and gains root.\n- **Reproduced impact from this run:** Full privilege escalation demonstrated.  The reproducer runs as uid 1000, modifies `/usr/bin/su` in the page cache, and the subsequent `echo id | /usr/bin/su` as the same uid 1000 user prints `uid=0(root) gid=0(root) groups=0(root)`.\n- **Parity:** `full`.\n- **Not demonstrated:** N/A — the claimed root shell was obtained.\n\n## Root Cause\n\nThe bug is in the skb fragment-copy routines.  `__pskb_copy_fclone()` (and the other helpers listed above) copy the fragment array and page references from one skb to another, but they do not propagate the `SKBFL_SHARED_FRAG` flag from the source `skb_shinfo()->flags`.  This flag is set by the DirtyFrag splice fix (`f4c50a4034e6`) whenever an skb carries file-backed page-cache fragments via `vmsplice()`/splice()` into a socket.\n\nWhen the netfilter `TEE` target clones an outbound ESP-in-UDP packet, the clone goes through `nf_dup_ipv4()` → `__pskb_copy_fclone()`.  The clone keeps a reference to the same physical page-cache page but is no longer marked `SKBFL_SHARED_FRAG`.  On the receive side, `esp_input()` calls `skb_cow_data()` which checks `skb_has_shared_frag()`.  Because the flag is missing, the cow path is skipped and the in-place AES-CBC decryption writes attacker-controlled bytes into the file-backed page-cache page.\n\nThe upstream fix is commit `48f6a5356a33dd78e7144ae1faef95ffc990aae0` (first tag v7.1-rc5), which propagates the `SKBFL_SHARED_FRAG` flag across the fragment-transfer helpers.  Ubuntu mainline v7.0.10 contains the backport, so it serves as the negative control in this reproduction.\n\n## Reproduction Steps\n\nThe reproduction is fully automated by `bundle/repro/reproduction_steps.sh`.  In short, it:\n\n1. Installs QEMU, busybox-static, cpio, gcc, coreutils, and jq if they are not present.\n2. Uses the prepared project cache to obtain the Ubuntu mainline v7.0.9 (vulnerable) and v7.0.10 (fixed) kernels plus matching rootfs images.\n3. Compiles the rafaeldtinoco `dirtyclone.c` exploit and a small `runas` helper that drops to uid 1000.\n4. Builds a custom initramfs that:\n   - mounts the rootfs read-only,\n   - switches root into the Ubuntu rootfs,\n   - loads `xt_TEE`, `nf_dup_ipv4`, `esp4`, and `xfrm_user`,\n   - runs the exploit as an unprivileged uid 1000 user,\n   - executes `echo id | /usr/bin/su` as the same uid 1000 user.\n5. Boots each kernel/rootfs pair in QEMU with the initramfs and captures the console output.\n6. Checks that the vulnerable run prints `LPE_SUCCESS` and `uid=0(root)`, while the fixed run prints `LPE_FAIL` and `su: Authentication failure`.\n7. Writes `bundle/repro/runtime_manifest.json` and `bundle/repro/validation_verdict.json`.\n\n### Expected evidence\n\n- `bundle/logs/qemu_vuln.log` must contain:\n  - `Linux (none) 7.0.9-070009-generic ...`,\n  - `[dc] wrote 192 bytes to /usr/bin/su starting at 0x0`,\n  - `uid=0(root) gid=0(root) groups=0(root)` from the `id` command,\n  - `LPE_SUCCESS: unprivileged user got root`.\n- `bundle/logs/qemu_fixed.log` must contain:\n  - `Linux (none) 7.0.10-070010-generic ...`,\n  - `[dc] post-write verify failed (target unchanged)`,\n  - `Password: su: Authentication failure`,\n  - `LPE_FAIL`.\n\n## Evidence\n\n- `bundle/logs/reproduction_steps.log` — high-level script progress.\n- `bundle/logs/qemu_vuln.log` — full console capture of the vulnerable kernel run showing the root shell.\n- `bundle/logs/qemu_fixed.log` — full console capture of the fixed kernel run showing the exploit is blocked.\n- `bundle/repro/runtime_manifest.json` — runtime evidence manifest.\n- `bundle/repro/validation_verdict.json` — structured verdict (`claim_outcome: confirmed`).\n\nKey excerpts from the vulnerable run:\n\n```\n[dc] cmd: iptables -t mangle -A OUTPUT -p udp --dport 4500 -j TEE --gateway 10.99.0.2 -> 0\n[dc] installed 48 xfrm SAs\n[dc] wrote 192 bytes to /usr/bin/su starting at 0x0\n[dc] /usr/bin/su page-cache patched (entry 0x78 = shellcode)\n=== LPE check as uid 1000 ===\nuid=0(root) gid=0(root) groups=0(root)\nLPE_SUCCESS: unprivileged user got root\n```\n\nKey excerpts from the fixed run:\n\n```\n[dc] wrote 192 bytes to /usr/bin/su starting at 0x0\n[dc] post-write verify failed (target unchanged)\n=== LPE check as uid 1000 ===\nPassword: su: Authentication failure\nLPE_FAIL\n```\n\nThe kernel configuration required for the path is present in both Ubuntu mainline builds: `CONFIG_XFRM`, `CONFIG_INET_ESP`, `CONFIG_NETFILTER_XT_TARGET_TEE`, and user namespaces.\n\n## Recommendations / Next Steps\n\n- **Upgrade:** Apply the upstream fix `48f6a5356a33` (or the corresponding stable backport).  Ubuntu mainline v7.0.10 and later, and mainline v7.1-rc5 and later, are patched.\n- **Mitigation until patched:** Disable unprivileged user namespaces (`kernel.unprivileged_userns_clone=0`) or prevent loading of `xt_TEE` / `esp4` / `nf_dup_ipv4` for untrusted users, since the exploit requires `CAP_NET_ADMIN` in a private network namespace.\n- **Testing:** Backport validation should verify that `SKBFL_SHARED_FRAG` survives `__pskb_copy_fclone()`, `skb_shift()`, `skb_gro_receive()`, `skb_gro_receive_list()`, `tcp_clone_payload()`, and `skb_segment()` by running this reproducer against the candidate kernel.\n\n## Additional Notes\n\n- **Idempotency:** The script was run twice consecutively from a clean state and produced the same confirmed verdict both times.\n- **Isolation:** The rootfs disk is mounted read-only inside the VM and the QEMU drive is opened with `readonly=on`, so the host-side image files are not modified by the exploit.\n- **Limitations:** The reproduction depends on the prebuilt Ubuntu mainline kernels and rootfs in the project cache.  The exploit path uses `udp/4500` ESP-in-UDP encapsulation and a netfilter `mangle/OUTPUT` TEE rule to force the `__pskb_copy_fclone()` clone; any kernel configuration that lacks one of these components will fail closed, which is expected behavior for an incomplete path.\n","cve_id":"CVE-2026-43503","source_url":"https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git","package":{"name":"Linux kernel","ecosystem":"linux"},"reproduced_at":"2026-07-03T13:21:16.909699+00:00","duration_secs":4255.0,"tool_calls":218,"handoffs":2,"total_cost_usd":3.790461789999998,"agent_costs":{"hypothesis_generator":0.02949005,"judge":0.478213,"repro":2.1572226,"support":0.0339058,"vuln_variant":1.0916303400000005},"cost_breakdown":{"hypothesis_generator":{"accounts/fireworks/models/kimi-k2p7-code":0.02949005},"judge":{"gpt-5.5":0.478213},"repro":{"accounts/fireworks/models/kimi-k2p7-code":2.1572226},"support":{"accounts/fireworks/models/kimi-k2p7-code":0.0339058},"vuln_variant":{"accounts/fireworks/models/kimi-k2p7-code":1.0916303400000005}},"quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-07-03T13:21:17.638589+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":10082,"category":"reproduction_script"},{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":7839,"category":"analysis"},{"path":"bundle/vuln_variant/reproduction_steps.sh","filename":"reproduction_steps.sh","size":9073,"category":"reproduction_script"},{"path":"bundle/vuln_variant/rca_report.md","filename":"rca_report.md","size":9380,"category":"analysis"},{"path":"bundle/ticket.md","filename":"ticket.md","size":2035,"category":"ticket"},{"path":"bundle/ticket.json","filename":"ticket.json","size":3286,"category":"other"},{"path":"bundle/AGENTS.repro.md","filename":"AGENTS.repro.md","size":1253,"category":"documentation"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":787,"category":"other"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":676,"category":"other"},{"path":"bundle/logs/reproduction_steps.log","filename":"reproduction_steps.log","size":156,"category":"log"},{"path":"bundle/logs/qemu_vuln.log","filename":"qemu_vuln.log","size":2227,"category":"log"},{"path":"bundle/logs/qemu_fixed.log","filename":"qemu_fixed.log","size":2168,"category":"log"},{"path":"bundle/logs/vuln_variant.log","filename":"vuln_variant.log","size":3789,"category":"log"},{"path":"bundle/logs/qemu_fragnesia_vuln.log","filename":"qemu_fragnesia_vuln.log","size":18384,"category":"log"},{"path":"bundle/logs/qemu_fragnesia_fixed.log","filename":"qemu_fragnesia_fixed.log","size":5584,"category":"log"},{"path":"bundle/vuln_variant/runtime_manifest.json","filename":"runtime_manifest.json","size":695,"category":"other"},{"path":"bundle/vuln_variant/validation_verdict.json","filename":"validation_verdict.json","size":884,"category":"other"},{"path":"bundle/vuln_variant/patch_analysis.md","filename":"patch_analysis.md","size":9063,"category":"documentation"},{"path":"bundle/vuln_variant/root_cause_equivalence.json","filename":"root_cause_equivalence.json","size":2949,"category":"other"},{"path":"bundle/vuln_variant/source_identity.json","filename":"source_identity.json","size":1209,"category":"other"},{"path":"bundle/vuln_variant/variant_manifest.json","filename":"variant_manifest.json","size":3013,"category":"other"}]}