## https://sploitus.com/exploit?id=217E804A-7275-5DD0-8E73-C1B32AE8550D
# cve-2026-46331-audit
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
```
## TL;DR
`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) |
## What this script does
Strictly read-only triage. Never loads a module, never touches sysctl, never executes a PoC. Designed to be safe to run on production.
- Inventories running kernel, distro, kernel package version.
- Probes `act_pedit` reachability across four independent signals:
- currently loaded (`lsmod`)
- loadable on demand (`modinfo`)
- built into the running kernel (`/boot/config-$(uname -r)`, `/proc/config.gz`, `modules.builtin`)
- blocked by `blacklist` or `install ... /bin/true` override in `modprobe.d`
- Lists existing `tc action pedit` rules (informational; no changes).
- Reads userns gates: `user.max_user_namespaces`, `kernel.unprivileged_userns_clone`, and the Ubuntu AppArmor userns restrictions.
- Heuristic vendor patch matrix per `/etc/os-release` (RHEL 8/9/10, Debian 11/12/13/14, Ubuntu 18.04โ26.04, SUSE, Amazon Linux, Arch-family rolling).
- Optional behavioural IoC hunt via `auditd` and `journalctl` (configurable window via `--since`).
- Risk score 0-100 and a final verdict: `PATCHED` / `NOT_APPLICABLE` / `MITIGATED` / `VULNERABLE` / `UNKNOWN`.
- Exit codes designed for fleet orchestration.
## Usage
```bash
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
```
### Exit codes
| 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) |
### Sample output
See [`examples/sample-output.txt`](examples/sample-output.txt) for a full run, and [`examples/sample-output.json`](examples/sample-output.json) for the JSON form.
## Risk scoring model
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 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 |
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.
## Mass deployment
### Ansible
```bash
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`:
```bash
grep -v '^[a-z]' /tmp/audit.jsonl | jq -s 'group_by(.verdict) | map({verdict: .[0].verdict, hosts: length})'
```
### Failed_when in a play
```yaml
- 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
```
## Mitigations (if you need to delay patching)
In order of preference. The script will recommend the right one based on what it found.
```bash
# 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
```
```bash
# 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.
## Bug family context
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.
| 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 |
Rust-in-kernel addresses exactly this class via ownership types. It will not show up in `net/sched` tomorrow.
## References
- NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-46331
- Red Hat: https://access.redhat.com/security/vulnerabilities/RHSB-2026-008
- Ubuntu: https://ubuntu.com/security/CVE-2026-46331
- Debian: https://security-tracker.debian.org/tracker/CVE-2026-46331
- Upstream commit & description: see the NVD record references.
## License
MIT. See [LICENSE](LICENSE).
## Disclaimer
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.