On 8/9/26 3:03 PM, Eli Britstein wrote:
> Commit [1] fixed the reassembly size cap to use dp_packet_size() rather
> than the IPv4 total-length or IPv6 payload-length of the first fragment.
> Those L3 fields describe only that fragment and exclude the Ethernet
> header, so they understated the buffer size being reassembled into.
> 
> That check still uses uint16_t start/end offsets per fragment.  If a
> fragment's data ends beyond byte 65535, the end offset wraps and the
> stored bounds are wrong.  rest_len can become negative while
> orig_len + rest_len still fits under the cap, so reassembly proceeds on
> a corrupt fragment list.
> 
> The wrapped bounds can still satisfy ipf_list_complete().  The negative
> rest_len is then passed as size_t to dp_packet_prealloc_tailroom(), and
> add_len wraps to a huge size_t in dp_packet_put(), causing out-of-
> bounds reads and writes on the reassembly buffer and heap corruption.
> 
> Reject such fragments when extracting keys and fail reassembly when
> rest_len is not positive.
> 
> [1] c3f4d9fe54f4 ("ipf: Fix the over-sized reassembly.")
> 
> Assisted-by: composer-2.5-fast, Cursor
> Fixes: 4ea96698f667 ("Userspace datapath: Add fragmentation handling.")
> Signed-off-by: Eli Britstein <[email protected]>
> ---
>  lib/ipf.c             | 46 ++++++++++++++++++++++++++++++++-----------
>  tests/ofproto-dpif.at | 45 ++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 80 insertions(+), 11 deletions(-)
> 
> diff --git a/lib/ipf.c b/lib/ipf.c
> index d836b8824..274c02f84 100644
> --- a/lib/ipf.c
> +++ b/lib/ipf.c
> @@ -434,7 +434,7 @@ ipf_reassemble_v4_frags(struct ipf_list *ipf_list)
>      int rest_len = frag_list[ipf_list->last_inuse_idx].end_data_byte -
>                     frag_list[1].start_data_byte + 1;
>  
> -    if (orig_len + rest_len > IPV4_PACKET_MAX_SIZE) {
> +    if (rest_len <= 0 || orig_len + rest_len > IPV4_PACKET_MAX_SIZE) {
>          ipf_print_reass_packet(
>              "Unsupported big reassembled v4 packet; v4 hdr:", l3);

This verdict is not accurate when rest_len <= 0.  We should have a
separate one.

>          dp_packet_delete(pkt);
> @@ -484,7 +484,7 @@ ipf_reassemble_v6_frags(struct ipf_list *ipf_list)
>      int rest_len = frag_list[ipf_list->last_inuse_idx].end_data_byte -
>                     frag_list[1].start_data_byte + 1;
>  
> -    if (orig_len + rest_len > IPV6_PACKET_MAX_DATA) {
> +    if (rest_len <= 0 || orig_len + rest_len > IPV6_PACKET_MAX_DATA) {
>          ipf_print_reass_packet(
>               "Unsupported big reassembled v6 packet; v6 hdr:", l3);

Same here.

>          dp_packet_delete(pkt);
> @@ -668,9 +668,17 @@ ipf_v4_key_extract(struct dp_packet *pkt, ovs_be16 
> dl_type, uint16_t zone,
>      const struct ip_header *l3 = dp_packet_l3(pkt);
>      uint16_t ip_tot_len = ntohs(l3->ip_tot_len);
>      size_t ip_hdr_len = IP_IHL(l3->ip_ihl_ver) * 4;
> +    uint32_t start = ntohs(l3->ip_frag_off & htons(IP_FRAG_OFF_MASK)) * 8;
>  
> -    *start_data_byte = ntohs(l3->ip_frag_off & htons(IP_FRAG_OFF_MASK)) * 8;
> -    *end_data_byte = *start_data_byte + ip_tot_len - ip_hdr_len - 1;
> +    /* Fragments with no data or with data ending past 65535 bytes would
> +     * wrap the uint16_t fragment bounds, corrupting reassembly. */
> +    if (ip_tot_len <= ip_hdr_len
> +        || start + (ip_tot_len - ip_hdr_len) - 1 > UINT16_MAX) {
> +        return false;
> +    }
> +
> +    *start_data_byte = start;
> +    *end_data_byte = start + (ip_tot_len - ip_hdr_len) - 1;
>      *ff = ipf_is_first_v4_frag(pkt);
>      *lf = ipf_is_last_v4_frag(pkt);
>      memset(key, 0, sizeof *key);
> @@ -742,7 +750,7 @@ invalid_pkt:
>  
>  }
>  
> -static void
> +static bool
>  ipf_v6_key_extract(struct dp_packet *pkt, ovs_be16 dl_type, uint16_t zone,
>                     struct ipf_list_key *key, uint16_t *start_data_byte,
>                     uint16_t *end_data_byte, bool *ff, bool *lf)
> @@ -758,9 +766,18 @@ ipf_v6_key_extract(struct dp_packet *pkt, ovs_be16 
> dl_type, uint16_t zone,
>                          NULL);
>      ovs_assert(nw_frag && frag_hdr);
>      ovs_be16 ip6f_offlg = frag_hdr->ip6f_offlg;
> -    *start_data_byte = ntohs(ip6f_offlg & IP6F_OFF_MASK) +
> +    uint32_t start = ntohs(ip6f_offlg & IP6F_OFF_MASK) +
>          sizeof (struct ovs_16aligned_ip6_frag);
> -    *end_data_byte = *start_data_byte + dp_packet_l4_size(pkt) - 1;
> +    size_t l4_size = dp_packet_l4_size(pkt);
> +
> +    /* As in ipf_v4_key_extract(), reject fragments that would wrap the
> +     * uint16_t fragment bounds. */
> +    if (!l4_size || start + l4_size - 1 > UINT16_MAX) {
> +        return false;
> +    }
> +
> +    *start_data_byte = start;
> +    *end_data_byte = start + l4_size - 1;
>      *ff = ipf_is_first_v6_frag(ip6f_offlg);
>      *lf = ipf_is_last_v6_frag(ip6f_offlg);
>      memset(key, 0, sizeof *key);
> @@ -773,6 +790,7 @@ ipf_v6_key_extract(struct dp_packet *pkt, ovs_be16 
> dl_type, uint16_t zone,
>      key->nw_proto = 0;   /* Not used for key for V6. */
>      key->zone = zone;
>      key->recirc_id = pkt->md.recirc_id;
> +    return true;
>  }
>  
>  static bool
> @@ -892,11 +910,17 @@ ipf_handle_frag(struct ipf *ipf, struct dp_packet *pkt, 
> ovs_be16 dl_type,
>      bool v6 = dl_type == htons(ETH_TYPE_IPV6);
>  
>      if (v6 && ipf_get_v6_enabled(ipf)) {
> -        ipf_v6_key_extract(pkt, dl_type, zone, &key, &start_data_byte,
> -                           &end_data_byte, &ff, &lf);
> +        if (!ipf_v6_key_extract(pkt, dl_type, zone, &key, &start_data_byte,
> +                                &end_data_byte, &ff, &lf)) {
> +            pkt->md.ct_state = CS_INVALID;
> +            return false;
> +        }
>      } else if (!v6 && ipf_get_v4_enabled(ipf)) {
> -        ipf_v4_key_extract(pkt, dl_type, zone, &key, &start_data_byte,
> -                           &end_data_byte, &ff, &lf);
> +        if (!ipf_v4_key_extract(pkt, dl_type, zone, &key, &start_data_byte,
> +                                &end_data_byte, &ff, &lf)) {
> +            pkt->md.ct_state = CS_INVALID;
> +            return false;
> +        }
>      } else {
>          return false;
>      }
> diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
> index 644b7b7fb..7e231b967 100644
> --- a/tests/ofproto-dpif.at
> +++ b/tests/ofproto-dpif.at
> @@ -5671,6 +5671,51 @@ CHECK_COVERAGE([dpif_netdev_output_grow_queues], [1])
>  OVS_VSWITCHD_STOP
>  AT_CLEANUP
>  
> +AT_SETUP([ofproto-dpif - fragment handling - reject oversized fragment])
> +OVS_VSWITCHD_START
> +add_of_ports br0 1 90
> +
> +AT_DATA([flows.txt], [dnl
> +table=0 in_port=90,ip actions=ct(commit),output:1
> +])
> +AT_CHECK([ovs-ofctl -O OpenFlow11 replace-flows br0 flows.txt])
> +
> +dnl Last fragment at offset 65528 carrying 28 data bytes: its end offset
> +dnl (65555) exceeds the maximum packet size and, truncated to 16 bits,
> +dnl wraps to 19. It must not be admitted for reassembly.
> +dnl IPv4 Packet content:
> +dnl   Ethernet II, Src: 50:54:00:00:00:09, Dst: 50:54:00:00:00:0a
> +dnl       Type: IPv4 (0x0800)
> +dnl   Internet Protocol Version 4, Src: 10.1.1.1, Dst: 10.1.1.2
> +dnl       0100 .... = Version: 4
> +dnl       .... 0101 = Header Length: 20 bytes (5)
> +dnl       Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT)
> +dnl       Total Length: 48
> +dnl       Identification: 0x0001 (1)
> +dnl       001. .... = Flags: 0x1, More fragments
> +dnl           0... .... = Reserved bit: Not set
> +dnl           .0.. .... = Don't fragment: Not set
> +dnl           ..1. .... = More fragments: Set

The packet below doesn't have the "More fragments" bit set.

> +dnl       ...1 1111 1111 1111 = Fragment Offset: 65528
> +dnl       Time to Live: 64
> +dnl       Protocol: UDP (17)
> +dnl       Header Checksum: 0x44b9
> +dnl   Data (28 bytes)
> +eth="50 54 00 00 00 0a 50 54 00 00 00 09 08 00"
> +ip="45 00 00 30 00 01 1f ff 40 11 44 b9 0a 01 01 01 0a 01 01 02"
> +data="00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 
> 17 18 19 1a 1b"
> +packet="${eth} ${ip} ${data}"
> +AT_CHECK([ovs-appctl netdev-dummy/receive p90 "$packet"])
> +
> +AT_CHECK([ovs-appctl dpctl/ipf-get-status -m | grep -E 'num frag:|frags 
> accepted:'], [], [dnl

Maybe good to wrap this line.  It's a little too long.

> +        num frag: 0
> +        v4 frags accepted: 0
> +        v6 frags accepted: 0
> +])
> +
> +OVS_VSWITCHD_STOP
> +AT_CLEANUP
> +
>  AT_SETUP([ofproto-dpif - handling of malformed TCP packets])
>  OVS_VSWITCHD_START
>  add_of_ports br0 1 90

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to