diff --git a/SPECS/openvswitch/CVE-2026-34956.patch b/SPECS/openvswitch/CVE-2026-34956.patch new file mode 100644 index 00000000000..4f6baf05c9d --- /dev/null +++ b/SPECS/openvswitch/CVE-2026-34956.patch @@ -0,0 +1,279 @@ +From 0034c6f9a642d5462ca073b82db2ea6b6400f8a7 Mon Sep 17 00:00:00 2001 +From: Aaron Conole +Date: Tue, 31 Mar 2026 14:39:23 +0200 +Subject: [PATCH] conntrack: Fix replace_substring to handle larger packets. + +There is a buffer size calculation issue in replace_string that can +result in a heap overflow with a specially crafted FTP packet. This +is a result of integer truncation when downscaling from size_t into +uint8_t size. Correct this by setting the types to size_t until the +underlying memmove to keep the sizes intact. + +The total_size, substr_size, and rep_str_size are expected to all be +sane values for the memmove, and modify_packet also expects this, so +document that as well. In the case of FTP, those are enforced in +repl_ftp_v*_addr at the checks for MAX_FTP_V*_NAT_DELTA, and the +packet data itself should be sanitized by the ovs_strlcpy that runs +early to extract a string of appropriate length. + +Fixes: bd5e81a0e596 ("Userspace Datapath: Add ALG infra and FTP.") +Reported-by: Seiji Sakurai +Signed-off-by: Aaron Conole +Signed-off-by: Ilya Maximets +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/openvswitch/ovs/commit/8e81545d9d1461758c72fbd08ce1f52e472bca94.patch +--- + lib/conntrack.c | 9 +- + tests/library.at | 4 + + tests/test-conntrack.c | 182 +++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 192 insertions(+), 3 deletions(-) + +diff --git a/lib/conntrack.c b/lib/conntrack.c +index 013709b..10ff0c7 100644 +--- a/lib/conntrack.c ++++ b/lib/conntrack.c +@@ -2993,9 +2993,9 @@ expectation_create(struct conntrack *ct, ovs_be16 dst_port, + } + + static void +-replace_substring(char *substr, uint8_t substr_size, +- uint8_t total_size, char *rep_str, +- uint8_t rep_str_size) ++replace_substring(char *substr, size_t substr_size, ++ size_t total_size, char *rep_str, ++ size_t rep_str_size) + { + memmove(substr + rep_str_size, substr + substr_size, + total_size - substr_size); +@@ -3013,6 +3013,9 @@ repl_bytes(char *str, char c1, char c2) + } + } + ++/* Replaces a substring in the packet and rewrites the packet ++ * size to match. This function assumes the caller has verified ++ * the lengths to prevent under/over flow. */ + static void + modify_packet(struct dp_packet *pkt, char *pkt_str, size_t size, + char *repl_str, size_t repl_size, +diff --git a/tests/library.at b/tests/library.at +index 7b4aceb..2079273 100644 +--- a/tests/library.at ++++ b/tests/library.at +@@ -306,3 +306,7 @@ AT_CHECK([ovstest test-cooperative-multitasking-nested-yield], [0], [], [dnl + cooperative_multitasking|ERR|Nested yield avoided, this is a bug! Enable debug logging for more details. + ]) + AT_CLEANUP ++ ++AT_SETUP([Conntrack Library - FTP ALG parsing]) ++AT_CHECK([ovstest test-conntrack ftp-alg-large-payload]) ++AT_CLEANUP +diff --git a/tests/test-conntrack.c b/tests/test-conntrack.c +index 292b6c0..e59f6cc 100644 +--- a/tests/test-conntrack.c ++++ b/tests/test-conntrack.c +@@ -29,6 +29,100 @@ + static const char payload[] = "50540000000a50540000000908004500001c0000000000" + "11a4cd0a0101010a0101020001000200080000"; + ++/* Build an Ethernet + IPv4 packet. If 'pkt' is NULL a new buffer is ++ * allocated with 64 bytes of extra headroom so the FTP MTU guard passes. ++ * The buffer is populated up through the IP header; l4 is set to point ++ * directly after the IP header. The caller is responsible for filling ++ * the L4 header and payload that follow. */ ++static struct dp_packet * ++build_eth_ip_packet(struct dp_packet *pkt, struct eth_addr eth_src, ++ struct eth_addr eth_dst, ovs_be32 ip_src, ovs_be32 ip_dst, ++ uint8_t proto, uint16_t payload_alloc) ++{ ++ struct ip_header *iph; ++ uint16_t proto_len; ++ ++ switch (proto) { ++ case IPPROTO_TCP: proto_len = TCP_HEADER_LEN; break; ++ case IPPROTO_UDP: proto_len = UDP_HEADER_LEN; break; ++ case IPPROTO_ICMP: proto_len = ICMP_HEADER_LEN; break; ++ default: proto_len = 0; break; ++ } ++ ++ if (pkt == NULL) { ++ /* 64-byte extra headroom keeps dp_packet_get_allocated() large enough ++ * that the FTP V4 MTU guard (orig_used_size + 8 <= allocated) passes ++ * even when the packet is near its maximum size. */ ++ pkt = dp_packet_new_with_headroom(ETH_HEADER_LEN + IP_HEADER_LEN ++ + proto_len + payload_alloc, 64); ++ } ++ ++ eth_compose(pkt, eth_src, eth_dst, ETH_TYPE_IP, ++ IP_HEADER_LEN + proto_len + payload_alloc); ++ iph = dp_packet_l3(pkt); ++ iph->ip_ihl_ver = IP_IHL_VER(5, 4); ++ iph->ip_tot_len = htons(IP_HEADER_LEN + proto_len + payload_alloc); ++ iph->ip_ttl = 64; ++ iph->ip_proto = proto; ++ packet_set_ipv4_addr(pkt, &iph->ip_src, ip_src); ++ packet_set_ipv4_addr(pkt, &iph->ip_dst, ip_dst); ++ iph->ip_csum = csum(iph, IP_HEADER_LEN); ++ dp_packet_set_l4(pkt, (char *) iph + IP_HEADER_LEN); ++ return pkt; ++} ++ ++/* Fill the TCP header and optional payload for a packet previously built with ++ * build_eth_ip_packet(). The 'payload' buffer of 'payload_len' bytes is ++ * appended after the TCP header if non-NULL. IP total-length, IP checksum, ++ * and TCP checksum are all updated to reflect the final packet contents. */ ++static struct dp_packet * ++build_tcp_packet(struct dp_packet *pkt, uint16_t tcp_src, uint16_t tcp_dst, ++ uint16_t tcp_flags, const char *tcp_payload, ++ size_t payload_len) ++{ ++ struct tcp_header *tcph; ++ struct ip_header *iph; ++ uint16_t ip_tot_len; ++ uint32_t tcp_csum; ++ struct flow flow; ++ ++ ovs_assert(pkt); ++ tcph = dp_packet_l4(pkt); ++ ovs_assert(tcph); ++ ++ tcph->tcp_src = htons(tcp_src); ++ tcph->tcp_dst = htons(tcp_dst); ++ put_16aligned_be32(&tcph->tcp_seq, 0); ++ put_16aligned_be32(&tcph->tcp_ack, 0); ++ tcph->tcp_ctl = TCP_CTL(tcp_flags, TCP_HEADER_LEN / 4); ++ tcph->tcp_winsz = htons(65535); ++ tcph->tcp_csum = 0; ++ tcph->tcp_urg = 0; ++ ++ if (tcp_payload && payload_len > 0) { ++ /* The caller must have pre-allocated space via build_eth_ip_packet's ++ * payload_alloc argument. Write directly to avoid a realloc that ++ * would lose the extra headroom required by the FTP MTU guard. */ ++ memcpy((char *) tcph + TCP_HEADER_LEN, tcp_payload, payload_len); ++ } ++ ++ /* Update IP total length and recompute IP checksum. */ ++ iph = dp_packet_l3(pkt); ++ ip_tot_len = IP_HEADER_LEN + TCP_HEADER_LEN + payload_len; ++ iph->ip_tot_len = htons(ip_tot_len); ++ iph->ip_csum = 0; ++ iph->ip_csum = csum(iph, IP_HEADER_LEN); ++ ++ /* Compute TCP checksum over pseudo-header + TCP segment. */ ++ tcp_csum = packet_csum_pseudoheader(iph); ++ tcph->tcp_csum = csum_finish( ++ csum_continue(tcp_csum, tcph, TCP_HEADER_LEN + payload_len)); ++ ++ /* Set l3/l4 offsets so conntrack can extract a flow key. */ ++ flow_extract(pkt, &flow); ++ return pkt; ++} ++ + static struct dp_packet_batch * + prepare_packets(size_t n, bool change, unsigned tid, ovs_be16 *dl_type) + { +@@ -251,6 +345,87 @@ test_pcap(struct ovs_cmdl_context *ctx) + conntrack_destroy(ct); + ovs_pcap_close(pcap); + } ++ ++/* ALG related testing. */ ++ ++/* FTP IPv4 PORT payload for testing. */ ++#define FTP_PORT_CMD_STR "PORT 192,168,123,2,113,42\r\n" ++#define FTP_CMD_PAD 234 ++#define FTP_PAYLOAD_LEN (sizeof FTP_PORT_CMD_STR - 1 + FTP_CMD_PAD) ++ ++/* Test modify_packet wrapping. ++ * ++ * The test builds a minimal FTP control-channel exchange: ++ * 1. A TCP SYN that creates a conntrack entry with helper=ftp and SNAT. ++ * 2. A PSH|ACK carrying "PORT 192,168,123,2,113,42\r\n" padded to exactly ++ * 261 bytes of TCP payload, which makes total_size == 256. ++ * ++ * After the PORT packet is processed the address field in the payload must ++ * read "192,168,1,1" (the SNAT address with dots replaced by commas). */ ++static void ++test_ftp_alg_large_payload(struct ovs_cmdl_context *ctx OVS_UNUSED) ++{ ++ /* Packet endpoints. */ ++ struct eth_addr eth_src = ETH_ADDR_C(00, 01, 02, 03, 04, 05); ++ struct eth_addr eth_dst = ETH_ADDR_C(00, 06, 07, 08, 09, 0a); ++ ovs_be32 ip_src = inet_addr("192.168.123.2"); /* FTP client. */ ++ ovs_be32 ip_dst = inet_addr("192.168.1.1"); /* FTP server / SNAT addr. */ ++ uint16_t sport = 12345; ++ uint16_t dport = 21; /* FTP control port. */ ++ ++ /* SNAT: rewrite client address to 192.168.1.1 in PORT commands. */ ++ struct nat_action_info_t nat_info; ++ memset(&nat_info, 0, sizeof nat_info); ++ nat_info.nat_action = NAT_ACTION_SRC; ++ nat_info.min_addr.ipv4 = ip_dst; ++ nat_info.max_addr.ipv4 = ip_dst; ++ ++ ct = conntrack_init(); ++ conntrack_set_tcp_seq_chk(ct, false); ++ ++ long long now = time_msec(); ++ ++ struct dp_packet *syn = build_eth_ip_packet(NULL, eth_src, eth_dst, ++ ip_src, ip_dst, ++ IPPROTO_TCP, 0); ++ build_tcp_packet(syn, sport, dport, TCP_SYN, NULL, 0); ++ ++ struct dp_packet_batch syn_batch; ++ dp_packet_batch_init_packet(&syn_batch, syn); ++ conntrack_execute(ct, &syn_batch, htons(ETH_TYPE_IP), false, true, 0, ++ NULL, NULL, "ftp", &nat_info, now, 0); ++ dp_packet_delete_batch(&syn_batch, true); ++ ++ /* We get to skip some of the processing because the conntrack execute ++ * above will create the required conntrack entries. */ ++ ++ /* Build the large payload: PORT command followed by padding spaces ++ * and a final "\r\n" to reach exactly FTP_PAYLOAD_LEN bytes. The ++ * FTP parser only looks at the first LARGEST_FTP_MSG_OF_INTEREST (128) ++ * bytes, so the trailing spaces do not interfere with parsing. */ ++ char ftp_payload[FTP_PAYLOAD_LEN]; ++ memcpy(ftp_payload, FTP_PORT_CMD_STR, sizeof FTP_PORT_CMD_STR - 1); ++ memset(ftp_payload + sizeof FTP_PORT_CMD_STR - 1, ' ', FTP_CMD_PAD); ++ ++ struct dp_packet *port_pkt = ++ build_eth_ip_packet(NULL, eth_src, eth_dst, ip_src, ip_dst, ++ IPPROTO_TCP, FTP_PAYLOAD_LEN); ++ build_tcp_packet(port_pkt, sport, dport, TCP_PSH | TCP_ACK, ++ ftp_payload, FTP_PAYLOAD_LEN); ++ ++ struct dp_packet_batch port_batch; ++ dp_packet_batch_init_packet(&port_batch, port_pkt); ++ conntrack_execute(ct, &port_batch, htons(ETH_TYPE_IP), false, true, 0, ++ NULL, NULL, "ftp", &nat_info, now, 0); ++ ++ struct tcp_header *th = dp_packet_l4(port_pkt); ++ size_t tcp_hdr_len = TCP_OFFSET(th->tcp_ctl) * 4; ++ const char *ftp_start = (const char *) th + tcp_hdr_len; ++ ovs_assert(!strncmp(ftp_start, "PORT 192,168,1,1,", 17)); ++ dp_packet_delete_batch(&port_batch, true); ++ conntrack_destroy(ct); ++} ++ + + static const struct ovs_cmdl_command commands[] = { + /* Connection tracker tests. */ +@@ -264,6 +439,13 @@ static const struct ovs_cmdl_command commands[] = { + * 'batch_size' (1 by default) per call, with the commit flag set. + * Prints the ct_state of each packet. */ + {"pcap", "file [batch_size]", 1, 2, test_pcap, OVS_RO}, ++ /* Verifies that the FTP ALG replace_substring function correctly handles ++ * a packet whose payload puts total_size at exactly 256 bytes. The ++ * original uint8_t parameter type truncated 256 to 0, leading to a ++ * near-SIZE_MAX memmove (heap overflow). The test confirms the address ++ * is rewritten to the SNAT target rather than causing a crash. */ ++ {"ftp-alg-large-payload", "", 0, 0, ++ test_ftp_alg_large_payload, OVS_RO}, + + {NULL, NULL, 0, 0, NULL, OVS_RO}, + }; +-- +2.45.4 + diff --git a/SPECS/openvswitch/openvswitch.spec b/SPECS/openvswitch/openvswitch.spec index 93420038476..3979a3dcc03 100644 --- a/SPECS/openvswitch/openvswitch.spec +++ b/SPECS/openvswitch/openvswitch.spec @@ -20,7 +20,7 @@ Summary: Open vSwitch daemon/database/utilities Name: openvswitch Version: 3.3.0 -Release: 2%{?dist} +Release: 3%{?dist} License: ASL 2.0 AND LGPLv2+ AND SISSL Vendor: Microsoft Corporation Distribution: Azure Linux @@ -34,6 +34,7 @@ Source1: openvswitch.sysusers # OVS (including OVN) backports (0 - 300) Patch0: 0001-tests-Fix-SSL-db-implementation-test-with-openssl-3..patch Patch10: 0001-tests-Fix-compatibility-issue-with-Python-3.13-in-vl.patch +Patch11: CVE-2026-34956.patch BuildRequires: gcc gcc-c++ make BuildRequires: autoconf automake libtool @@ -505,6 +506,9 @@ fi %{_sysusersdir}/openvswitch.conf %changelog +* Fri May 15 2026 Azure Linux Security Servicing Account - 3.3.0-3 +- Patch for CVE-2026-34956 + * Thu Jan 08 2026 Tobias Brick - 3.3.0-2 - Add patches from fedora f40 to fix tests with new versions of openssl and python. - Update to use correct locations for license files.