Package: release.debian.org
Severity: normal
Tags: bookworm
X-Debbugs-Cc: [email protected]
Control: affects -1 + src:openvswitch
User: [email protected]
Usertags: pu

Hi,

I'd like to update openvswitch to fix CVE-2026-34956.

[ Tests ]
There's included unit tests, including new ones that are actually
testing the bug.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in (old)stable
  [x] the issue is verified as fixed in unstable

Note that the changelog in attached debdiff targets trixie-security,
though I'll obviously fix this before rebuild and upload, so please
ignore it.

Cheers,

Thomas Goirand (zigo)
diff -Nru openvswitch-3.1.0/debian/changelog openvswitch-3.1.0/debian/changelog
--- openvswitch-3.1.0/debian/changelog  2024-02-18 16:46:26.000000000 +0100
+++ openvswitch-3.1.0/debian/changelog  2026-04-01 09:34:09.000000000 +0200
@@ -1,3 +1,11 @@
+openvswitch (3.1.0-2+deb12u2) bookworm-security; urgency=medium
+
+  * CVE-2026-34956: Invalid memory access in conntrack FTP alg. Applied
+    upstream patch: conntrack: Fix replace_substring to handle larger packets.
+    (Closes: #1132449).
+
+ -- Thomas Goirand <[email protected]>  Wed, 01 Apr 2026 09:34:09 +0200
+
 openvswitch (3.1.0-2+deb12u1) bookworm-security; urgency=medium
 
   * CVE-2023-5366: A flaw was found in Open vSwitch that allows ICMPv6 Neighbor
diff -Nru 
openvswitch-3.1.0/debian/patches/CVE-2026-34956_conntrack_fix_replace_substring_to_handle_larger_packets.patch
 
openvswitch-3.1.0/debian/patches/CVE-2026-34956_conntrack_fix_replace_substring_to_handle_larger_packets.patch
--- 
openvswitch-3.1.0/debian/patches/CVE-2026-34956_conntrack_fix_replace_substring_to_handle_larger_packets.patch
      1970-01-01 01:00:00.000000000 +0100
+++ 
openvswitch-3.1.0/debian/patches/CVE-2026-34956_conntrack_fix_replace_substring_to_handle_larger_packets.patch
      2026-04-01 09:34:09.000000000 +0200
@@ -0,0 +1,282 @@
+Author: Aaron Conole <[email protected]>
+Date: Tue, 31 Mar 2026 14:39:23 +0200
+Description: 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 <[email protected]>
+Signed-off-by: Aaron Conole <[email protected]>
+Signed-off-by: Ilya Maximets <[email protected]>
+Origin: upstream, 
https://github.com/openvswitch/ovs/commit/8e81545d9d1461758c72fbd08ce1f52e472bca94.patch
+Bug-Debian: https://bugs.debian.org/1132449
+Last-Update: 2026-04-01
+
+Index: openvswitch/AUTHORS.rst
+===================================================================
+--- openvswitch.orig/AUTHORS.rst
++++ openvswitch/AUTHORS.rst
+@@ -698,6 +698,7 @@ Scott Hendricks
+ Sean Brady                      [email protected]
+ Sebastian Andrzej Siewior       [email protected]
+ Sébastien RICCIO                [email protected]
++Seiji Sakurai                   [email protected]
+ Shweta Seth                     [email protected]
+ Simon Jouet                     [email protected]
+ Spiro Kourtessis                [email protected]
+Index: openvswitch/lib/conntrack.c
+===================================================================
+--- openvswitch.orig/lib/conntrack.c
++++ openvswitch/lib/conntrack.c
+@@ -2898,9 +2898,9 @@ expectation_create(struct conntrack *ct,
+ }
+ 
+ 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);
+@@ -2918,6 +2918,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,
+Index: openvswitch/tests/library.at
+===================================================================
+--- openvswitch.orig/tests/library.at
++++ openvswitch/tests/library.at
+@@ -283,3 +283,7 @@ AT_CLEANUP
+ AT_SETUP([uuidset module])
+ AT_CHECK([ovstest test-uuidset], [0], [], [ignore])
+ AT_CLEANUP
++
++AT_SETUP([Conntrack Library - FTP ALG parsing])
++AT_CHECK([ovstest test-conntrack ftp-alg-large-payload])
++AT_CLEANUP
+Index: openvswitch/tests/test-conntrack.c
+===================================================================
+--- openvswitch.orig/tests/test-conntrack.c
++++ openvswitch/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)
+ {
+@@ -252,6 +346,87 @@ test_pcap(struct ovs_cmdl_context *ctx)
+     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,
++                      0, 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,
++                      0, 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. */
+     /* Starts 'n_threads' threads. Each thread will send 'n_pkts' packets to
+@@ -264,6 +439,13 @@ static const struct ovs_cmdl_command com
+      * '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},
+ };
diff -Nru openvswitch-3.1.0/debian/patches/series 
openvswitch-3.1.0/debian/patches/series
--- openvswitch-3.1.0/debian/patches/series     2024-02-18 16:46:26.000000000 
+0100
+++ openvswitch-3.1.0/debian/patches/series     2026-04-01 09:34:09.000000000 
+0200
@@ -2,3 +2,4 @@
 CVE-2023-1668_ofproto-dpif-xlate_Always_mask_ip_proto_field.patch
 CVE-2023-5366-Fix-missing-masks-on-a-final-stage-with-ports-trie.patch
 CVE-2023-3966-netdev-offload-tc_Check_geneve_metadata_length.patch
+CVE-2026-34956_conntrack_fix_replace_substring_to_handle_larger_packets.patch

Reply via email to