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
- Threat model (why this is in MFV scope)
- Summary of findings
- Root Cause
- Variant 1 β Inflated tensor shape vector β heap-buffer-overflow READ
- Variant 2 β Inflated subgraphs vector β SEGV at unmapped address
- Affected versions
- Exploitability assessment
- Reproduction (self-contained)
- Remediation
- Novelty / dedup
- Suggested CVSS
- Threat model (why this is in MFV scope)
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()(inloadSubgraph) - Each tensor's
shape()βshape->size()β(*shape)[s](inloadOperand)
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:1704discards the verifier return value. BothCircleLoaderandTFLiteLoaderinherit the vulnerableloadModel(). - The bug has been present since the verifier call was added. A
git logsearch shows no prior fix attempt. - Arm NN: NOT affected β both
TfLiteParserandDeserializerproperly check the verifier return and throw on failure.
Exploitability assessment
- Primitive: OOB read, attacker-controlled scope. The attacker crafts a
.circlefile 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
.circleor.tflitemodels 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'
Verifieris 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():
- Builds a valid 192-byte
.circlemodel via flatbuffers API - Corrupts a vector length field (shape count or subgraphs count)
- Calls
VerifyModelBuffer()(returnsfalseβ correctly rejects) - Calls
GetModel()+ walks subgraphs/tensors/shapes (exactly as onert does) - 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).