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 formatModelFormat::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;
});
rootUidis taken from the model-controlledrootKeyof the compositeDictionary.allPrimitiveFunctionsis astd::unordered_set<FunctionPtr>(FunctionPtr == std::shared_ptr<Function>) built from the model-controlledfunctionsKeyvector inCompositeFunction::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
functionsvector โallPrimitiveFunctionsis empty โfind_ifimmediately returnsend(), or - any
rootKeyuid string that does not correspond to some function'sUid().
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
FunctionwhoseUid() == rootUid, sofind_ifreturns a valid iterator): returns cleanly, exit 0,use_count=2โ proving the crash is specifically the uncheckedend()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:
DictionaryValue::Value<T>()null-pointer dereference (DictionaryValue type-tag handling),- ONNX-import
UnpackDoubleout-of-bounds read (ONNX import path), NDShape::TotalSizeallocation 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.