OpenMcdf CVE-2026-45785
MEDIUMSeverity by source
AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Primary rating from GitHub Advisory · only source for this CVE.
CVSS VectorGitHub Advisory
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Lifecycle Timeline
2DescriptionGitHub Advisory
Summary
The BST name-lookup loop in DirectoryTree.TryGetDirectoryEntry (OpenMcdf/DirectoryTree.cs:35-46) walks directory entries by repeatedly calling directories.TryGetSibling(child, siblingType, validateColor). A crafted CFB file with cyclic Left/Right sibling links among directory entries - constructed so the per-step BST-order check in TryGetSibling (DirectoryEntries.cs:84-85) is satisfied at every step - drives this while (child is not null) loop forever. There is no cycle detection in TryGetDirectoryEntry.
Details
The recent Brent's-algorithm commit (24f445a) protects DirectoryTreeEnumerator and works correctly for both attached repros - pure EnumerateEntries() throws FileFormatException: Directory tree contains a loop cleanly. The unprotected code path is the lookup-by-name loop, which is reached from multiple public APIs:
RootStorage.OpenStorage(name)/TryOpenStorage(name)RootStorage.OpenStream(name)/TryOpenStream(name)
The second one matters most: typical consumers iterate EnumerateEntries() and call OpenStream(entry.Name) per Stream entry. With Brent's algorithm catching the enumeration cycle but not the per-entry lookup, callers can still hang as soon as they touch a streamed entry.
PoC
Two minimal repros attached, demonstrating the same lookup-loop bug reached via two different public APIs:
repro_lookup.cfb(5,632 bytes) - hangs on directOpenStorage(name)for a name not present in the directoryrepro_enumerate.cfb(7,936 bytes) - hangs onOpenStream(entry.Name)called for an entry returned byEnumerateEntries()(the common consumer pattern)
Repro 1 - OpenStorage(name)
using OpenMcdf;
using var fs = File.OpenRead("repro_lookup.cfb");
using var root = RootStorage.Open(fs);
root.TryOpenStorage("__substg1.0_3001001F", out _);
// process spins at 100% CPU; Ctrl+C required.Repro 2 - OpenStream from inside an enumeration loop
using OpenMcdf;
using var fs = File.OpenRead("repro_enumerate.cfb");
using var root = RootStorage.Open(fs);
foreach (var entry in root.EnumerateEntries()) // safe: Brent's catches enumeration cycles
{
if (entry.Type == EntryType.Stream)
_ = root.OpenStream(entry.Name); // hangs: lookup path has no cycle detection
}Both processes will not terminate.
(Note: pure foreach (var entry in root.EnumerateEntries()) { } with no per-entry lookup is safe - Brent's algorithm in DirectoryTreeEnumerator catches the enumeration cycle and throws FileFormatException: Directory tree contains a loop. The hang only manifests once a name lookup is performed.)
Impact
A denial of service affecting any application that opens untrusted CFB files with OpenMcdf. A small crafted input with a cyclic directory tree reaches the unprotected BST name-lookup in DirectoryTree.TryGetDirectoryEntry, hit by any caller of OpenStorage / TryOpenStorage / OpenStream / TryOpenStream - including the very common pattern of iterating EnumerateEntries() and calling OpenStream(entry.Name) per Stream entry. The cycles bypass the per-step BST-order check in TryGetSibling, so no exception is thrown and try/catch cannot protect callers. The affected thread is unrecoverable without killing the process. Downstream CFB consumers (e.g. .msg-file parsers) inherit transitively.
AnalysisAI
Denial of service in OpenMcdf versions up to and including 3.1.3 allows an attacker to permanently hang any thread that processes a crafted Compound File Binary (CFB) file by exploiting an unguarded infinite loop in the BST name-lookup path of DirectoryTree.TryGetDirectoryEntry. The flaw is distinct from - and unaddressed by - the Brent's-algorithm cycle detection added to DirectoryTreeEnumerator in commit 24f445a: while EnumerateEntries() now safely throws a FileFormatException on cyclic input, any subsequent call to OpenStorage(), TryOpenStorage(), OpenStream(), or TryOpenStream() enters the unprotected while-loop and spins at 100% CPU indefinitely. Publicly available proof-of-concept CFB files (5,632 and 7,936 bytes) demonstrate the hang via two distinct API paths; no public exploit identified at time of analysis that escalates beyond DoS, and the vulnerability is not listed in the CISA KEV catalog.
Technical ContextAI
OpenMcdf (pkg:nuget/openmcdf) is a .NET library for reading and writing Microsoft Compound File Binary (CFB) format files - the container format underlying legacy Office documents, .msg email files, and numerous other Windows data formats. The CFB directory tree is internally organized as a Red-Black BST (Binary Search Tree) where each directory entry carries Left and Right sibling sector indices. The name-lookup routine DirectoryTree.TryGetDirectoryEntry (DirectoryTree.cs:35-46) traverses this tree using a while(child is not null) loop, advancing via DirectoryEntries.TryGetSibling (DirectoryEntries.cs:84-85), which validates BST ordering at each step. CWE-835 (Loop with Unreachable Exit Condition) describes the root cause precisely: a maliciously crafted CFB file can encode Left/Right sibling links that form a cycle while still satisfying the per-step BST-order comparison, meaning TryGetSibling never signals an anomaly and the exit condition child is not null is never reached. The partial mitigation - Brent's cycle-detection algorithm in DirectoryTreeEnumerator - only covers the enumeration code path; the lookup code path has no equivalent guard. Because the loop does not throw an exception, callers cannot protect themselves with try/catch.
RemediationAI
The primary remediation is upgrading the OpenMcdf NuGet package to version 3.1.4, which is the vendor-confirmed fixed release per GHSA-5qwm-7pvp-w988 (https://github.com/openmcdf/openmcdf/security/advisories/GHSA-5qwm-7pvp-w988). Update the package reference in your project file to <PackageReference Include="OpenMcdf" Version="3.1.4" /> and rebuild. If an immediate upgrade is not feasible, the most actionable compensating control is to validate or reject CFB files from untrusted sources before passing them to OpenMcdf - for example, by enforcing a file-size ceiling or running CFB parsing in a dedicated subprocess with a timeout and kill signal, so a hang does not block the parent process. Note that wrapping OpenStorage/OpenStream calls in try/catch does not protect against this vulnerability because the loop never throws. Restricting which file types and sources are accepted upstream (e.g., rejecting .msg or .cfb attachments from external senders in an email gateway) is a viable network-boundary control but carries the trade-off of blocking legitimate content.
Same technique Denial Of Service
View allShare
External POC / Exploit Code
Leaving vuln.today
GHSA-5qwm-7pvp-w988