oss-sec mailing list archives
Re: OSEC-2026-01 in the OCaml runtime: Buffer Over-Read in OCaml Marshal Deserialization
From: Demi Marie Obenour <demiobenour () gmail com>
Date: Tue, 3 Mar 2026 01:03:28 -0500
On 3/2/26 04:53, Florian Weimer wrote:
* Demi Marie Obenour:On 2/27/26 14:39, Florian Weimer wrote:* Alan Coopersmith:https://sympa.inria.fr/sympa/arc/ocsf-ocaml-security-announcements/2026-02/msg00000.html announces:From: Hannes Mehnert <hannes () mehnert org> To: ocsf-ocaml-security-announcements () inria fr Subject: [ocsf-ocaml-security-announcements] OSEC-2026-01 in the OCaml runtime: Buffer Over-Read in OCaml Marshal Deserialization Date: Tue, 17 Feb 2026 15:16:54 +0100 Dear everyone, it is my pleasure to announce the first security announcement of this year, and the first on this mailing list. It should any moment now also appear at https://osv.dev/list?q=OSEC-2026-01 Human link: https://github.com/ocaml/security-advisories/tree/main/advisories/2026/OSEC-2026-01.mdSurprised to read this. I think this comment from 2018 is still appropriate: | Marshal should not used in contexts where an attacker can control the | data. I don't believe it is, at least in any project I'm aware of, and | if it were, it's unlikely that those project perform enough check on | the result of Marshal to make the use safe anyway. <https://github.com/ocaml/ocaml/issues/7765#issuecomment-473076288> The demarshaller does not have access to type information from the program, so it has the ability to construct an arbitrary object graph.That is indeed true. However, unlike in many other languages, this does not directly allow arbitrary code execution.Not really. This code type x = A of int | B of int | C of int | D of int | E of int let f x fA fB fC fD fE = match x with | A a -> fA a | B b -> fB b | C c -> fC c | D d -> fD d | E e -> fE e gets compiled to: 0000000000000000 <camlBlah.f_5>: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: 49 89 c0 mov %rax,%r8 7: 49 89 d1 mov %rdx,%r9 a: 4d 3b 3e cmp (%r14),%r15 d: 76 51 jbe 60 <camlBlah.f_5+0x60> f: 49 0f b6 40 f8 movzbq -0x8(%r8),%rax 14: 48 8d 15 00 00 00 00 lea 0x0(%rip),%rdx # 1b <camlBlah.f_5+0x1b> 17: R_X86_64_PC32 .rodata-0x4 1b: 48 63 04 82 movslq (%rdx,%rax,4),%rax 1f: 48 01 c2 add %rax,%rdx 22: ff e2 jmp *%rdx 24: 49 8b 00 mov (%r8),%rax 27: 48 8b 3b mov (%rbx),%rdi 2a: 5d pop %rbp 2b: ff e7 jmp *%rdi 2d: 0f 1f 00 nopl (%rax) 30: 49 8b 00 mov (%r8),%rax 33: 48 8b 37 mov (%rdi),%rsi 36: 48 89 fb mov %rdi,%rbx 39: 5d pop %rbp 3a: ff e6 jmp *%rsi 3c: 49 8b 00 mov (%r8),%rax 3f: 48 8b 3e mov (%rsi),%rdi 42: 48 89 f3 mov %rsi,%rbx 45: 5d pop %rbp 46: ff e7 jmp *%rdi 48: 49 8b 00 mov (%r8),%rax 4b: 49 8b 39 mov (%r9),%rdi 4e: 4c 89 cb mov %r9,%rbx 51: 5d pop %rbp 52: ff e7 jmp *%rdi 54: 49 8b 00 mov (%r8),%rax 57: 48 8b 39 mov (%rcx),%rdi 5a: 48 89 cb mov %rcx,%rbx 5d: 5d pop %rbp 5e: ff e7 jmp *%rdi 60: e8 00 00 00 00 call 65 <camlBlah.f_5+0x65> 61: R_X86_64_PLT32 caml_call_gc-0x4 65: eb a8 jmp f <camlBlah.f_5+0xf> 67: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1) 6e: 00 00 Add offset 0x1b, there's the tag load, and this tag is used to index a jump table without a bounds check. Admittedly, This does not give full control over program execution directly. One would have to search for a suitable gadget. There are likely better ways to exploit unsafe demarshalling, this is just the first approach I could think of. Thanks, Florian
One can use C code or the Obj module to validate the value before using it. In this case, one could use Obj.is_int to check that this is a boxed value, Obj.tag to check that its tag is in the correct range, Obj.len to check that the length is correct, and Obj.field and Obj.is_int to validate the constructor payloads. Only after validation is complete would one call Obj.obj to cast the validated value to the intended type. It's also possible to unmarshal to an opaque type and provide safe functions for traversing the object graph. This is unlike Java, Ruby, or Python, where arbitrary code can be executed during the unmarshalling process itself. I do agree that whether existing code does this is questionable, but it is definitely *possible* to do correctly. -- Sincerely, Demi Marie Obenour (she/her/hers)
Attachment:
OpenPGP_0xB288B55FFF9C22C1.asc
Description: OpenPGP public key
Attachment:
OpenPGP_signature.asc
Description: OpenPGP digital signature
Current thread:
- OSEC-2026-01 in the OCaml runtime: Buffer Over-Read in OCaml Marshal Deserialization Alan Coopersmith (Feb 27)
- Re: OSEC-2026-01 in the OCaml runtime: Buffer Over-Read in OCaml Marshal Deserialization Florian Weimer (Feb 27)
- Re: OSEC-2026-01 in the OCaml runtime: Buffer Over-Read in OCaml Marshal Deserialization Demi Marie Obenour (Mar 01)
- Re: OSEC-2026-01 in the OCaml runtime: Buffer Over-Read in OCaml Marshal Deserialization Florian Weimer (Mar 02)
- Re: OSEC-2026-01 in the OCaml runtime: Buffer Over-Read in OCaml Marshal Deserialization Demi Marie Obenour (Mar 03)
- Re: OSEC-2026-01 in the OCaml runtime: Buffer Over-Read in OCaml Marshal Deserialization Demi Marie Obenour (Mar 01)
- Re: OSEC-2026-01 in the OCaml runtime: Buffer Over-Read in OCaml Marshal Deserialization Florian Weimer (Feb 27)
