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.

ArmNN Deserializer β€” NULL-pointer dereferences via missing optional FlatBuffers fields (CWE-476)

Status: gated, access on request (huntr / ProtectAI triage). Do not use against production systems you do not own.

Summary

ARM-software/armnn's native model-file parser (armnnDeserializer, src/armnnDeserializer/Deserializer.cpp) repeatedly calls flatbuffers accessor methods on fields that are optional in ArmnnSchema.fbs (i.e. legally absent/null from any well-formed, VerifyModelBuffer-passing .armnn file) without a null check first. Two independent call sites crash on a .armnn file that is otherwise structurally valid:

  1. IDeserializer::DeserializerImpl::GetOutputs() (Deserializer.cpp:848):

    TensorRawPtrVector IDeserializer::DeserializerImpl::GetOutputs(const GraphPtr& graphPtr, unsigned int layerIndex)
    {
        CHECK_LAYERS(graphPtr, 0, layerIndex);
        auto layer = GetBaseLayer(graphPtr, layerIndex);
        const auto& numOutputs = layer->outputSlots()->size();   // <-- outputSlots() may be nullptr
        ...
    

    LayerBase.outputSlots is an optional [OutputSlot] vector field in the schema. A layer (e.g. a ConstantLayer) whose base.outputSlots field is simply omitted from the serialized flatbuffer produces layer->outputSlots() == nullptr, and ->size() on that null pointer segfaults.

  2. IDeserializer::DeserializerImpl::SetupInputLayers() (Deserializer.cpp:1059):

    void IDeserializer::DeserializerImpl::SetupInputLayers(GraphPtr graph)
    {
        CHECK_GRAPH(graph, 0);
        const unsigned int numInputs = graph->inputIds()->size();   // <-- inputIds() may be nullptr
        ...
    

    Graph.inputIds is an optional [int] vector field. A graph that omits it entirely (as opposed to supplying an empty [], which is legal and handled by CHECK_GRAPH) makes graph->inputIds() return nullptr, and ->size() segfaults identically.

Both are the same root-cause pattern (CWE-476: calling a flatbuffers accessor on an optional field's raw pointer without a null check), just triggered by omitting two different optional fields, so we treat this as one vulnerability class with two independently reachable PoCs.

Attacker input β†’ sink

  • nullderef_poc_getoutputs.armnn: one InputLayer + one ConstantLayer whose base.base (LayerBase) table explicitly omits the outputSlots field. Loading it via armnnDeserializer::IDeserializer::CreateNetworkFromBinary() reaches CreateNetworkFromGraph() β†’ dispatch to ParseConstant() (Deserializer.cpp:1487) β†’ GetOutputs() (Deserializer.cpp:848) β†’ null-deref on outputSlots()->size().
  • nullderef_poc_setupinputlayers.armnn: a Graph table that omits the inputIds field entirely. CreateNetworkFromGraph() calls SetupInputLayers() unconditionally (Deserializer.cpp:939) after parsing all layers, which null-derefs on inputIds()->size().

Both fields are declared (optional) (no explicit default forcing presence) in ArmnnSchema.fbs, so flatbuffers::Verifier/circle-style structural verification (FlatBuffers' own Verify(), which ArmNN's deserializer does not even call before walking the buffer) would not reject either file as malformed β€” omitting an optional field is, by flatbuffers definition, perfectly valid wire data.

Reproduction

Built at ArmNN commit (see ARMNN_COMMIT.txt), current main HEAD at time of testing, with ASan (-fsanitize=address -g -O1 -DNDEBUG), reusing the same build/harness pattern as the companion armnn-heapoverflow/armnn-stackoverflow findings (harness.cpp β€” minimal driver that calls the real, unmodified armnnDeserializer::IDeserializer::CreateNetworkFromBinary()).

# harness already built against libarmnnDeserializer.so / libarmnn.so (ASan build)
LD_LIBRARY_PATH=<armnn-build-dir> ASAN_OPTIONS=detect_leaks=0 ./harness nullderef_poc_getoutputs.armnn
LD_LIBRARY_PATH=<armnn-build-dir> ASAN_OPTIONS=detect_leaks=0 ./harness nullderef_poc_setupinputlayers.armnn

Observed result (real evidence, not theoretical)

nullderef_poc_getoutputs.armnn:

==916400==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
    #0 ... flatbuffers::Vector<flatbuffers::Offset<armnnSerializer::OutputSlot>, unsigned int>::size() const vector.h:164
    #1 ... armnnDeserializer::IDeserializer::DeserializerImpl::GetOutputs(...) Deserializer.cpp:848
    #2 ... armnnDeserializer::IDeserializer::DeserializerImpl::ParseConstant(...) Deserializer.cpp:1487
    #3 ... armnnDeserializer::IDeserializer::DeserializerImpl::CreateNetworkFromGraph(...) Deserializer.cpp:934
    #4 ... armnnDeserializer::IDeserializer::DeserializerImpl::CreateNetworkFromBinary(...) Deserializer.cpp:882
    #5 ... armnnDeserializer::IDeserializer::CreateNetworkFromBinary(...) Deserializer.cpp:59
    #6 ... main harness.cpp:29
SUMMARY: AddressSanitizer: SEGV vector.h:164 in flatbuffers::Vector<...>::size() const

nullderef_poc_setupinputlayers.armnn:

==916402==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
    #0 ... flatbuffers::Vector<int, unsigned int>::size() const vector.h:164
    #1 ... armnnDeserializer::IDeserializer::DeserializerImpl::SetupInputLayers(...) Deserializer.cpp:1059
    #2 ... armnnDeserializer::IDeserializer::DeserializerImpl::CreateNetworkFromGraph(...) Deserializer.cpp:939
    #3 ... armnnDeserializer::IDeserializer::DeserializerImpl::CreateNetworkFromBinary(...) Deserializer.cpp:882
    #4 ... armnnDeserializer::IDeserializer::CreateNetworkFromBinary(...) Deserializer.cpp:59
    #5 ... main harness.cpp:29
SUMMARY: AddressSanitizer: SEGV vector.h:164 in flatbuffers::Vector<int, ...>::size() const

Full logs: asan_crash_log_getoutputs.txt, asan_crash_log_setupinputlayers.txt. Both are raw SIGSEGV on a null pointer read β€” reproducible identically with or without ASan/NDEBUG, unlike the sibling underflow/OOB-read findings that depend on NDEBUG compiling out an assert().

Suggested fix

Add explicit null checks (if (layer->outputSlots() == nullptr) { ... } / if (graph->inputIds() == nullptr) { ... }) before calling ->size()/iterating, matching the pattern ArmNN already uses elsewhere for genuinely optional fields (e.g. graph->featureVersions() in GetFeatureVersions(), a few lines above SetupInputLayers() in the same file).

Files

  • nullderef_poc_getoutputs.armnn β€” crafted model triggering the GetOutputs() null-deref.
  • nullderef_poc_setupinputlayers.armnn β€” crafted model triggering the SetupInputLayers() null-deref.
  • harness.cpp β€” minimal driver that calls the real, unmodified armnnDeserializer::IDeserializer::CreateNetworkFromBinary() API on a file path given on the command line.
  • ArmnnSchema.fbs β€” copy of the exact schema used (from the tested commit).
  • asan_crash_log_getoutputs.txt, asan_crash_log_setupinputlayers.txt β€” full AddressSanitizer reports from running the harness against each PoC file.
  • ARMNN_COMMIT.txt β€” the exact ARM-software/armnn commit hash this was built and tested against.
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