
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.
| 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) |
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)install ... /bin/true override in modprobe.d, or by blacklist on kernels that autoload act_pedit through its net-act-pedit alias (6.9+; older kernels request it by name, which blacklist does not stop)tc action pedit rules (informational; no changes).user.max_user_namespaces, kernel.unprivileged_userns_clone, user.max_net_namespaces, and the Ubuntu AppArmor userns restrictions./etc/os-release (RHEL 8/9/10, Debian 11/12/13/14, Ubuntu 18.04→26.04, SUSE, Amazon Linux, Arch-family rolling).auditd and journalctl (configurable window via --since and --until).PATCHED / NOT_APPLICABLE / MITIGATED / VULNERABLE / UNKNOWN.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 --since 2026-06-01 --until 2026-09-01 # bounded IoC window
sudo ./cve-2026-46331-audit.sh --no-banner # text report without the ASCII banner
./cve-2026-46331-audit.sh --version # print script version and exit
| 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) |
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:
| 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 or blacklist active | +5 |
act_pedit currently loaded | +5 |
| Unprivileged userns+netns reachable, no AppArmor gate | +15 |
| Unprivileged userns+netns reachable, AppArmor gate active | +8 |
| IoCs found in hunt window | +10 |
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
# 1a) Hard block via modprobe install override (strongest).
echo 'install act_pedit /bin/true' | sudo tee /etc/modprobe.d/disable-act_pedit.conf
# 1b) `blacklist act_pedit` only stops the autoload on kernels 6.9+, which
# request the "net-act-pedit" alias. Older kernels load it by name and
# ignore the blacklist, so prefer 1a. Check your kernel:
modinfo -F alias act_pedit | grep -qx net-act-pedit && echo "blacklist works here"
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
# Option 2b: restrict network namespaces (breaks the CAP_NET_ADMIN acquisition path).
# Less disruptive than disabling all user namespaces, but still breaks some container tooling.
sudo sysctl -w user.max_net_namespaces=0
The real fix is a vendor kernel update plus a reboot. uname -r after reboot is the only thing that proves it.