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-476 NULL Pointer Dereference in CNTK v2 Dictionary/DictionaryValue Checkpoint Deserializer

Status: Verified (independent adversarial re-verification complete, ASan repro captured).

Target

  • Project: Microsoft Cognitive Toolkit (CNTK) v2
  • Repository: https://github.com/microsoft/CNTK
  • Affected files:
    • Source/CNTKv2LibraryDll/Serialization.cpp
    • Source/CNTKv2LibraryDll/Variable.cpp
    • Source/CNTKv2LibraryDll/Learner.cpp
    • Source/CNTKv2LibraryDll/proto/CNTK.proto
    • Source/CNTKv2LibraryDll/API/CNTKLibrary.h
  • Commit basis: current master at time of research (CNTK is an archived/deprecated Microsoft project; no version tags have shipped changes to this code path).

Dedup note: This is a distinct bug from two other findings already filed by this researcher against CNTK:

  1. EnigmaConsultant/cntk-onnximport-unpackdouble-oob β€” an out-of-bounds read in the ONNX-import UnpackDouble path.
  2. EnigmaConsultant/huntr-poc-cntk-ndshape-bomb β€” an NDShape::TotalSize integer/allocation bomb.

This third bug is a NULL-pointer dereference in the native CNTK v2 checkpoint deserializer (Dictionary/DictionaryValue), unrelated to ONNX import and unrelated to shape/allocation arithmetic.

Root Cause

A three-step gap between proto validation, storage, and consumption:

1. Serializer::CreateFromProto(const proto::NDArrayView& src) (Serialization.cpp:528-534) returns nullptr when src.data_type() / src.storage_format() fail the generated proto::NDArrayView::DataType_IsValid() / StorageFormat_IsValid() check.

NDArrayView.DataType has a gap in its valid values: only 0(Unknown), 1(Float), 2(Double), 4(Float16), 5(Int8), 6(Int16) are valid; 3 is undefined. Confirmed directly from the generated protobuf code:

inline bool NDArrayView_DataType_IsValid(int value) {
  return 0 <= value && value <= 6 && ((119u >> value) & 1) != 0;
}
// 119 = 0b1110111 -> bit 3 is 0 -> data_type == 3 is "invalid" but still parses off the wire

2. Serializer::Copy(const proto::DictionaryValue&, DictionaryValue&) (Serialization.cpp:732-734), case NDArrayView:

dst.m_data.m_ptr = CreateFromProto(src.nd_array_view_value());

stores that nullptr with no null check, while dst.m_valueType has already been set to DictionaryValue::Type::NDArrayView a few lines above (Serialization.cpp:697). The type tag and the payload pointer become inconsistent: tag says "valid NDArrayView", pointer is null.

3. DictionaryValue::Value<T>() for pointer-backed types (API/CNTKLibrary.h:1640-1662, dereference at lines 1647 and 1660):

template <typename T>
const T& DictionaryValue::Value() const
{
    VerifyType<T>();
    ...
    return *(reinterpret_cast<T*>(m_data.m_ptr));   // <-- unconditional deref
}

VerifyType<T>() (CNTKLibrary.h:1714-1718) only checks GetValueType<T>() != m_valueType (the tag) and throws a RuntimeError on a type mismatch β€” it never checks m_data.m_ptr for null. The function then unconditionally dereferences the pointer.

Reachability (real, unmodified call sites)

  • Variable::Deserialize(), Source/CNTKv2LibraryDll/Variable.cpp:547:
    auto& value = dict[valueKey].Value<NDArrayView>();
    
    followed immediately by value.DeepClone(device, value.IsReadOnly()) (Variable.cpp:551) β€” invoked for every Parameter/Constant variable whenever any native CNTK v2 .model/checkpoint file is loaded, via Dictionary::Load -> Function::Load -> ... -> Variable::Deserialize.
  • A second reachable site exists at Learner.cpp:496 for optimizer-state checkpoints.

A crafted checkpoint whose stored tensor value has data_type=3 (or any other undefined enum int) on the wire causes steps 1-3 above to hand back a dangling/null NDArrayView reference, and the very next member access on it (IsReadOnly()) dereferences address 0.

PoC Methodology

