All of lore.kernel.org
 help / color / mirror / Atom feed
* [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
* [LTP] [PATCH v2] cve: add pwnescape reproducer
@ 2026-10-20 16:22 Andrea Cervesato
  2026-10-20 19:01 ` [LTP] " linuxtestproject.agent
  0 siblings, 1 reply; 4+ messages in thread
From: Andrea Cervesato @ 2026-10-20 16:22 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.
---
Changes in v2:
- add CONFIG_SYSCTL=y requirement
- import stddef.h for offset
- Link to v1: https://lore.kernel.org/20261020-pwnescape-v1-1-99e82e00fa36@suse.com

To: Linux Test Project <ltp@lists.linux.it>
---
 runtest/cve               |   1 +
 testcases/cve/.gitignore  |   1 +
 testcases/cve/pwnescape.c | 312 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 314 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..c829c2a75
--- /dev/null
+++ b/testcases/cve/pwnescape.c
@@ -0,0 +1,312 @@
+// 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 <stddef.h>
+
+#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",
+		"CONFIG_SYSCTL=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: 121876b3da3ff59b0dd17b4798b2e4699ba431d1
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

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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.