Eli Britstein <[email protected]> writes:
> 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/dpctl.c | 4 ++
> lib/dpif-provider.h | 1 +
> lib/ipf.c | 77 +++++++++++++++++++++++++-------
> lib/ipf.h | 1 +
> tests/ofproto-dpif.at | 46 +++++++++++++++++++
> tests/system-userspace-macros.at | 8 ++++
> 6 files changed, 122 insertions(+), 15 deletions(-)
[...]
> @@ -661,16 +676,26 @@ invalid_pkt:
> }
>
> static bool
> -ipf_v4_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)
> +ipf_v4_key_extract(struct ipf *ipf, 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)
> {
> 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;
> +
> + /* 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) {
> + ipf_count(ipf, true, IPF_NFRAGS_TOO_LARGE);
^ should be false, I think.
> + return false;
> + }
>
> - *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;
> + *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);
[...]
> @@ -5670,6 +5670,52 @@ 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 000. .... = Flags: 0x0
> +dnl 0... .... = Reserved bit: Not set
> +dnl .0.. .... = Don't fragment: Not set
> +dnl ..0. .... = More fragments: Not 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
> + num frag: 0
> + v4 frags accepted: 0
> + v6 frags accepted: 0
> +])
May also be worth asserting on the v4 frags too large here as well.
That probably would have flagged the 'true' argument to ipf_count in the
patch.
> +
> +OVS_VSWITCHD_STOP
> +AT_CLEANUP
> +
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev