
pedit COW
CVE-2026-46331 (nicknamed “pedit COW”) is a local Linux kernel privilege-escalation flaw in the traffic-control subsystem. An unprivileged user (in an unprivileged network namespace) can configure the act_pedit (packet editor) filter to trigger a partial copy-on-write (COW) write into the page cache. In effect, the kernel writes attacker-controlled data into a file’s in-memory image without marking the page private, corrupting the cached copy of that file. Crucially, the exploit requires only CAP_NET_ADMIN (obtainable in a user namespace) and does not modify the on-disk file. In practice, a working proof-of-concept (PoC) called packet_edit_meme was published on June 17, 2026, demonstrating how to overwrite the page-cache image of a setuid binary (e.g. /bin/su) to spawn a root shell. The vulnerability stems from incorrect COW-range calculation in tcf_pedit_act() and has been fixed upstream (June 4, 2026) by moving the writable-region check into the per-key loop.
act_pedit. Unpatched stable releases (including many distro kernels) are vulnerable.tc pedit filter, and overwrites the ELF entry point of a setuid binary in memory with shellcode.skb_ensure_writable() inside the key loop). As a workaround, block or unload the act_pedit module or disable unprivileged user namespaces (e.g. sysctl user.max_user_namespaces=0). After mitigation, drop caches (echo 3 > /proc/sys/vm/drop_caches) to evict any poisoned pages.This report provides a detailed technical analysis of CVE-2026-46331: its cause, exploitation, detection, and remediation strategies, with references to vendor advisories, CVEs, and the public exploit.
Definition: CVE-2026-46331 is an out-of-bounds write bug in the Linux kernel’s Traffic Control (net/sched) subsystem, specifically in the act_pedit (packet editor) action. The function tcf_pedit_act() computes a “copy-on-write” range for packet-edit operations before iterating over typed keys, using a static hint tcfp_off_max_hint. However, some keys (e.g. TCP/UDP header edits) determine their final byte offset only at runtime. The code never re-checks writability for these dynamic offsets. As a result, writes can occur outside the pre-COW’d region: part of the packet write is never made private, leading to a partial COW. This erroneous write propagates into the shared page-cache memory of a file (if the packet buffers happen to reference file pages), corrupting the cached file image.
Background: The Linux packet editor (pedit) action allows administrators to rewrite arbitrary bytes within packet headers (link, network, or transport layers) as packets traverse a configured tc filter. It works by specifying an offset (possibly anchored to a header) and a 32-bit value/mask. Internally, pedit operates on socket-buffers (sk_buff) and must make the target packet memory writable before modifying it (via skb_ensure_writable() in COW fashion). Ideally, the kernel should clone (private-copy) any shared pages before writing to avoid altering memory used elsewhere.
Root Cause: In tcf_pedit_act(), the code mistakenly calculates the writable range only once upfront, using tcfp_off_max_hint (the maximum static offset). This hint does not include any runtime header offset that typed keys add when the packet is being processed. Keys like TCP or UDP can compute an offset based on the position of the IP header at runtime (for example, if an earlier key shifts the network header). Thus, during the per-key loop, the actual offset for a key may exceed the range that was pre-allocated as writable. The code then writes into packet memory via skb_store_bits(), but since the page beyond the pre-COW’d region was not made private, the write corrupts a page that is still shared with the page cache. In short, “calculating the writable packet range too early” causes an out-of-bounds, cross-page write. Negative offsets (e.g. editing Ethernet headers on ingress) are also mishandled, and even offset_valid() lacked a guard for INT_MIN, compounding the flaw.
Why It Happens: This bug is essentially a logic error in copy-on-write range calculation. The kernel assumed the static maximum offset (known at load time) was sufficient for all edits. It failed to update the COW range when keys with dynamic offsets were actually applied. After a series of queued edits, the final write could lie outside the pre-checked region. Because packet buffers may reference memory-mapped file pages (e.g. via zero-copy mechanisms), this “partial COW” write can reach the page cache of a file on disk. In practice, the packet editor action may receive pages from a sendfile or splice; thus a single packet filter operation can indirectly write attacker-chosen data into a file’s in-memory image, without altering the disk.
Components and Data Flow: The vulnerable code resides in the Linux net/sched subsystem (act_pedit.c). When a packet matches a configured pedit rule, tcf_pedit_act() is invoked. Internally it calls skb_ensure_writable(skb, X) exactly once, where X = tcfp_off_max_hint. This makes the first X bytes of the packet private (COW’d). Then, in a loop over each key (edit operation), it computes the key’s actual write offset by adding the runtime header offset to the key’s specified offset, and writes a 32-bit value into the packet. In pseudocode:
u32 off_max = action->tcfp_off_max_hint;
skb_ensure_writable(skb, off_max);
for (i = 0; i < num_keys; i++) {
u32 hdr_off = compute_header_offset(skb, key[i].hdr_type);
u32 write_off = hdr_off + key[i].offset;
skb_store_bits(skb, write_off, &key[i].value, 4);
}
Because hdr_off is computed only when processing each key, the initial skb_ensure_writable() call did not account for it. If hdr_off + key[i].offset exceeds off_max, the code falls back to skb_store_bits() on fragments rather than the main linear area, meaning it writes into a page not made private. That is the failure point.
Attack Surface: The only interface needed is the tc filter with a pedit action, which normally requires the CAP_NET_ADMIN capability. However, ordinary users can obtain CAP_NET_ADMIN within a private network namespace (user namespace cloning) without real privileges. Thus, an unprivileged user can enter a user+net namespace and create a tc pedit rule on loopback. The write occurs when a packet is processed (the attacker typically generates traffic on loopback to trigger it). The trust boundary (user vs kernel) is crossed because the kernel trusted its own COW setup, but the user-supplied offsets broke that assumption.
Internal Mechanism: On the kernel side, the vulnerability manifests as an out-of-bounds write (CWE-787). It corrupts kernel memory that is mapped into user space (file page cache). Specifically, it can overwrite the contents of any file page that happens to be mapped into the socket buffer. In the proof-of-concept, /bin/su is mmapped by sending it into the socket buffer, so the exploit flips its entry point bytes in memory. This does not modify the on-disk file, but any subsequent execution of that binary reads the poisoned image from the cache. The blog analysis notes: