
cve-2026-46331-audit script
Read-only audit script for CVE-2026-46331 (a.k.a. pedit COW) — a partial copy-on-write bug in the Linux kernel's net/sched act_pedit action that lets a local unprivileged user corrupt page cache memory and escalate to root.
____ _____ ____ ___ _____ ____ _____ __
| _ \| ____| _ \_ _|_ _| / ___/ _ \ \ / /
| |_) | _| | | | | | | | | | | | | \ \ /\ / /
| __/| |___| |_| | | | | | |__| |_| |\ V V /
|_| |_____|____/___| |_| \____\___/ \_/\_/
CVE-2026-46331 net/sched act_pedit partial COW
tcf_pedit_act() computes the COW range for skb_ensure_writable() once before the key loop, using tcfp_off_max_hint. Typed keys add a runtime header offset the hint does not cover, so part of the eventual write lands outside the COW'd region. Result: shared page-cache pages get scribbled on, and a cached setuid binary (classic target: /bin/su) can be poisoned in memory. On-disk hashes stay clean. File-integrity monitors will not see it.
Same bug family as Dirty COW (CVE-2016-5195), Dirty Pipe (CVE-2022-0847), Copy Fail (CVE-2026-31431), and Dirty Frag (CVE-2026-46300). The entry point is different, the page-ownership failure is the same.
Strictly read-only triage. Never loads a module, never touches sysctl, never executes a PoC. Designed to be safe to run on production.
act_pedit reachability across four independent signals:
lsmod)modinfo)/boot/config-$(uname -r), /proc/config.gz, modules.builtin)blacklist or install ... /bin/true override in modprobe.dtc action pedit rules (informational; no changes).user.max_user_namespaces, kernel.unprivileged_userns_clone, and the Ubuntu AppArmor userns restrictions.sudo ./cve-2026-46331-audit.sh # full text report
sudo ./cve-2026-46331-audit.sh --json # machine-readable JSON, no banner
sudo ./cve-2026-46331-audit.sh --quiet --no-hunt # one-line verdict for mass scans
sudo ./cve-2026-46331-audit.sh --since 2026-06-01 # widen IoC hunt window
sudo ./cve-2026-46331-audit.sh --no-banner # text report without the ASCII banner
See examples/sample-output.txt for a full run, and examples/sample-output.json for the JSON form.
The score is a weighted combination of:
Capped at 100. Anything above ~70 should be treated as urgent on multi-tenant / CI / Kubernetes nodes; under ~20 is usually a confirmation that the host is fine.
ansible -i inventory all -m script \
-a "cve-2026-46331-audit.sh --json --quiet --no-hunt" \
--become \
| tee /tmp/audit.jsonl
Then aggregate with jq:
grep -v '^[a-z]' /tmp/audit.jsonl | jq -s 'group_by(.verdict) | map({verdict: .[0].verdict, hosts: length})'
- name: audit CVE-2026-46331
ansible.builtin.script: cve-2026-46331-audit.sh --quiet --no-hunt
register: audit
failed_when: audit.rc == 2
changed_when: false
In order of preference. The script will recommend the right one based on what it found.
# Option 1: block act_pedit if you don't use it.
tc actions list action pedit # MUST be empty before doing this
echo 'install act_pedit /bin/true' | sudo tee /etc/modprobe.d/disable-act_pedit.conf
lsmod | grep -q act_pedit && sudo rmmod act_pedit
# Option 2: restrict unprivileged user namespaces.
# Will break rootless Podman/Docker, browser sandboxes, Flatpak, unprivileged unshare, some CI sandboxes.
sudo sysctl -w user.max_user_namespaces=0 # EL-family
sudo sysctl -w kernel.unprivileged_userns_clone=0 # Debian/Ubuntu
The real fix is a vendor kernel update plus a reboot. uname -r after reboot is the only thing that proves it.
This is the fifth iteration of the same architectural failure. Each one fixed the proximate cause, none fixed the root cause: zero-copy fast paths sharing pages across ownership boundaries, where every writer has to prove privacy before mutating bytes, in hundreds of call sites.
Rust-in-kernel addresses exactly this class via ownership types. It will not show up in net/sched tomorrow.
MIT. See LICENSE.
For authorised defensive use on systems you own or are explicitly permitted to audit. The script never executes the public PoC, never modifies system state, and never loads modules. Treat the verdict as triage, not as proof of compromise or proof of safety.
| CVE | CVE-2026-46331 |
| Component | Linux kernel net/sched / act_pedit |
| Class | Partial COW → page cache corruption → LPE |
| Attack vector | Local (CAP_NET_ADMIN, typically acquired via unprivileged userns) |
| Upstream affected | 5.18 .. 7.1-rc6 |
| Upstream fix | 7.1-rc7 |
| Public PoC | packet_edit_meme (verified RHEL 10, Debian 13, Ubuntu 24.04.4) |
| Red Hat severity | Important |
| SUSE CVSS 3.1 | 7.8 (AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H) |
/etc/os-releaseauditd and journalctl (configurable window via --since).PATCHED / NOT_APPLICABLE / MITIGATED / VULNERABLE / UNKNOWN.| Code | Meaning |
|---|
| 0 | PATCHED or NOT_APPLICABLE (kernel predates the bug) |
| 1 | MITIGATED (mitigation active, kernel still vulnerable, patch anyway) |
| 2 | VULNERABLE (one or more required preconditions met, no fixed kernel) |
| 3 | UNKNOWN (treat as suspect in shared / CI / Kubernetes contexts) |
| 4 | ERROR (script could not run; missing tools or bad environment) |
| Signal | Weight |
|---|
Vendor verdict VULNERABLE | +50 |
Vendor verdict UNKNOWN | +30 |
act_pedit built into kernel | +25 |
act_pedit loadable, no override | +20 |
act_pedit loadable, override active | +5 |
act_pedit currently loaded | +5 |
| Unprivileged userns reachable, no AppArmor gate | +15 |
| Unprivileged userns reachable, AppArmor gate active | +8 |
| IoCs found in hunt window | +10 |
| CVE | Year | Subsystem | Primitive |
|---|
| CVE-2016-5195 | 2016 | mm | COW race on read-only mapping |
| CVE-2022-0847 | 2022 | pipe / splice | pipe_buffer flag leak into page cache |
| CVE-2026-31431 | 2026 | crypto AF_ALG/AEAD | in-place crypto over read-only file cache |
| CVE-2026-46300 | 2026 | XFRM ESP-in-TCP | skbuff shared-frag marker loss |
| CVE-2026-46331 | 2026 | net/sched act_pedit | partial COW from late-resolved typed-key offset |