Initial skeleton: README, CVE inventory, roadmap, ARCH, ethics + copy_fail_family module absorbed from DIRTYFAIL

This commit is contained in:
2026-05-16 19:26:24 -04:00
commit cf30b249de
45 changed files with 10336 additions and 0 deletions
+244
View File
@@ -0,0 +1,244 @@
# DIRTYFAIL — defender's playbook
A one-page operational guide for sysadmins assessing and mitigating
exposure to the Copy Fail and Dirty Frag CVE family on Linux hosts.
If you're operating a fleet of Linux servers, the questions below are
the ones to answer in order.
---
## 1. Am I vulnerable?
**Quickest answer (no compilation):**
```bash
curl -sSL https://raw.githubusercontent.com/KaraZajac/DIRTYFAIL/main/tools/dirtyfail-check.sh \
| bash
```
(Read the script first if you don't trust me — it's ~150 lines of
plain bash, no curl-pipe-bash voodoo. Read-only on your system.)
Exit code: `0` mitigated, `1` vulnerable, `2` couldn't determine.
**Empirical answer (builds the C tool, runs the active probes):**
```bash
git clone https://github.com/KaraZajac/DIRTYFAIL.git
cd DIRTYFAIL && make
./dirtyfail --scan --active
```
The default `--scan` mode runs precondition checks (kernel version,
module presence, LSM state) plus an active probe of the Copy Fail
primitive against a sentinel file in `/tmp`. Adding `--active` extends
the sentinel-STORE probe to the other four primitives (ESP v4, ESP v6,
RxRPC, GCM) — this is the only way to distinguish a backported-patched
kernel from an unpatched one without running the full exploit. The
probes only modify temporary files in `/tmp`; `/etc/passwd` is never
touched.
**Per-CVE breakdown (manual checks):**
| Question | Command | Vulnerable if |
|---|---|---|
| Is the algif_aead module reachable? | `lsmod \| grep algif_aead` + `grep algif_aead /etc/modprobe.d/*` | Loaded AND not blacklisted |
| Are esp4/esp6 modules reachable? | `modinfo esp4 esp6` | Both present, not blacklisted |
| Is rxrpc reachable? | `lsmod \| grep rxrpc` + `getsockopt(AF_RXRPC, ...)` | Module loadable from unprivileged context |
| Is unprivileged userns hardened? | `cat /proc/sys/kernel/apparmor_restrict_unprivileged_userns` | Returns `0` or file absent |
| Does PAM accept empty passwords? | `grep nullok /etc/pam.d/common-auth` | "nullok" present without "nullok_secure" |
---
## 2. How do I mitigate?
Three options, listed best-to-worst:
### A. Apply the upstream kernel patch (best)
The fix is mainline commit `f4c50a4034e6` (merged 2026-05-07). Each
distro's kernel package is on its own backport timeline:
| Distro | Status (as of 2026-05-09) |
|---|---|
| Debian 13 (`6.12.86+deb13`) | ✅ patched |
| Ubuntu 24.04 LTS | ❌ not yet patched (kernel 6.8.0-111) |
| Ubuntu 26.04 LTS | ❌ not yet patched (kernel 7.0.0-15.15, predates upstream merge) |
| AlmaLinux 10.1 | ❌ not yet patched (kernel 6.12 EL) |
| Fedora 44 | ❌ not yet patched (kernel 6.19.10) |
Run `apt list --upgradable linux-image-*` / `dnf check-update kernel`
periodically and apply.
### B. Layered LSM mitigation (Ubuntu 26.04 model)
If you're on Ubuntu 24.04 or 26.04, you can replicate Ubuntu 26.04's
defense-in-depth approach without waiting for the kernel patch:
```bash
# 1. Block unprivileged user namespaces from acquiring caps
echo 'kernel.apparmor_restrict_unprivileged_userns = 1' \
| sudo tee /etc/sysctl.d/99-userns-restrict.conf
sudo sysctl --system
# 2. Verify the AA hardening is in effect:
sudo unshare -U -r bash -c 'echo deny > /proc/self/setgroups 2>&1' \
|| echo "OK: unprivileged userns has no caps (mitigation working)"
```
This blocks the EXPLOIT INFRASTRUCTURE (no caps in unprivileged
userns), not the underlying kernel bug. Real-root exploitation still
works.
### C. Module blacklist (`dirtyfail --mitigate` or manual)
Heaviest hammer — blacklists every module that hosts a primitive.
**Side effects: breaks IPsec, AFS, and any userspace using `AF_ALG`
AEAD.**
Automated:
```bash
sudo ./dirtyfail --mitigate
```
Manual equivalent:
```bash
sudo tee /etc/modprobe.d/dirtyfail-mitigations.conf <<'EOF'
install algif_aead /bin/false
install esp4 /bin/false
install esp6 /bin/false
install rxrpc /bin/false
EOF
sudo rmmod algif_aead esp4 esp6 rxrpc 2>/dev/null
sudo sysctl vm.drop_caches=3
```
Undo: `sudo ./dirtyfail --cleanup-mitigate` (or delete the conf
files, then `sudo modprobe <name>` to reload as needed).
### D. Disable `pam_unix nullok`
Optional belt-and-suspenders: even if a page-cache STORE lands, the
exploit relies on PAM's `nullok` flag to convert "empty password
field in /etc/passwd" into a successful `su`. Removing `nullok` from
`/etc/pam.d/common-auth` (Debian/Ubuntu) or `/etc/pam.d/system-auth`
(Red Hat family) closes that step:
```bash
sudo sed -i 's/\bnullok\b//g' /etc/pam.d/common-auth # Debian/Ubuntu
# Verify a passworded user can still log in normally before logging out!
```
---
## 3. What should I monitor?
Even after mitigation, the kernel bug remains until the patch lands.
For detection:
### auditd rules (universal)
A ready-to-load rules file ships in `tools/99-dirtyfail.rules`. It
covers six syscall paths used by the exploit chain: XFRM netlink,
add_key(rxrpc), unshare(CLONE_NEWUSER), AF_ALG socket creation,
AppArmor `change_onexec` writes, and direct `/etc/passwd`/`/etc/shadow`
modifications.
```bash
sudo install -m 0640 tools/99-dirtyfail.rules /etc/audit/rules.d/
sudo augenrules --load
sudo systemctl restart auditd
```
Search for events:
```bash
# grep is more reliable than ausearch on distros that use ENRICHED
# log_format (Debian 13, Fedora 44 — ausearch -k can return "no matches"
# even when SYSCALL events with the key are present in the file).
sudo grep -E 'type=SYSCALL.*key="dirtyfail-' /var/log/audit/audit.log | tail -20
# Or per-key, only the most recent entries:
sudo grep 'key="dirtyfail-xfrm"' /var/log/audit/audit.log | tail -5
sudo grep 'key="dirtyfail-rxkey"' /var/log/audit/audit.log | tail -5
sudo grep 'key="dirtyfail-userns"' /var/log/audit/audit.log | tail -5
sudo grep 'key="dirtyfail-afalg"' /var/log/audit/audit.log | tail -5
```
(`sudo ausearch -k <key>` is the documented tool for this and works on
older distros, but enriched-format compat issues mean `grep` is the
safer default.)
The `dirtyfail-userns` rule fires on every legitimate `unshare -U` and
rootless container start — pair it with `dirtyfail-xfrm` in a SIEM
correlation rule (same auid, both within ~5s) for a high-fidelity
alert. Tuning notes inline in the rules file.
### eBPF / falco (if you have it)
Falco's `Sensitive mount opened for writing` and `Detect outbound
connections to common miner pool ports` rule sets won't help directly,
but a custom rule on `unshare(CLONE_NEWUSER)` followed by
`sendto(SOCK_RAW, NETLINK_XFRM)` from a non-zero uid is high-fidelity.
### Cheap log signal
```bash
# Hits if our exploit's marker bytes show up in /etc/passwd's page cache
# (run periodically; doesn't catch every variant but is zero-cost)
grep -E '^[^:]+::0:0:|^[^:]+:x:0000:' /etc/passwd
```
---
## 4. Quick reference card
```
SCAN this host:
curl ... | bash # bash check (no compile)
./dirtyfail --scan # preconds + Copy Fail probe (~1s)
./dirtyfail --scan --active # all 5 sentinel-STORE probes (~10s)
./dirtyfail --scan --active --json # same, machine-readable for SIEM
MITIGATE (Ubuntu / fleet-wide):
sudo ./dirtyfail --mitigate # one-shot defensive deployment
sudo ./dirtyfail --cleanup-mitigate # undo
MITIGATE (manual, no DIRTYFAIL):
See section 2-C above.
PATCH:
apt list --upgradable | grep linux-image
dnf check-update kernel
MONITOR:
/etc/audit/rules.d/99-dirtyfail.rules (see section 3)
EMERGENCY (suspected compromise via this CVE class):
sudo sysctl vm.drop_caches=3 # evicts page-cache exploits
sudo systemctl restart sshd # forces re-read of /etc/passwd
grep dirtyfail /etc/passwd # check for backdoor user
rm -f /var/tmp/.dirtyfail.state # clean DIRTYFAIL state file
```
---
## 5. Glossary
- **Page-cache write**: kernel writes attacker-controlled bytes into the
in-memory copy of a file (`/etc/passwd`, `/usr/bin/su`) without
modifying the file on disk. Persists in RAM until eviction.
- **PAM nullok**: configuration flag that permits authentication for
accounts with an empty password field in `/etc/passwd` (or
`/etc/shadow`).
- **xfrm-ESP**: the kernel's ESP (Encapsulating Security Payload)
implementation in the IPsec stack. The bug class affects in-place
AEAD decrypt over splice-pinned page-cache pages.
- **Userns capability stripping**: kernel-level enforcement that
unprivileged user namespaces have no `CAP_NET_ADMIN` /
`CAP_SYS_ADMIN`, blocking exploit infrastructure even when the
underlying kernel bug is unpatched.
+324
View File
@@ -0,0 +1,324 @@
# DIRTYFAIL — research notes
This document captures kernel-source audits and analysis adjacent to
the published CVEs (CVE-2026-31431 / CVE-2026-43284 / CVE-2026-43500).
It's a living research log, not a vendor advisory: findings here are
based on reading mainline kernel source and the disclosed write-ups,
and may need re-verification as the kernel evolves.
---
## §1. Adjacent kernel paths — audit for the same skb_cow_data() bypass pattern
### TL;DR
Ten kernel paths beyond the published CVEs were audited for the
same in-place-AEAD-over-splice-pinned-pages bug class. **All ten
are structurally immune.** No undisclosed CVE candidates surfaced
in this audit; the bug class is genuinely tightly scoped to the
three published sinks plus the algif_aead authencesn/rfc4106-gcm
primitives.
### The vulnerable pattern
The CVE-2026-43284-class bug requires all four of:
1. **In-place AEAD**`aead_request_set_crypt(req, src, dst, ...)`
where `src == dst` or the scatterlists alias the same memory.
2. **Conditional skip-COW** — input handler has a branch that bypasses
`skb_cow_data()` on certain skb shapes (typically: non-linear with
no frag_list).
3. **`skb_to_sgvec` over skb frags** — the scatterlist passed to the
AEAD is built directly from the skb's frags, so splice-pinned page
references end up in it.
4. **Userspace path to the skb's frags**`splice(2)`, `sendfile(2)`,
or `sendmsg(MSG_SPLICE_PAGES)` can deliver attacker-controlled
page-cache pages into those frags.
Removing any one of the four breaks the chain. The published CVEs are
the three sinks where all four conditions align (esp_input, esp6_input,
rxkad_verify_packet_1) plus the algif_aead authencesn / rfc4106-gcm
primitives that share the in-place destination scatterlist pattern.
### §1.1 Path-by-path verdict
| Path | In-place crypto? | skb_cow_data | Splice-reachable? | Verdict |
|---|---|---|---|---|
| esp_input (esp4) | ✅ | conditional skip | yes | **CVE-2026-43284** (patched) |
| esp6_input | ✅ | conditional skip | yes | **CVE-2026-43284 v6** (patched) |
| algif_aead authencesn | ✅ | n/a (different path) | yes via splice→AF_ALG | **CVE-2026-31431** (patched) |
| algif_aead rfc4106-gcm | ✅ | n/a | yes | **Copy Fail GCM variant** (patched as side-effect of CF revert) |
| rxkad_verify_packet_1 | ✅ | conditional skip | yes via RxRPC handshake | **CVE-2026-43500** (NOT patched as of 2026-05-09) |
| **ah_input (ah4 + ah6)** | ✅ (HMAC, not decrypt) | **UNCONDITIONAL** | n/a | NOT vulnerable — structurally immune |
| **ipcomp_input** | ❌ (decompress, separate output pages) | conditional skip | n/a (output is fresh page) | NOT vulnerable — separate dst |
| **macsec_decrypt** | ✅ | **UNCONDITIONAL** | no — rx skbs come from netdev | NOT vulnerable — structurally immune |
| **tls_sw recv decrypt** | ✅ | unconditional, also rx-only | no — rx skbs come from TCP rx ring | NOT vulnerable |
| **tls_sw send encrypt + MSG_SPLICE_PAGES** | YES (read-only on user pages) | n/a (msg_en allocated separately) | yes (msg_pl) but only as src | NOT vulnerable — separate src/dst |
| **WireGuard `decrypt_packet`** | ✅ ChaCha20Poly1305 in-place | **UNCONDITIONAL** at line 252 | yes via UDP rx (but COW protects) | NOT vulnerable — structurally immune |
| **algif_skcipher `_skcipher_recvmsg`** | ✅ symmetric in-place possible | n/a (different module structure) | src yes (TX SGL), dst no (recv iovec) | NOT vulnerable — separate src/dst |
| **espintcp** (ESP-in-TCP) | n/a (delegates) | n/a | reaches esp_input via xfrm_rcv_encap | inherits f4c50a4034e6 patch — NOT a new CVE |
| **OpenVPN kernel offload `ovpn_aead_decrypt`** | ✅ AEAD in-place | **UNCONDITIONAL** at line 210 | yes via UDP rx (but COW protects) | NOT vulnerable — structurally immune |
| **SCTP-AUTH `sctp_auth_calculate_hmac`** | HMAC only (no decrypt, no destination write into skb data frags) | n/a | n/a — digest writes to auth chunk header (kernel-allocated), not data frags | NOT vulnerable — read-only over data |
### §1.2 Eliminated paths — why each is immune
**`ah_input` (net/ipv4/ah4.c, net/ipv6/ah6.c)** — IPsec Authentication
Header. Calls `skb_cow_data(skb, 0, &trailer)` UNCONDITIONALLY before
`skb_to_sgvec_nomark` builds the HMAC scatterlist. No skip-cow branch.
Splice-pinned pages would always be copied into a private buffer
before HMAC verification.
**`xfrm_ipcomp.c`** — IPCOMP decompression has a conditional skip-cow
branch, but the output is allocated as a fresh kernel page
(`alloc_page(GFP_ATOMIC)`) and the destination scatterlist `dsg` is
built separately from the input scatterlist `sg`. Even with
splice-pinned input pages, decompression output goes to fresh pages.
Not in-place over input.
**`macsec_decrypt` (drivers/net/macsec.c)** — MACsec receive AEAD.
Calls `skb_cow_data(skb, 0, &trailer)` unconditionally before
`skb_to_sgvec` and the in-place decrypt. Additionally: macsec rx
skbs come from netdev rx, not from userspace splice — the attacker
has no path to plant a page-cache page reference.
**`tls_sw_recvmsg` (net/tls/tls_sw.c)** — kTLS receive AEAD.
kernel.org docs: "To decrypt 'in place' kTLS calls skb_cow_data()."
COW is unconditional on the rx path. Additionally: TLS rx skbs come
from the TCP rx queue, not from splice — the only way a user can put
a page-cache page reference into a TCP rx skb is via rare
`SO_PEEK_OFF` / `MSG_PEEK` paths or kernel-side socket forwarding,
neither of which gives the attacker control.
### §1.3 kTLS send via MSG_SPLICE_PAGES — closest near-miss
The kTLS *send* path was modified in 2023 ("splice, net: Handle
MSG_SPLICE_PAGES in AF_TLS", LWN 933386) to support
`MSG_SPLICE_PAGES`, which is the same primitive Dirty Frag and Copy
Fail abuse. This was the most plausible adjacent candidate.
**Resolved: not vulnerable.** Direct reading of `net/tls/tls_sw.c`:
- `tls_sw_sendmsg_splice()` adds the user's spliced pages to `msg_pl`
(the plaintext sk_msg buffer) via `sk_msg_page_add()`.
- `tls_alloc_encrypted_msg()` calls
`sk_msg_alloc(sk, msg_en, len, 0)`**fresh kernel pages** for the
encrypted buffer.
- `tls_push_record()` chains the scatterlists:
```c
sg_chain(rec->sg_aead_out, 2, &msg_en->sg.data[i]);
```
- `tls_do_encryption()`:
```c
aead_request_set_crypt(aead_req, rec->sg_aead_in,
rec->sg_aead_out, data_len, rec->iv_data);
```
- `sg_aead_in` (chained from msg_pl, contains user's spliced page)
≠ `sg_aead_out` (chained from msg_en, kernel-allocated pages).
The encrypt READS the user's spliced /etc/passwd page but WRITES
ciphertext to `msg_en`'s kernel-allocated pages. The user's
page-cache page is never modified. This is exactly the defense the
algif_aead patch (a664bf3d603d) implemented when it reverted to
out-of-place AEAD; kTLS has had it from inception.
Compare to the vulnerable `esp_input` pattern:
```c
/* vulnerable: src == dst */
skb_to_sgvec(skb, sg, ...);
aead_request_set_crypt(req, sg, sg, ...);
```
```c
/* safe: src ≠ dst */
sg_chain(sg_aead_in, ..., msg_pl); /* user spliced pages */
sg_chain(sg_aead_out, ..., msg_en); /* kernel private pages */
aead_request_set_crypt(req, sg_aead_in, sg_aead_out, ...);
```
### §1.3a WireGuard receive — `decrypt_packet()`
ChaCha20Poly1305 in-place AEAD on incoming UDP skbs. Confirmed
**not vulnerable** — `drivers/net/wireguard/receive.c:232277`:
```c
static bool decrypt_packet(struct sk_buff *skb, struct noise_keypair *keypair)
{
struct scatterlist sg[MAX_SKB_FRAGS + 8];
/* ... */
offset = -skb_network_offset(skb);
skb_push(skb, offset);
num_frags = skb_cow_data(skb, 0, &trailer); /* line 252, UNCONDITIONAL */
/* ... */
sg_init_table(sg, num_frags);
if (skb_to_sgvec(skb, sg, 0, skb->len) <= 0)
return false;
if (!chacha20poly1305_decrypt_sg_inplace(sg, skb->len, NULL, 0,
PACKET_CB(skb)->nonce,
keypair->receiving.key))
return false;
```
`skb_cow_data` at line 252 is UNCONDITIONAL — no skip-cow branch. By
the time the in-place AEAD runs, any splice-pinned pages have already
been copied into kernel-private pages. Same defensive pattern as
AH, MACsec, kTLS rx.
### §1.3b algif_skcipher — `_skcipher_recvmsg()`
The companion module to algif_aead, exposing symmetric ciphers
(AES-CBC, AES-CTR, etc.) over AF_ALG. Same author and patchset era
as the in-place optimization that introduced Copy Fail (2017,
72548b093ee3); the Copy Fail upstream fix only reverted algif_aead,
so worth verifying algif_skcipher independently.
`crypto/algif_skcipher.c:151152`:
```c
skcipher_request_set_crypt(&areq->cra_u.skcipher_req, areq->tsgl,
areq->first_rsgl.sgl.sgt.sgl, len, ctx->iv);
```
- `areq->tsgl` = TX SGL, populated via `af_alg_pull_tsgl()`. CAN
contain user-spliced page-cache pages (sendmsg + splice path).
- `areq->first_rsgl.sgl.sgt.sgl` = RX SGL, populated via
`af_alg_get_rsgl(sk, msg, ...)` from the user's `recv()` iovec,
via `iov_iter_get_pages` mapping the calling process's anonymous
memory.
The cipher operation reads from `tsgl` (potentially user-spliced
page-cache pages) and writes to `rsgl` (user's recv buffer in their
own anonymous memory). **src ≠ dst; output never lands on
splice-pinned page-cache pages.**
Why this differs from algif_aead's Copy Fail: the algif_aead bug was
specifically about the `authencesn` template internally chaining TAG
pages into the destination SGL extension (`req->dst` extends past
the end of `req->src`'s last page into chained tag pages, which
happen to be the source's spliced pages). Plain skcipher has no AEAD
tags, no chained scratch — clean src/dst separation. **Not
vulnerable.**
### §1.3c espintcp — IPsec ESP over TCP
`net/xfrm/espintcp.c` is a *transport-layer wrapper* — it does no
cryptographic work itself. The `handle_esp()` function delegates
straight to `xfrm6_rcv_encap` / `xfrm4_rcv_encap`, which call into
the standard `esp_input()` / `esp6_input()` handlers. Any skb that
reaches the ESP path through espintcp is processed by the same code
that was patched by f4c50a4034e6 (SKBFL_SHARED_FRAG check).
**Verdict: not a separate CVE.** On unpatched kernels, espintcp is
just an alternative transport for the existing CVE-2026-43284 sink
(esp_input). On patched kernels the same fix covers both UDP and TCP
encapsulation. The SHARED_FRAG flag is set wherever splice can plant
pages into TCP send buffers, and the producer-side flagging
propagates through TCP into the espintcp path.
### §1.3d OpenVPN kernel offload — `ovpn_aead_decrypt()`
New module in 6.16+ implementing OpenVPN's data channel
(ChaCha20Poly1305 / AES-GCM) in the kernel. Receive AEAD path is in
`drivers/net/ovpn/crypto_aead.c`:
```c
/* line ~210 */
nfrags = skb_cow_data(skb, 0, &trailer); /* UNCONDITIONAL */
/* ... */
/* line ~228 */
skb_to_sgvec_nomark(skb, sg + 1, payload_offset, payload_len);
/* ... */
/* line ~239 */
aead_request_set_crypt(req, sg, sg, payload_len + tag_size, iv);
```
In-place AEAD (`sg, sg`) — but `skb_cow_data()` is called
unconditionally before `skb_to_sgvec_nomark` builds the scatterlist.
Splice-pinned pages always copied to kernel-private memory before
the AEAD runs. **Not vulnerable.** Same defensive pattern as
WireGuard, AH, MACsec, kTLS rx.
### §1.3e SCTP-AUTH HMAC validation
`net/sctp/auth.c:sctp_auth_calculate_hmac()` (lines 606642) computes
HMAC over an SCTP AUTH chunk:
```c
data_len = skb_tail_pointer(skb) - (unsigned char *)auth;
digest = (u8 *)(&auth->auth_hdr + 1);
hmac_sha1_usingrawkey(asoc_key->data, asoc_key->len,
(const u8 *)auth, data_len, digest);
```
The HMAC is computed READ-ONLY over the skb's chunk data. The
digest output is written to the auth chunk's digest field
(`&auth->auth_hdr + 1`), which on the SEND path lives in
kernel-allocated chunk header memory — not in any user-spliced
data fragment. On the RECEIVE path, verification computes HMAC
over received data and compares to the sender-provided digest in a
private buffer — pure read.
The bug class requires a kernel-side WRITE to a splice-pinned page;
SCTP-AUTH only ever READS from skb data and writes the digest to a
kernel-allocated chunk header. **Not vulnerable.**
### §1.4 The protective patterns that distinguish safe from vulnerable
Every safe path on the list achieves immunity through one of three
mechanisms, each of which removes one of the four required conditions:
1. **Unconditional `skb_cow_data()`** before any in-place crypto —
AH, MACsec, kTLS rx. (Removes condition 2.)
2. **Separate destination scatterlist** allocated from kernel-private
pages — kTLS tx, IPCOMP, post-patch algif_aead.
(Removes condition 1.)
3. **The in-place crypto target is fundamentally not a splice-able
skb** — kTLS rx skbs come from TCP rx, not user splice.
(Removes condition 4.)
### §1.5 Out-of-scope or low-value candidates
The candidates that remained after §1.3a-e were all eliminated as
not worth a deeper audit:
- **AF_SMC encryption** — uses kTLS/ULP underneath, already covered
by the kTLS audit (§1.3 / §1.4b).
- **io_uring crypto extensions** — would inherit AF_ALG semantics,
already covered by the algif_skcipher audit (§1.3b).
- **Bluetooth CMTP/HIDP crypto** — privileged-only (HCI device
access), not an unprivileged-LPE vector.
- **Kernel TLS NIC offload** — encryption runs on the NIC firmware,
different threat surface entirely (firmware-side bug, not
page-cache-write).
- **dm-crypt / fscrypt** — block-layer / filesystem-layer
encryption. Different threat model; user can't splice arbitrary
page-cache pages into block requests in any meaningful way.
### §1.6 Methodology
For each candidate path, read the input handler and ask:
1. Does it call `skb_cow_data()` BEFORE building the AEAD
scatterlist?
2. Is there a conditional branch (typically based on `skb_cloned`,
`skb_has_frag_list`, `skb_is_nonlinear`) that bypasses (1)?
3. Is the resulting scatterlist used as BOTH src AND dst of
`aead_request_set_crypt()` / equivalent?
4. Can a userspace primitive (`splice(2)`, `sendfile(2)`,
`sendmsg(MSG_SPLICE_PAGES)`, AF_ALG send) deliver
attacker-controlled pages into the input skb's frags?
All four must be true for the bug class to apply. A single "no" is
sufficient for "not vulnerable."
---
## §2. References
- V4bel/dirtyfrag write-up — [github.com/V4bel/dirtyfrag/blob/master/assets/write-up.md](https://github.com/V4bel/dirtyfrag/blob/master/assets/write-up.md)
- Theori/Xint Copy Fail disclosure — [xint.io/blog/copy-fail-linux-distributions](https://xint.io/blog/copy-fail-linux-distributions)
- LWN — Replace sendpage with sendmsg(MSG_SPLICE_PAGES) — [lwn.net/Articles/928487](https://lwn.net/Articles/928487/)
- LWN — Handle MSG_SPLICE_PAGES in AF_TLS — [lwn.net/Articles/933386](https://lwn.net/Articles/933386/)
- TLS 1.3 Rx improvements (Kicinski) — [people.kernel.org/kuba/tls-1-3-rx-improvements-in-linux-5-20](https://people.kernel.org/kuba/tls-1-3-rx-improvements-in-linux-5-20)
- 0xdeadbeefnetwork Copy_Fail2 (GCM variant) — [github.com/0xdeadbeefnetwork/Copy_Fail2-Electric_Boogaloo](https://github.com/0xdeadbeefnetwork/Copy_Fail2-Electric_Boogaloo)
- Linux source (torvalds/master) — `net/ipv4/ah4.c`, `net/ipv6/ah6.c`, `net/xfrm/xfrm_ipcomp.c`, `drivers/net/macsec.c`, `net/tls/tls_sw.c`