* [LTP] [PATCH] cve: add pwnescape reproducer
@ 2026-10-20 13:59 Andrea Cervesato
2026-10-20 14:51 ` [LTP] " linuxtestproject.agent
0 siblings, 1 reply; 4+ messages in thread
From: Andrea Cervesato @ 2026-10-20 13:59 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
Verify that IPv4 fragmentation validates csum_start before completing
partial checksums so bogus virtio_net_hdr checksum offsets cannot
overwrite the IPv4 header.
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
Before 6785011f8b16
tst_test.c:2061: TINFO: LTP version: 20260930-29-g34e338bbb
tst_test.c:2064: TINFO: Tested kernel: 7.3.0-rc5-virtme #18 SMP PREEMPT_DYNAMIC Tue Oct 20 15:48:53 CEST 2026 x86_64
tst_kconfig.c:90: TINFO: Parsing kernel config '/lib/modules/7.3.0-rc5-virtme/build/.config'
tst_kconfig.c:90: TINFO: Parsing kernel config '/lib/modules/7.3.0-rc5-virtme/build/.config'
tst_test.c:1895: TINFO: Overall timeout per run is 0h 00m 30s
[ 1.323287] tun: Universal TUN/TAP device driver, 1.6
pwnescape.c:217: TFAIL: IPv4 header IHL corrupted by checksum completion (15)
HINT: You _MAY_ be missing kernel fixes:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=6785011f8b16
After 6785011f8b16
tst_test.c:2061: TINFO: LTP version: 20260930-29-g34e338bbb
tst_test.c:2064: TINFO: Tested kernel: 7.3.0-rc5-virtme #19 SMP PREEMPT_DYNAMIC Tue Oct 20 15:52:44 CEST 2026 x86_64
tst_kconfig.c:90: TINFO: Parsing kernel config '/lib/modules/7.3.0-rc5-virtme/build/.config'
tst_kconfig.c:90: TINFO: Parsing kernel config '/lib/modules/7.3.0-rc5-virtme/build/.config'
tst_test.c:1895: TINFO: Overall timeout per run is 0h 00m 30s
[ 1.305821] tun: Universal TUN/TAP device driver, 1.6
[ 1.307941] modprobe (198) used greatest stack depth: 11968 bytes left
pwnescape.c:223: TPASS: kernel dropped the datagram with checksum metadata inside the IPv4 header
This reproducer has been generated by DeepSeep v4 Pro and tuned up by
Gemini 4 Argon.
---
runtest/cve | 1 +
testcases/cve/.gitignore | 1 +
testcases/cve/pwnescape.c | 309 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 311 insertions(+)
diff --git a/runtest/cve b/runtest/cve
index 894863e33..0fe25cd83 100644
--- a/runtest/cve
+++ b/runtest/cve
@@ -91,6 +91,7 @@ cve-2023-52879 fanotify25
cve-2026-43499 ghostlock
cve-2026-53362 setsockopt11
cve-2026-64600 refluxfs
+cve-2026-99999 pwnescape
# Tests below may cause kernel memory leak
cve-2020-25704 perf_event_open03
cve-2022-0185 fsconfig03
diff --git a/testcases/cve/.gitignore b/testcases/cve/.gitignore
index 418d67566..58246fd2d 100644
--- a/testcases/cve/.gitignore
+++ b/testcases/cve/.gitignore
@@ -17,5 +17,6 @@ cve-2025-38236
cve-2025-21756
cve-2026-46331
ghostlock
+pwnescape
refluxfs
sctphantom
diff --git a/testcases/cve/pwnescape.c b/testcases/cve/pwnescape.c
new file mode 100644
index 000000000..b46a01cc3
--- /dev/null
+++ b/testcases/cve/pwnescape.c
@@ -0,0 +1,309 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Linux Test Project
+ */
+
+/*\
+ * Regression test for commit 6785011f8b16 ("ipv4: validate
+ * checksum_start before completing checksum").
+ *
+ * A frame written to a TAP device can carry a virtio_net_hdr with
+ * VIRTIO_NET_HDR_F_NEEDS_CSUM, asking the stack to complete a partial
+ * checksum later. If csum_start points into the IPv4 header instead of
+ * at the transport header, skb_checksum_help() completes the checksum
+ * and stores the result at csum_start + csum_offset, overwriting the
+ * first two bytes of the IPv4 header that were already parsed. When
+ * such a packet needs to be fragmented, ip_do_fragment() then reads
+ * the corrupted version/IHL byte and copies an attacker-chosen number
+ * of "header" bytes, which can read past skb_headlen() and disclose
+ * adjacent kernel memory in the IPv4 options of the emitted fragments.
+ *
+ * The test loads the tun module when run as root and runs inside a
+ * private network namespace so that it needs no other privileges.
+ *
+ * [Algorithm]
+ *
+ * - create a TAP device with MAC 02:00:00:00:00:01, address
+ * 10.0.0.1/24 and MTU 200 inside a fresh network namespace
+ * - add a permanent neighbour entry for 10.0.0.3 on the TAP device
+ * - enable IPv4 forwarding in the network namespace
+ * - open an AF_PACKET (:manpage:`packet(7)`) socket bound to the TAP
+ * device
+ * - send a 320 byte control IPv4 datagram behind two VID 0 VLAN tags
+ * with checksum metadata at the transport header (frame offset 42)
+ * and verify its fragments are forwarded with an intact header
+ * - send the test datagram with csum_start on the IPv4 header (frame
+ * offset 22) and a payload word chosen so that completing the bogus
+ * checksum writes 0x4f (version 4, IHL 15) at the start of the IPv4
+ * header, then check whether the emitted fragments carry the
+ * corrupted header
+ */
+
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <linux/if_tun.h>
+#include <linux/virtio_net.h>
+
+#include "tst_test.h"
+#include "tst_module.h"
+#include "tst_netdevice.h"
+#include "tst_timer.h"
+#include "lapi/if_ether.h"
+#include "lapi/if_packet.h"
+#include "lapi/ioctl.h"
+
+#define TAP_NAME "pwnescape"
+#define TAP_MTU 200
+#define PAYLOAD_LEN 300
+
+#define IP_PROTO 253
+#define TTL 64
+
+#define CTRL_ID 0x2000
+#define TEST_ID 0x3000
+#define GOOD_IHL 5
+#define BAD_FIRST_BYTE 0x4f /* IPv4, IHL 15 */
+
+struct vlan_tag {
+ uint16_t tci;
+ uint16_t proto;
+} __attribute__((packed));
+
+struct pkt_frame {
+ struct ethhdr eth;
+ struct vlan_tag vlan[2];
+ struct iphdr ip;
+ uint16_t csum_adj;
+ uint8_t payload[PAYLOAD_LEN - sizeof(uint16_t)];
+} __attribute__((packed));
+
+static int tap_fd = -1;
+static int cap_fd = -1;
+
+static struct pkt_frame ctrl_frame;
+static struct pkt_frame test_frame;
+
+static const uint8_t tap_mac[ETH_ALEN] = {0x02, 0x00, 0x00, 0x00, 0x00, 0x01};
+static const uint8_t src_mac[ETH_ALEN] = {0x02, 0x00, 0x00, 0x00, 0x00, 0x02};
+static const uint8_t nbr_mac[ETH_ALEN] = {0x02, 0x00, 0x00, 0x00, 0x00, 0x03};
+
+static uint16_t ip_checksum(const void *buf, size_t len)
+{
+ const uint16_t *w = buf;
+ uint32_t s = 0;
+
+ for (size_t i = 0; i < len / 2; i++)
+ s += w[i];
+
+ s = (s & 0xffff) + (s >> 16);
+ s = (s & 0xffff) + (s >> 16);
+
+ return (uint16_t)~s;
+}
+
+static void build_frame(struct pkt_frame *fr, uint16_t id, int corrupt_iph)
+{
+ memset(fr, 0, sizeof(*fr));
+
+ memcpy(fr->eth.h_dest, tap_mac, ETH_ALEN);
+ memcpy(fr->eth.h_source, src_mac, ETH_ALEN);
+ fr->eth.h_proto = htons(ETH_P_8021Q);
+ fr->vlan[0].proto = htons(ETH_P_8021Q);
+ fr->vlan[1].proto = htons(ETH_P_IP);
+
+ fr->ip.version = 4;
+ fr->ip.ihl = GOOD_IHL;
+ fr->ip.tot_len = htons(sizeof(fr->ip) + PAYLOAD_LEN);
+ fr->ip.id = htons(id);
+ fr->ip.ttl = TTL;
+ fr->ip.protocol = IP_PROTO;
+ fr->ip.saddr = inet_addr("10.0.0.2");
+ fr->ip.daddr = inet_addr("10.0.0.3");
+ fr->ip.check = ip_checksum(&fr->ip, sizeof(fr->ip));
+
+ /* a valid IPv4 header always sums to 0xffff (-0 in one's complement)
+ * both before and after the forwarding TTL decrement, so with a zero
+ * payload the folded checksum over the whole IPv4 packet is simply
+ * ~csum_adj
+ */
+ if (corrupt_iph)
+ fr->csum_adj = htons(~(uint16_t)(BAD_FIRST_BYTE << 8));
+}
+
+/* write a frame to the TAP device with a virtio_net_hdr that asks
+ * for checksum completion at the given frame-relative csum_start
+ */
+static void send_datagram(struct pkt_frame *fr, uint16_t csum_start)
+{
+ struct virtio_net_hdr vnet_hdr = {
+ .flags = VIRTIO_NET_HDR_F_NEEDS_CSUM,
+ .csum_start = csum_start,
+ .csum_offset = 0,
+ };
+ struct iovec iov[] = {
+ {.iov_base = &vnet_hdr, .iov_len = sizeof(vnet_hdr)},
+ {.iov_base = fr, .iov_len = sizeof(*fr)},
+ };
+
+ SAFE_WRITEV(SAFE_WRITE_ALL, tap_fd, iov, ARRAY_SIZE(iov));
+}
+
+static void drain_capture(void)
+{
+ uint8_t buf[256];
+ ssize_t n;
+
+ do {
+ n = recv(cap_fd, buf, sizeof(buf), MSG_DONTWAIT);
+ } while (n > 0);
+
+ if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK)
+ tst_brk(TBROK | TERRNO, "recv() failed");
+}
+
+/* wait up to two seconds for a fragmented packet with the given IP id;
+ * store its IPv4 IHL in *ihl and return 1
+ */
+static int capture_fragment(uint16_t id, unsigned int *ihl)
+{
+ struct pollfd pfd = {.fd = cap_fd, .events = POLLIN};
+
+ tst_timer_start(CLOCK_MONOTONIC);
+
+ while (!tst_timer_expired_ms(2000)) {
+ struct {
+ struct ethhdr eth;
+ struct iphdr ip;
+ } __attribute__((packed)) hdr;
+ ssize_t n;
+
+ if (SAFE_POLL(&pfd, 1, 100) <= 0)
+ continue;
+
+ n = SAFE_RECV(0, cap_fd, &hdr, sizeof(hdr), 0);
+ if (n < (ssize_t)sizeof(hdr))
+ continue;
+
+ /* a forwarded fragment has MF set or a non-zero fragment
+ * offset; the injected frame itself never does, which
+ * distinguishes the forwarded output from its receive-side
+ * copy on the same device
+ */
+ if (hdr.eth.h_proto == htons(ETH_P_IP) &&
+ ntohs(hdr.ip.id) == id &&
+ ntohs(hdr.ip.frag_off) & (IP_MF | IP_OFFMASK)) {
+ *ihl = hdr.ip.ihl;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static void run(void)
+{
+ unsigned int ihl;
+
+ drain_capture();
+
+ send_datagram(&ctrl_frame, offsetof(struct pkt_frame, csum_adj));
+ if (!capture_fragment(CTRL_ID, &ihl))
+ tst_brk(TBROK, "control datagram was not forwarded back");
+ if (ihl != GOOD_IHL)
+ tst_brk(TBROK, "control datagram IHL corrupted (%u)", ihl);
+
+ send_datagram(&test_frame, offsetof(struct pkt_frame, ip));
+ if (capture_fragment(TEST_ID, &ihl) && ihl != GOOD_IHL) {
+ tst_res(TFAIL,
+ "IPv4 header IHL corrupted by checksum completion (%u)",
+ ihl);
+ return;
+ }
+
+ tst_res(TPASS,
+ "kernel dropped the datagram with checksum metadata inside the IPv4 header");
+}
+
+static void setup(void)
+{
+ struct ifreq ifr;
+ struct sockaddr_ll sll;
+ struct arpreq req;
+
+ if (!geteuid())
+ tst_modprobe("tun", NULL);
+
+ tst_setup_netns();
+
+ /* the tun socket must be opened in the test's own network
+ * namespace: TUNSETIFF checks for CAP_NET_ADMIN in the user
+ * namespace that owns the netns the file was opened in
+ */
+ tap_fd = open("/dev/net/tun", O_RDWR);
+ if (tap_fd < 0)
+ tst_brk(TCONF | TERRNO, "open /dev/net/tun failed");
+
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, TAP_NAME, IFNAMSIZ - 1);
+ ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR;
+ SAFE_IOCTL(tap_fd, TUNSETIFF, &ifr);
+
+ NETDEV_SET_HWADDR(TAP_NAME, tap_mac, sizeof(tap_mac));
+ NETDEV_SET_STATE(TAP_NAME, 1);
+ NETDEV_ADD_ADDRESS_INET(TAP_NAME, inet_addr("10.0.0.1"), 24, 0);
+
+ cap_fd = SAFE_SOCKET(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
+
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, TAP_NAME, IFNAMSIZ - 1);
+ ifr.ifr_mtu = TAP_MTU;
+ SAFE_IOCTL(cap_fd, SIOCSIFMTU, &ifr);
+
+ memset(&req, 0, sizeof(req));
+ tst_init_sockaddr_inet((struct sockaddr_in *)&req.arp_pa, "10.0.0.3", 0);
+ req.arp_ha.sa_family = ARPHRD_ETHER;
+ memcpy(req.arp_ha.sa_data, nbr_mac, sizeof(nbr_mac));
+ req.arp_flags = ATF_COM | ATF_PERM;
+ strncpy(req.arp_dev, TAP_NAME, IFNAMSIZ - 1);
+ SAFE_IOCTL(cap_fd, SIOCSARP, &req);
+
+ SAFE_FILE_PRINTF("/proc/sys/net/ipv4/ip_forward", "1");
+
+ memset(&sll, 0, sizeof(sll));
+ sll.sll_family = AF_PACKET;
+ sll.sll_protocol = htons(ETH_P_ALL);
+ sll.sll_ifindex = NETDEV_INDEX_BY_NAME(TAP_NAME);
+ SAFE_BIND(cap_fd, (struct sockaddr *)&sll, sizeof(sll));
+
+ build_frame(&ctrl_frame, CTRL_ID, 0);
+ build_frame(&test_frame, TEST_ID, 1);
+}
+
+static void cleanup(void)
+{
+ if (cap_fd != -1)
+ SAFE_CLOSE(cap_fd);
+
+ if (tap_fd != -1)
+ SAFE_CLOSE(tap_fd);
+}
+
+static struct tst_test test = {
+ .test_all = run,
+ .setup = setup,
+ .cleanup = cleanup,
+ .needs_kconfigs = (const char *[]) {
+ "CONFIG_TUN",
+ "CONFIG_USER_NS=y",
+ "CONFIG_NET_NS=y",
+ NULL
+ },
+ .save_restore = (const struct tst_path_val[]) {
+ {PATH_USER_MAX_USER_NAMESPACES, "1024", TST_SR_SKIP},
+ {}
+ },
+ .tags = (const struct tst_tag[]) {
+ {"linux-git", "6785011f8b16"},
+ {}
+ },
+};
---
base-commit: 2ae4cfe69b9010018f2e5df6fa4bbba21810d4f2
change-id: 20261020-pwnescape-e3a7a61211f3
Best regards,
--
Andrea Cervesato <andrea.cervesato@suse.com>
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [LTP] cve: add pwnescape reproducer
2026-10-20 13:59 [LTP] [PATCH] cve: add pwnescape reproducer Andrea Cervesato
@ 2026-10-20 14:51 ` linuxtestproject.agent
2026-10-20 16:22 ` Andrea Cervesato via ltp
0 siblings, 1 reply; 4+ messages in thread
From: linuxtestproject.agent @ 2026-10-20 14:51 UTC (permalink / raw)
To: Andrea Cervesato; +Cc: ltp
Hi Andrea,
On 20 Oct 2026, Andrea Cervesato wrote:
> cve: add pwnescape reproducer
> + SAFE_FILE_PRINTF("/proc/sys/net/ipv4/ip_forward", "1");
Medium severity: CONFIG_TUN and the namespace dependencies allow
CONFIG_SYSCTL=n. On that configuration this path is absent, so setup
reports TBROK instead of TCONF; add CONFIG_SYSCTL=y to .needs_kconfigs.
Verdict - Needs revision
---
Note:
The agent can sometimes produce false positives although often its
findings are genuine. If you find issues with the review, please
comment this email or ignore the suggestions.
Regards,
LTP AI Reviewer
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [LTP] cve: add pwnescape reproducer
2026-10-20 14:51 ` [LTP] " linuxtestproject.agent
@ 2026-10-20 16:22 ` Andrea Cervesato via ltp
0 siblings, 0 replies; 4+ messages in thread
From: Andrea Cervesato via ltp @ 2026-10-20 16:22 UTC (permalink / raw)
To: linuxtestproject.agent; +Cc: ltp
> Hi Andrea,
>
> On 20 Oct 2026, Andrea Cervesato wrote:
> > cve: add pwnescape reproducer
>
> > + SAFE_FILE_PRINTF("/proc/sys/net/ipv4/ip_forward", "1");
>
> Medium severity: CONFIG_TUN and the namespace dependencies allow
> CONFIG_SYSCTL=n. On that configuration this path is absent, so setup
> reports TBROK instead of TCONF; add CONFIG_SYSCTL=y to .needs_kconfigs.
This is valid
--
Andrea Cervesato
SUSE QE Automation Engineer Linux
andrea.cervesato@suse.com
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [LTP] cve: add pwnescape reproducer
2026-10-20 16:22 [LTP] [PATCH v2] " Andrea Cervesato
@ 2026-10-20 19:01 ` linuxtestproject.agent
0 siblings, 0 replies; 4+ messages in thread
From: linuxtestproject.agent @ 2026-10-20 19:01 UTC (permalink / raw)
To: Andrea Cervesato; +Cc: ltp
Hi Andrea,
On Tue, 20 Oct 2026, Andrea Cervesato wrote:
> cve: add pwnescape reproducer
> + .tags = (const struct tst_tag[]) {
> + {"linux-git", "6785011f8b16"},
> + {}
> + },
Add {"CVE", "2026-99999"} to .tags in testcases/cve/pwnescape.c.
Without it, metadata extraction assigns only the regression group, so
CVE-based metadata selection and the generated CVE catalog omit this test;
the runtest/cve entry does not populate that metadata.
Runtime verification was blocked by unshare(CLONE_NEWUSER) returning
EPERM in this environment, before the forwarding behavior was exercised.
Verdict - Needs revision
---
Note:
The agent can sometimes produce false positives although often its
findings are genuine. If you find issues with the review, please
comment this email or ignore the suggestions.
Regards,
LTP AI Reviewer
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-20 19:02 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-20 13:59 [LTP] [PATCH] cve: add pwnescape reproducer Andrea Cervesato
2026-10-20 14:51 ` [LTP] " linuxtestproject.agent
2026-10-20 16:22 ` Andrea Cervesato via ltp
-- strict thread matches above, loose matches on Subject: below --
2026-10-20 16:22 [LTP] [PATCH v2] " Andrea Cervesato
2026-10-20 19:01 ` [LTP] " linuxtestproject.agent
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox