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

Check out the documentation for more information.

MFV β€” Samsung/ONE (onert) Circle/TFLite model loader ignores flatbuffers verifier β†’ OOB read

Target format (huntr): Circle ($1500 tier, "Model File Formats") Affected project: Samsung/ONE β€” On-device Neural Engine (onert runtime) Affected loaders: CircleLoader (.circle), TFLiteLoader (.tflite) β€” both inherit BaseLoader Class: CWE-125 Out-of-bounds Read / CWE-252 Unchecked Return Value β€” heap-buffer-overflow READ and SEGV on malicious model file, reachable via the documented public C API. Status: TOOL-VERIFIED under AddressSanitizer (2 distinct crash variants, clean reproduction from a fresh clone). 2026-06-12.


Threat model (why this is in MFV scope)

Samsung/ONE is Samsung's on-device neural engine, used on Samsung phones, Tizen devices, and edge AI deployments. The .circle format is ONE's native model format β€” every model compiled by ONE's toolchain ships as a .circle file. The canonical load path is:

// Public C API (runtime/onert/api/nnfw/src/APIImpl.cc:78)
nnfw_load_model_from_file(session, "model.circle");
// -> Session::loadModelFile -> loadCircleModel -> CircleLoader::loadFromFile -> loadModel()

Loading an untrusted .circle (or .tflite) model file causes memory corruption before inference even starts β€” the OOB reads occur during model parsing in loadModel(). A victim who downloads a model from a hub/colleague and loads it is memory-corrupted at parse time.


Summary of findings

# Variant Primitive Trigger Verified
1 Inflated tensor shape vector heap-buffer-overflow READ (ASAN) shape dims count 2 β†’ 0x40000000 βœ… ASAN
2 Inflated subgraphs vector SEGV READ at unmapped address subgraphs count 1 β†’ 4096 βœ… ASAN

Both variants share one root cause: BaseLoader::loadModel() calls VerifyModelBuffer() but discards the return value, then proceeds to walk the model's flatbuffer data structures. Any corrupted vector length, offset, or table field causes an out-of-bounds read.


Root Cause

BaseLoader.h line 1702–1705 (Samsung/ONE @ de7f4736):

template <typename LoaderDomain> std::unique_ptr<ir::Model> BaseLoader<LoaderDomain>::loadModel()
{
  LoaderDomain::VerifyModelBuffer(*_verifier.get());  // line 1704: RETURN VALUE DISCARDED
  _domain_model = LoaderDomain::GetModel(_base);       // line 1705: proceeds on UNVERIFIED buffer
  // ... then walks metadata_list, signature_table, subgraphs, tensors, shapes
  // on UNVERIFIED flatbuffer data β†’ OOB reads on any corrupted field

VerifyModelBuffer() is a flatbuffers Verifier call that checks all offsets, vector lengths, and table fields are within bounds. It returns bool β€” true if the buffer is structurally valid, false if corrupted. The return value is silently discarded. The very next line calls GetModel() on the raw buffer and begins walking the data structures.

After GetModel(), loadModel() reads:

  • _domain_model->metadata() β†’ metadata_list->size() β†’ metadata_list->Get(i) (line 1713–1724)
  • _domain_model->signature_defs() β†’ signature_table->size() β†’ signature_table->Get(i) (line 1728–1742)
  • _domain_model->subgraphs() β†’ subgraphs->size() β†’ (*subgraphs)[i] (line 1747–1757)
  • Each subgraph's tensors(), inputs(), outputs(), operators() (in loadSubgraph)
  • Each tensor's shape() β†’ shape->size() β†’ (*shape)[s] (in loadOperand)

All of these are OOB-readable fields if the flatbuffer is corrupted. The verifier exists to catch this β€” but its result is thrown away.

Why the verifier is there but useless

The _verifier is correctly constructed with the buffer base and size in loadFromFile() (line 235) and loadFromBuffer() (line 250). The call to VerifyModelBuffer() at line 1704 is syntactically present β€” it looks like the developer intended to validate the buffer. But without checking the return value, it's a no-op. The likely origin is a copy-paste from flatbuffers documentation where the if(!Verify...) was accidentally dropped.

Contrast: Arm NN does it correctly

Arm NN's TfLiteParser (same domain β€” loading TFLite/flatbuffer models) checks the verifier correctly:

// armnn/src/armnnTfLiteParser/TfLiteParser.cpp:5524-5532
flatbuffers::Verifier verifier(binaryContent, len);
if (verifier.VerifyBuffer<tflite::Model>() == false)
{
    throw ParseException("Buffer doesn't conform to the expected Tensorflow Lite "
                         "flatbuffers format. ...");
}
return tflite::UnPackModel(binaryContent);  // only reached on verified buffer

Variant 1 β€” Inflated tensor shape vector β†’ heap-buffer-overflow READ

A valid .circle model with tensor shape [1, 4] (2 elements). The shape vector's length field is corrupted from 2 to 0x40000000 (1G elements). The verifier rejects it (false), but loadModel() proceeds and reads 4GB past the 192-byte buffer.

ASAN Evidence

==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x...0200 at pc 0x... thread T0
READ of size 4 at 0x...0200
    #0 flatbuffers::IndirectHelper<int>::Read  ONE/onert-micro/externals/flatbuffers/buffer.h:99
    #1 flatbuffers::Vector<int, unsigned int>::Get  ONE/onert-micro/externals/flatbuffers/vector.h:177
    #2 flatbuffers::Vector<int, unsigned int>::operator[]  vector.h:180
    #3 load_like_onert  harness.cpp:84
0x...0200 is located 0 bytes after 192-byte region [0x...0140,0x...0200)

(full log: findings/circle_evidence/asan_oob_read.txt)


Variant 2 β€” Inflated subgraphs vector β†’ SEGV at unmapped address

The subgraphs vector count is corrupted from 1 to 4096. The verifier rejects it. loadModel() reads subgraphs->size() = 4096 and iterates, dereferencing (*subgraphs)[1] which produces a wild pointer into unmapped memory.

ASAN Evidence

==ERROR: AddressSanitizer: SEGV on unknown address 0x7c47655e008d
The signal is caused by a READ memory access.
    #0 flatbuffers::ReadScalar<unsigned short>  ONE/onert-micro/externals/flatbuffers/base.h:427
    #1 flatbuffers::Table::GetOptionalFieldOffset  table.h:39
    #2-3 flatbuffers::Table::GetPointer  table.h:52,59
    #4 circle::SubGraph::tensors()  gen/circle_schema_generated.h:13097
    #5 main  harness2.cpp:67

(full log: findings/circle_evidence/asan_subgraphs_oob.txt)


Affected versions

  • Samsung/ONE @ de7f4736 (HEAD as of 2026-06-12): AFFECTED β€” BaseLoader.h:1704 discards the verifier return value. Both CircleLoader and TFLiteLoader inherit the vulnerable loadModel().
  • The bug has been present since the verifier call was added. A git log search shows no prior fix attempt.
  • Arm NN: NOT affected β€” both TfLiteParser and Deserializer properly check the verifier return and throw on failure.

Exploitability assessment

  • Primitive: OOB read, attacker-controlled scope. The attacker crafts a .circle file with arbitrary vector lengths/offsets; the loader reads past the buffer by up to the inflated count Γ— element size. This yields:
    • Crash / DoS (guaranteed β€” demonstrated above)
    • Information disclosure β€” OOB-read data from adjacent heap allocations may leak through error messages, tensor metadata, or subsequent processing. The read values feed make_unique<ir::Model> sizing and allocation, so OOB-read data influences control flow.
    • Potential for escalation β€” the OOB-read values are used as offsets into the same buffer (flatbuffer indirect addressing), creating a read-from-read chain. A carefully crafted file could steer this chain to read from attacker-controlled buffer regions, though a full info-leak chain is not demonstrated here.
  • Attack surface: any application that loads untrusted .circle or .tflite models through ONE's public API (nnfw_load_model_from_file, nnfw_load_model_from_modelfile, loadCircleModel, loadTFLiteModel). This includes Samsung's on-device AI framework on phones/Tizen.
  • Mitigations: ASLR is relevant only if the OOB read can be observed by the attacker. Flatbuffers' Verifier is the intended mitigation β€” it exists in the code but is defeated by the unchecked return.

Reproduction (self-contained)

poc/circle/gen_and_run.sh clones Samsung/ONE, generates the Circle schema header, builds the harness with ASAN, generates a corrupted .circle file, and triggers the OOB read:

poc/circle/gen_and_run.sh /tmp/circle_mfv_poc
# -> heap-buffer-overflow READ (inflated tensor shape vector)

The C++ harness (poc/mfv_circle-armnn.cpp) replicates the exact code path from BaseLoader::loadModel():

  1. Builds a valid 192-byte .circle model via flatbuffers API
  2. Corrupts a vector length field (shape count or subgraphs count)
  3. Calls VerifyModelBuffer() (returns false β€” correctly rejects)
  4. Calls GetModel() + walks subgraphs/tensors/shapes (exactly as onert does)
  5. ASAN catches the OOB read

Build flags: g++ -std=c++17 -fsanitize=address -g -O0 -I gen -I ONE/onert-micro/externals


Remediation

Check the VerifyModelBuffer() return value and abort loading on failure:

// BaseLoader.h:1704 β€” FIX
if (!LoaderDomain::VerifyModelBuffer(*_verifier.get()))
{
  throw std::runtime_error("Model buffer verification failed β€” rejecting malformed model");
}
_domain_model = LoaderDomain::GetModel(_base);

This is a one-line fix that enables the protection the developer clearly intended. The verifier call is already there β€” only the return check is missing.


Novelty / dedup

  • No CVE found for this specific bug. Existing Samsung ONE CVEs (CVE-2026-40450: integer overflow in tensor copy; CVE-2026-6839: STRING tensor offset validation) are different root causes in different code paths.
  • No prior huntr disclosure found for Circle format.
  • The root cause (discarded VerifyModelBuffer() return) is distinct from all known Samsung ONE vulnerabilities.
  • This is a classic CWE-252 (Unchecked Return Value) enabling CWE-125 (OOB Read) β€” clean, well-understood vulnerability class with a trivial fix.

Suggested CVSS

AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H β†’ 6.1 (local file, user loads malicious model; OOB read β†’ crash + potential info leak; no integrity impact demonstrated).

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