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: Unchecked std::find_if iterator dereference in CNTK v2 PrimitiveFunction::Deserialize block argument-map resolution (OOB read / near-null shared_ptr copy) on model load

Target

  • Project: Microsoft Cognitive Toolkit (CNTK) v2 β€” CNTKv2LibraryDll
  • Repo: https://github.com/microsoft/CNTK
  • Version/commit: current master (10a8ffc); CNTK v2 model deserializer
  • Vulnerable file: Source/CNTKv2LibraryDll/PrimitiveFunction.cpp (PrimitiveFunction::Deserialize, op == PrimitiveOpType::Block branch, lines 1399–1416)
  • Entry point: public CNTK::Function::Load(const std::wstring& filepath, DeviceDescriptor, ...) with CNTKv2 model format
  • Attack surface: untrusted CNTK-v2 model file loaded by a victim

Vulnerability

In the Block-op branch of PrimitiveFunction::Deserialize, the lambda findCompositeArgumentByUid resolves each block argument-map key uid against the inner composite's Arguments() by dereferencing the result of std::find_if without ever comparing the returned iterator against end():

// Source/CNTKv2LibraryDll/PrimitiveFunction.cpp:1399-1404
auto compositeArguments = composite->Arguments();
auto findCompositeArgumentByUid = [&compositeArguments](const std::wstring& uid) {
    return *std::find_if(compositeArguments.begin(), compositeArguments.end(),
        [&uid](const Variable& argument) {
            return (argument.Uid() == uid);
        });
};
...
// later, over fully model-controlled keys:
for (size_t i = 0; i < blockArgumentsMapKeys.size(); ++i)
    argumentsMap.push_back({ findCompositeArgumentByUid(blockArgumentsMapKeys[i]),   // <-- OOB deref
                             uidToVariableMap.at(blockArgumentsMapValues[i]) });
  • blockArgumentsMapKeys is fully model-controlled β€” it is deserialized from the blockFunctionCompositeArgumentsMapKeysKey vector of the BlockFunction dictionary.
  • compositeArguments is composite->Arguments() of the deserialized inner composite.
  • If a key uid matches no argument β€” in particular when the inner composite has zero arguments β€” std::find_if returns end(), and *end() copy-constructs a Variable out of past-the-end / null memory. The copy reads the null Variable's std::shared_ptr<VariableFields> m_dataFields control block:
// Source/CNTKv2LibraryDll/Variable.cpp:45-47
const std::wstring& Variable::Uid() const { return m_dataFields->m_uid; }
// Source/CNTKv2LibraryDll/API/CNTKLibrary.h:2117
VariableFieldsPtr m_dataFields;   // == std::shared_ptr<VariableFields>

ValidateDictionary only checks required-key presence, type-tag, and version; it never cross-references the block argument-map keys against the composite's actual arguments, so nothing guards the dereference.

CWE-825 (dereference of a past-the-end iterator) / CWE-476 (copy of a default-constructed null shared_ptr).

Reachability

Function::Load(path, CNTKv2)
  -> operator>>(istream&, Dictionary&) -> Serializer::Read
  -> Function::Deserialize -> CompositeFunction::Deserialize
      for each function dict:
        PrimitiveFunction::Deserialize(...)             // op == Block
          -> composite = DeserializeBlockComposite(...)  // inner composite w/ 0 args
          -> compositeArguments = composite->Arguments() // EMPTY
          -> findCompositeArgumentByUid(blockArgumentsMapKeys[0])
             -> *std::find_if(begin, end, pred)          // pred never matches -> end()
             -> copy-construct Variable from *end()       // <-- OOB read + shared_ptr copy -> SIGSEGV

PoC / Verification method

A full CNTK build is infeasible in this environment (archived project; requires MKL / Boost / OpenCV / pinned protobuf toolchain) β€” matching the approach used for the five prior verified CNTK findings. Verification uses a faithful compiled harness that reproduces the crashing statement verbatim:

  • harness.cpp copies PrimitiveFunction.cpp:1401-1403 character-for-character β€” the same *std::find_if(compositeArguments.begin(), compositeArguments.end(), [uid]{ argument.Uid()==uid }) over the exact container type std::vector<Variable>.
  • Variable is reduced to its crash-relevant layout only: a single std::shared_ptr<VariableFields> m_dataFields, with Uid() copied verbatim (return m_dataFields->m_uid;).
  • TRIGGER: empty compositeArguments with a key matching nothing (models a block whose inner composite has no arguments).
  • NEGATIVE CONTROL: compositeArguments contains a Variable whose Uid() equals the key.

