Skip to main content

OpenMcdf CVE-2026-45785

MEDIUM
Loop with Unreachable Exit Condition (Infinite Loop) (CWE-835)
2026-05-19 https://github.com/openmcdf/openmcdf GHSA-5qwm-7pvp-w988
6.2
CVSS 3.1 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
6.2 MEDIUM
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
Attack Vector
Local
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

Lifecycle Timeline

2
Source Code Evidence Fetched
May 19, 2026 - 20:18 vuln.today
Analysis Generated
May 19, 2026 - 20:18 vuln.today

DescriptionGitHub 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 direct OpenStorage(name) for a name not present in the directory
  • repro_enumerate.cfb (7,936 bytes) - hangs on OpenStream(entry.Name) called for an entry returned by EnumerateEntries() (the common consumer pattern)

Repro 1 - OpenStorage(name)

csharp
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

csharp
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.)

repros.zip

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.

Share

CVE-2026-45785 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy