This is an AI generated review of your patch.  A human has looked at the
results and deemed any concerns as plausible.

Changed files: lib/ipf.c (overlap predicate), tests/ofproto-dpif.at (new
test). Purpose: the previous duplicate/overlap test only caught a new
fragment whose start or end landed inside an existing fragment's range,
so a new fragment that strictly encloses an older one (start < old.start
and end > old.end) passed. The new closed-interval test `start_new <=
end_old && end_new >= start_old` is the standard interval-intersection
predicate and covers containment plus both partial-overlap cases.

Correctness of the predicate

>     for (int i = 0; i <= last_inuse_idx; i++) {
> -        if ((start_data_byte >= frag_list[i].start_data_byte &&
> -            start_data_byte <= frag_list[i].end_data_byte) ||
> -            (end_data_byte >= frag_list[i].start_data_byte &&
> -             end_data_byte <= frag_list[i].end_data_byte)) {
> +        if (start_data_byte <= frag_list[i].end_data_byte &&
> +            end_data_byte >= frag_list[i].start_data_byte) {

Verified against the invariants of the callers:

- Ranges are inclusive on both ends. ipf_v4_key_extract
  (lib/ipf.c:705-706) and ipf_v6_key_extract (lib/ipf.c:806-807) compute
  `end = start + len - 1`, so the closed-interval comparison is the
  right form; adjacent fragments (`start_new == end_old + 1`) are
  correctly *not* flagged, which matters because ipf_list_complete
  (lib/ipf.c:397-401) requires exactly that contiguity.
  
- No signedness/overflow exposure: parameters are `size_t`, stored
  fields are `uint16_t` and the producers already reject ranges that
  would wrap past UINT16_MAX (lib/ipf.c:697-703, 799-804), so the
  int-promoted uint16_t operands can never be negative when converted to
  the unsigned comparison type.
  
- Only caller is ipf_process_frag (lib/ipf.c:903-906); on overlap it
  bumps IPF_NFRAGS_OVERLAP and deletes the packet, which is
  unchanged. `ipf_is_beyond_last_frag` is still evaluated independently
  and the duped branch takes precedence in counting — no double-count
  and no counter regression.
  
- Tightening policy: rejecting any overlap matches RFC 5722 discard
  behavior and matches what conntrack would do downstream anyway (the
  stated rationale in the comment above ipf_process_frag). I grepped for
  tests expecting overlapping fragments to be accepted; only the
  exact-duplicate test (tests/ofproto-dpif.at:5713-5716) expects a
  nonzero overlap counter, and exact duplicates were already caught by
  the old predicate, so no existing expectation
  changes. system-traffic-derived suites all expect `frags overlapped:
  0`.
  

Test hunk

I checked the hand-written packet bytes rather than trusting the
wireshark-style comments:

- Both IP header checksums are correct. Using the sibling last-fragment
  test (tests/ofproto-dpif.at:5704, same addresses/TTL/proto/ID/tot_len,
  frag_off 0x0032, csum 0x62f3) as the base one's-complement sum,
  flipping the MF bit adds 0x2000 and offset 0x2032 adds 0x2000 (net
  zero), giving 0x42f3 for packet1; for packet2 frag_off 0x2000 with
  tot_len 0x04c4 gives 0x4005. A bad checksum would have made
  ipf_is_valid_v4_frag mark the packet CS_INVALID and the test would
  pass for the wrong reason, so this mattered to check.
- `printf '%0*d' 800 0` / `2400 0` produce 400 and 1200 payload bytes
  respectively, consistent with tot_len 420 and 1220
  (ipf_is_valid_v4_frag requires `ip_tot_len == l3_size`).
- packet1 is a middle fragment (MF set) at offset 400 with l3 size 420,
  so it clears the minimum-size gate; packet2 covers bytes 0..1199 and
  fully contains 400..799 — exactly the case the old code
  missed. Expected counters (`num frag: 1`, accepted 1, completed 0,
  overlapped 1) follow from that.

One documentation nit in the test comment:

> dnl The minimum fragment size is clamped to 400 bytes, so both fragments must
> dnl be at least that large to be admitted for reassembly.

Two inaccuracies here (lib/ipf.c:1479 and 675): values below 400 are
rejected by `ipf-set-min-frag` with an error, not clamped; and the size
gate applies only to non-last fragments (`!lf`) and is compared against
`dp_packet_l3_size()` (header included), not the payload. Harmless to
the assertions, but the wording invites a future edit that assumes last
fragments are also size-gated.

Also worth noting (pre-existing, not worsened): the overlap scan is O(n)
per fragment over an unsorted array, so admission is quadratic in
fragment count; the commit keeps that shape.

Verdict: PASS

```json
{
  "findings": [
    {
      "type": "Documentation",
      "title": "Test comment misdescribes the minimum fragment size rule",
      "description": "The new test's comment states the minimum fragment size 
is 'clamped to 400 bytes' and that 'both fragments must be at least that 
large'. In fact ipf_set_min_frag() rejects values below IPF_V4_FRAG_SIZE_LBOUND 
with an error rather than clamping, and the size gate in ipf_is_valid_v4_frag() 
applies only to non-last fragments (condition '!lf') and is compared against 
dp_packet_l3_size() (IP header included), not payload length.",
      "reasoning": "Read lib/ipf.c:1475-1494 (lower-bound rejection, no 
clamping) and lib/ipf.c:672-678 ('!lf && dp_packet_l3_size(pkt) < 
min_v4_frag_size_'). The assertions themselves are unaffected because packet1 
is a middle fragment with l3 size 420 >= 400, but the comment can mislead a 
future maintainer into thinking last fragments are size-gated or that an 
out-of-range value silently succeeds.",
      "severity": "low",
      "severity_explanation": "No functional impact; only risks incorrect 
assumptions when the test is modified or copied.",
      "locations": [
        {
          "file": "tests/ofproto-dpif.at",
          "function_or_symbol": "ofproto-dpif - fragment handling - reject 
overlapped fragment",
          "line_range": "5780-5782",
          "why_this_location_matters": "Comment asserts clamping and a size 
requirement on both fragments, which does not match the implementation."
        },
        {
          "file": "lib/ipf.c",
          "function_or_symbol": "ipf_set_min_frag",
          "line_range": "1475-1494",
          "why_this_location_matters": "Shows values below the bound return an 
error instead of being clamped."
        },
        {
          "file": "lib/ipf.c",
          "function_or_symbol": "ipf_is_valid_v4_frag",
          "line_range": "672-678",
          "why_this_location_matters": "Shows the gate is skipped for last 
fragments and measures L3 size, not payload."
        }
      ],
      "preexisting": false
    }
  ]
}
```

---
findings: 1  worst severity: low  tokens: 514973 in / 38910 out

Eli Britstein <[email protected]> writes:

> The previous overlap check only tested whether a new fragment's
> endpoints fell inside an existing range, missing the case where the
> new fragment completely contains an older one.  Use closed-interval
> overlap so conflicting fragments are rejected.
>
> Assisted-by: composer-2.5-fast, Cursor
> Fixes: 4ea96698f667 ("Userspace datapath: Add fragmentation handling.")
> Acked-by: Mike Pattrick <[email protected]>
> Signed-off-by: Eli Britstein <[email protected]>
> ---
>  lib/ipf.c             |  8 ++---
>  tests/ofproto-dpif.at | 73 +++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 77 insertions(+), 4 deletions(-)
>
> diff --git a/lib/ipf.c b/lib/ipf.c
> index 72ebd238a..2b016e60b 100644
> --- a/lib/ipf.c
> +++ b/lib/ipf.c
> @@ -843,16 +843,16 @@ ipf_list_key_lookup(struct ipf *ipf, const struct 
> ipf_list_key *key,
>      return NULL;
>  }
>  
> +/* Returns true if the new fragment overlaps any existing fragment.  Uses
> + * closed-interval overlap: start_a <= end_b && end_a >= start_b. */
>  static bool
>  ipf_is_frag_duped(const struct ipf_frag *frag_list, int last_inuse_idx,
>                    size_t start_data_byte, size_t end_data_byte)
>      /* OVS_REQUIRES(ipf_lock) */
>  {
>      for (int i = 0; i <= last_inuse_idx; i++) {
> -        if ((start_data_byte >= frag_list[i].start_data_byte &&
> -            start_data_byte <= frag_list[i].end_data_byte) ||
> -            (end_data_byte >= frag_list[i].start_data_byte &&
> -             end_data_byte <= frag_list[i].end_data_byte)) {
> +        if (start_data_byte <= frag_list[i].end_data_byte &&
> +            end_data_byte >= frag_list[i].start_data_byte) {
>              return true;
>          }
>      }
> diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
> index 0e84dc308..0ff4c50f8 100644
> --- a/tests/ofproto-dpif.at
> +++ b/tests/ofproto-dpif.at
> @@ -5769,6 +5769,79 @@ AT_CHECK([ovs-appctl dpctl/ipf-get-status -m \
>  OVS_VSWITCHD_STOP
>  AT_CLEANUP
>  
> +AT_SETUP([ofproto-dpif - fragment handling - reject overlapped 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 The minimum fragment size is clamped to 400 bytes, so both fragments must
> +dnl be at least that large to be admitted for reassembly.
> +AT_CHECK([ovs-appctl dpctl/ipf-set-min-frag v4 400], [], [dnl
> +setting minimum fragment size successful
> +])
> +
> +dnl First admit a fragment carrying bytes 400..799, then reject a new 
> fragment
> +dnl covering bytes 0..1199 that fully contains the previously admitted range.
> +dnl
> +dnl Packet 1 (admitted).  Middle fragment carrying bytes 400..799 (MF set).
> +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: 420
> +dnl       Identification: 0x0020 (32)
> +dnl       001. .... = Flags: 0x1 (More fragments)
> +dnl       ...0 0000 0011 0010 = Fragment Offset: 400
> +dnl       Time to Live: 64
> +dnl       Protocol: UDP (17)
> +dnl       Header Checksum: 0x42f3
> +dnl   Data (400 bytes)
> +eth="50 54 00 00 00 0a 50 54 00 00 00 09 08 00"
> +ip1="45 00 01 a4 00 20 20 32 40 11 42 f3"
> +addrs="0a 01 01 01 0a 01 01 02"
> +data1=$(printf '%0*d' 800 0)
> +packet1="${eth}${ip1}${addrs}${data1}"
> +
> +dnl Packet 2 (rejected as overlap).  First fragment carrying bytes 0..1199
> +dnl (MF set), fully containing packet 1's byte range.
> +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: 1220
> +dnl       Identification: 0x0020 (32)
> +dnl       001. .... = Flags: 0x1 (More fragments)
> +dnl       ...0 0000 0000 0000 = Fragment Offset: 0
> +dnl       Time to Live: 64
> +dnl       Protocol: UDP (17)
> +dnl       Header Checksum: 0x4005
> +dnl   Data (1200 bytes)
> +ip2="45 00 04 c4 00 20 20 00 40 11 40 05"
> +data2=$(printf '%0*d' 2400 0)
> +packet2="${eth}${ip2}${addrs}${data2}"
> +
> +AT_CHECK([ovs-appctl netdev-dummy/receive p90 "$packet1"])
> +AT_CHECK([ovs-appctl netdev-dummy/receive p90 "$packet2"])
> +
> +AT_CHECK([ovs-appctl dpctl/ipf-get-status -m \
> +| grep -E 'num frag:|v4 frags accepted:|v4 frags completed:|v4 frags 
> overlapped:'], [], [dnl
> +        num frag: 1
> +        v4 frags accepted: 1
> +        v4 frags completed: 0
> +        v4 frags overlapped: 1
> +])
> +
> +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