A full CNTK build is infeasible in this environment (archived/deprecated project requiring MKL/Boost/OpenCV/a pinned old protobuf toolchain; local disk headroom was ~2GB). Verification instead used the real generated protobuf code plus the exact vulnerable CNTK source lines, copied verbatim, rather than any approximation:

  1. Copied CNTK.proto verbatim from Source/CNTKv2LibraryDll/proto/CNTK.proto (current master) and compiled it with a real local protoc v31.1, producing the actual generated CNTK.pb.h/.cc that CNTK's own build would produce from this identical schema.
  2. Wrote a standalone harness (harness.cpp) that:
    • parses a real raw-wire-format serialized proto::Dictionary payload with the genuine generated parser (dict.ParseFromArray, matching Serializer::Read/ParseMessage's non-2GB-prefixed path, Serialization.cpp:869-877);
    • reproduces, byte-for-byte, Serializer::CreateFromProto(proto::NDArrayView)'s IsValid-check-and-return-nullptr (Serialization.cpp:530-534);
    • reproduces DictionaryValue::Value<T>()'s unconditional *(reinterpret_cast<T*>(m_data.m_ptr)) (CNTKLibrary.h:1647/1660), confirmed against the real VerifyType<T>() (CNTKLibrary.h:1714-1718), which has no null check;
    • reproduces the exact real call sequence from Variable.cpp:547/551 (dict[valueKey].Value<NDArrayView>() then .IsReadOnly()).
  3. Hand-crafted two raw protobuf wire payloads with an independent Python byte-encoder (make_payloads.py, not using the CNTK/protobuf libraries to build them β€” only raw varint/tag encoding per the CNTK.proto wire layout) representing Dictionary{"value": DictionaryValue{type=NDArrayView, nd_array_view_value=NDArrayView{...}}}:
    • payload_good.bin β€” data_type=1/Float, valid.
    • payload_bad.bin β€” data_type=3, an undefined enum gap value.
  4. Built the harness with AddressSanitizer against the real generated CNTK.pb.cc and ran both payloads.

Result

  • payload_good.bin (control) parses and completes normally: exit 0, IsReadOnly=0, no crash. Confirms a clean negative control β€” the harness does not spuriously crash on well-formed input.
  • payload_bad.bin (28 bytes) reproducibly crashes with a genuine SIGSEGV/NULL dereference caught by ASan, at the exact reinterpret_cast-dereferenced-then-member-accessed pattern that Variable.cpp:547/551 performs in the real CNTK binary.

Captured evidence (verbatim)

=== GOOD (control), well-formed data_type=1/Float ===
Parsed native CNTK Dictionary with 1 entries (version=1)
nd_array_view_value.data_type on the wire = 1 (IsValid=1)
CreateFromProto result pointer = 0x7b43bade0070
Calling dict["value"].Value<NDArrayView>() (Variable.cpp:547) ...
  (unexpectedly reached past the deref: IsReadOnly=0)
Loaded parameter value, IsReadOnly=0 -- no crash.
exit=0

=== BAD (crash trigger), data_type=3 (undefined enum gap value) ===
Parsed native CNTK Dictionary with 1 entries (version=1)
nd_array_view_value.data_type on the wire = 3 (IsValid=0)
CreateFromProto result pointer = (nil)
Calling dict["value"].Value<NDArrayView>() (Variable.cpp:547) ...
AddressSanitizer:DEADLYSIGNAL
=================================================================
==354301==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x56310c5187b4 bp 0x7ffe5b3ec3f0 sp 0x7ffe5b3ec3e0 T0)
==354301==The signal is caused by a READ memory access.
==354301==Hint: address points to the zero page.
    #0 0x56310c5187b4 in NDArrayView::IsReadOnly() const harness.cpp:79
    #1 0x56310c51689f in DeserializeParameterValue harness.cpp:180
    #2 0x56310c516f6e in main harness.cpp:243
    #3 0x7f5f9f031f76  (/usr/lib/x86_64-linux-gnu/libc.so.6+0x29f76)
    #4 0x7f5f9f032026 in __libc_start_main (/usr/lib/x86_64-linux-gnu/libc.so.6+0x2a026)
    #5 0x56310c5165a0 in _start
SUMMARY: AddressSanitizer: SEGV harness.cpp:79 in NDArrayView::IsReadOnly() const
==354301==ABORTING

Impact

Any application that loads a CNTK v2 native checkpoint (.model file) from an untrusted source β€” a very common trust boundary for ML model files β€” can be crashed (denial of service) by supplying a checkpoint containing a single NDArrayView value with an out-of-range data_type (or storage_format) enum on the wire. The crash occurs deep inside the deserializer before any application-level validation of tensor contents can run, and the same pattern reaches any DictionaryValue::Value<T>() consumer downstream of Serializer::Copy, so the blast radius includes both model loading (Variable::Deserialize) and optimizer-state/checkpoint loading (Learner.cpp:496).

Suggested Fix

In Serializer::Copy(const proto::DictionaryValue&, DictionaryValue&) (Serialization.cpp), check the result of CreateFromProto(...) for nullptr before assigning it to dst.m_data.m_ptr, and either throw a well-formed deserialization error or leave dst in a state that will not be treated as containing a valid NDArrayView. Defense in depth: DictionaryValue::Value<T>() / VerifyType<T>() in API/CNTKLibrary.h should also assert/throw on a null m_data.m_ptr for pointer-backed types rather than unconditionally dereferencing.

Known CVEs / Prior Work

No CVE was found specifically covering this Dictionary/DictionaryValue null-propagation path in CNTK v2's native serializer at time of writing. This finding is unrelated to any known CVE in CNTK's ONNX import path.

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