Built three ways: production g++ -O2 -g -std=c++14 (matches CNTK Release), ASan g++ -fsanitize=address -O0, and run under gdb. The trigger crashes reliably (SIGSEGV, read of address 0x0); the control exits 0 in every build, proving the crash is specifically the unchecked end() dereference and not a harness artifact.

Captured evidence (verbatim)

PRODUCTION build (g++ -O2 -g -std=c++14 harness.cpp -o harness_prod)

[control] compositeArguments.size()=1, key present
control ok: resolved argument uid rank=38
control exit=0
[trigger] compositeArguments.size()=0 (empty) -> find_if returns end()
trigger exit=139                                (139 = 128 + SIGSEGV)

gdb backtrace (prod)

Program received signal SIGSEGV, Segmentation fault.
#0  operator() (uid=L"x_placeholder_uid_that_matches_nothing", ...) at harness.cpp:93   (== PrimitiveFunction.cpp:1401-1403, *find_if)
#1  ResolveBlockArgument (compositeArguments=std::vector of length 0, capacity 0, ...) at harness.cpp:96
#2  main (...) at harness.cpp:122
$1 = (void *) 0x0    (si_addr β€” read of null address)

AddressSanitizer (g++ -fsanitize=address -O0)

[trigger] compositeArguments.size()=0 (empty) -> find_if returns end()
AddressSanitizer:DEADLYSIGNAL
==280525==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x55a134584be3 ...)
==280525==The signal is caused by a READ memory access.
==280525==Hint: address points to the zero page.
    #0 std::__shared_ptr<VariableFields, (__gnu_cxx::_Lock_policy)2>::__shared_ptr(... const&)  shared_ptr_base.h:1529
    #1 std::shared_ptr<VariableFields>::shared_ptr(... const&)                                  shared_ptr.h:203
    #2 Variable::Variable(Variable const&)                                                      harness.cpp:67
    #3 operator()                                                                               harness.cpp:93   (== PrimitiveFunction.cpp:1401-1403)
    #4 ResolveBlockArgument                                                                     harness.cpp:96
    #5 main                                                                                     harness.cpp:122
SUMMARY: AddressSanitizer: SEGV harness.cpp:67 in Variable::Variable(Variable const&)
==280525==ABORTING
(ASan negative control: exit 0)

Impact

Loading an untrusted CNTK v2 model file triggers an out-of-bounds / near-null pointer dereference during deserialization, before any inference runs. Minimum impact is a reliable loader-side denial of service (SIGSEGV). Because the dereferenced address is *end() of a heap std::vector<Variable> β€” memory immediately past a controlled allocation β€” under a suitable heap layout the read target and the subsequently-copied shared_ptr control block are influenced by adjacent heap contents, raising the ceiling above pure DoS to an attacker-influenced invalid read on model load.

Suggested fix

Compare the std::find_if result against compositeArguments.end() before dereferencing, and raise a deserialization error (as other CNTK deserializer paths do) when a block argument-map key uid resolves to no composite argument:

auto it = std::find_if(compositeArguments.begin(), compositeArguments.end(),
    [&uid](const Variable& argument) { return (argument.Uid() == uid); });
if (it == compositeArguments.end())
    RuntimeError("Deserialize: block argument-map key '%S' does not match any composite argument.", uid.c_str());
return *it;

Additionally, ValidateDictionary for BlockFunction should cross-reference the argument-map keys against the inner composite's arguments.

Dedup note

  • No CVE is known for this specific unchecked-find_if-dereference path in CNTK's PrimitiveFunction::Deserialize block argument-map resolution.
  • Distinct from the reporter's prior verified CNTK findings (different functions / root causes): cntk-ndshape-bomb, cntk-dictionaryvalue-nullderef, cntk-compositefunction-invalid-iterator, cntk-inferoutputs-arity-oob, cntk-onnximport-unpackdouble-oob.
  • CNTK is archived/in maintenance mode but the repository is public and the vulnerable code is in current master.

Files

  • harness.cpp β€” faithful minimal reproducer (crashing statement copied verbatim from PrimitiveFunction.cpp:1401-1403)
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