You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

CWE-825 / CWE-476: Invalid-iterator dereference in CNTK v2 CompositeFunction::DeserializeBlockComposite

Unchecked *std::find_if(... end()) on the root-function lookup crashes on any crafted native .model file.

Target

  • Project: Microsoft Cognitive Toolkit (CNTK)
  • Component: Source/CNTKv2LibraryDll/CompositeFunction.cpp โ€” native CNTK v2 model (checkpoint) deserializer
  • Version: current master (archived project; the vulnerable code is present in the released 2.7 line through HEAD)
  • Entry point: CNTK::Function::Load(filepath, DeviceDescriptor, ...) with format ModelFormat::CNTKv2
  • Class: CWE-825 (Expired Pointer / invalid iterator dereference), CWE-476 (NULL Pointer Dereference)

Root cause

CompositeFunction::DeserializeBlockComposite resolves the composite graph's root function by dereferencing the result of std::find_if without ever comparing it to end() (CompositeFunction.cpp, ~lines 203-206, current master):

FunctionPtr root = *std::find_if(allPrimitiveFunctions.begin(), allPrimitiveFunctions.end(),
    [&rootUid](const FunctionPtr& func) {
        return func->Uid() == rootUid;
    });
  • rootUid is taken from the model-controlled rootKey of the composite Dictionary.
  • allPrimitiveFunctions is a std::unordered_set<FunctionPtr> (FunctionPtr == std::shared_ptr<Function>) built from the model-controlled functionsKey vector in CompositeFunction::Deserialize (CompositeFunction.cpp ~lines 251-289).

If rootUid matches no deserialized function, std::find_if returns end(). Dereferencing that past-the-end iterator of the unordered_set reads through the container's null terminal hash node and copy-constructs a std::shared_ptr<Function> from it โ€” a read of near-null memory (offset 0x8).

This is trivially reachable:

  • an empty functions vector โ†’ allPrimitiveFunctions is empty โ†’ find_if immediately returns end(), or
  • any rootKey uid string that does not correspond to some function's Uid().

ValidateDictionary (CompositeFunction.cpp ~196/233) only checks required-key presence, the type tag, and the version. It performs no cross-reference that the declared root actually exists among the functions, so nothing guards the dereference.

Reachability (source trace)

Function::Load(filepath, CNTKv2)          Function.cpp:508-519
  -> operator>>(std::istream&, Dictionary&)
  -> Serializer::Read
  -> Function::Deserialize                Function.cpp:1093-1095
  -> CompositeFunction::Deserialize       CompositeFunction.cpp:229
  -> DeserializeBlockComposite            CompositeFunction.cpp:296 -> 190
  -> *std::find_if(...)  <-- crash        CompositeFunction.cpp:203

A malicious native CNTK v2 .model whose top-level composite Dictionary has type=CompositeFunction, an empty inputs vector, an empty functions vector, a uid, and a rootKey set to any string reaches the dereference of end() on the empty allPrimitiveFunctions set.

Proof of concept

A full CNTK build is infeasible in this environment (archived project requiring MKL / Boost / OpenCV / pinned protobuf; only ~7 GB free on /), matching the approach used for the three prior verified CNTK findings by this researcher. Instead, a faithful harness reproduces the exact vulnerable lines verbatim: the same std::find_if lambda over the same container type std::unordered_set<std::shared_ptr<Function>> (CNTK's FunctionPtr), with Function exposing the same Uid() surface. Only Function/FunctionPtr are reduced to a minimal stand-in; the crashing statement is copied character-for-character from CompositeFunction.cpp:203-206.

  • Harness: harness.cpp (in this repo)
  • Build: g++ 15.2 -fsanitize=address -O0 -std=c++14 harness.cpp -o harness
  • TRIGGER (empty set, no matching rootUid): AddressSanitizer SEGV.
  • NEGATIVE CONTROL (insert a Function whose Uid() == rootUid, so find_if returns a valid iterator): returns cleanly, exit 0, use_count=2 โ€” proving the crash is specifically the unchecked end() dereference and not an artifact of the harness.

Captured evidence (verbatim)

=== NEGATIVE CONTROL (matching root uid) ===
[control] set size=1
returned root use_count=2 ptr=0x7b7823be0020
control exit=0
=== TRIGGER (empty set / no matching root uid) ===
[trigger] set size=0 (empty) -> find_if returns end()
AddressSanitizer:DEADLYSIGNAL
==1100204==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000008 (pc 0x5604be983bdd bp 0x7fffe078c2f0 sp 0x7fffe078c2e0 T0)
==1100204==The signal is caused by a READ memory access.
==1100204==Hint: address points to the zero page.
    #0 ... std::__shared_ptr<Function, ...>::__shared_ptr(... const&) /usr/include/c++/15/bits/shared_ptr_base.h:1529
    #1 ... std::shared_ptr<Function>::shared_ptr(std::shared_ptr<Function> const&) /usr/include/c++/15/bits/shared_ptr.h:203
    #2 ... DeserializeBlockComposite_vuln harness.cpp:46   (== CompositeFunction.cpp:203 verbatim)
    #3 ... main harness.cpp:74
SUMMARY: AddressSanitizer: SEGV harness.cpp:46 in DeserializeBlockComposite_vuln
==1100204==ABORTING
Registers: rax=0x0000000000000008 rsi=0x0000000000000008  (read of past-the-end null hash-node value at offset 0x8)

The crash was re-reproduced during packaging (fresh build, g++ 15.2, ASan): SEGV on address 0x000000000008, READ access, same std::__shared_ptr copy-constructor frame reached from DeserializeBlockComposite_vuln; the control path returns exit 0 with use_count=2.

Impact

Denial of service (guaranteed crash) on loading an untrusted native CNTK v2 .model file via the public Function::Load API. The dereference reads attacker-influenceable near-null memory during a shared_ptr copy; the reference-count manipulation on a fabricated control block is a memory-safety violation with potential for worse-than-DoS behavior depending on heap layout.

Suggested fix

Compare the iterator against end() before dereferencing and raise a deserialization error if the declared root uid is not found:

auto it = std::find_if(allPrimitiveFunctions.begin(), allPrimitiveFunctions.end(),
    [&rootUid](const FunctionPtr& func) { return func->Uid() == rootUid; });
if (it == allPrimitiveFunctions.end())
    RuntimeError("DeserializeBlockComposite: root function uid '%S' not found among deserialized functions", rootUid.c_str());
FunctionPtr root = *it;

Deduplication

Distinct from the three prior CNTK findings by this researcher:

  1. DictionaryValue::Value<T>() null-pointer dereference (DictionaryValue type-tag handling),
  2. ONNX-import UnpackDouble out-of-bounds read (ONNX import path),
  3. NDShape::TotalSize allocation bomb (shape arithmetic).

This is an unchecked-iterator (find_if == end()) invalid dereference in the composite-function graph deserializer โ€” unrelated to DictionaryValue tagging, ONNX import, or shape arithmetic. No known CVE covers CompositeFunction::DeserializeBlockComposite.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support