Samsung ONE (onert runtime) β unchecked Tensor.buffer index -> OOB read / SEGV in BaseLoader::loadOperand()
Status: gated, manual-approval PoC repository. For authorized security research / bug-bounty triage only (huntr.com MFF).
Target
- Project: Samsung/ONE β
runtime/onert(the on-device NN runtime, as distinct from thecompiler/luciimport path covered by a separate, already-confirmed finding in this campaign) - Vulnerable code:
runtime/onert/core/src/loader/BaseLoader.h,BaseLoader<LoaderDomain>::loadOperand(), line 401 (at the last commit before this directory was removed from the repository β see "A note on timing" below) - Class: CWE-125 Out-of-bounds Read / crash (SIGSEGV), triggered while loading a crafted
.circlemodel.
Root cause
Every Tensor in a .circle/TFLite-format FlatBuffer model carries a buffer field β an
unsigned 32-bit index into the model's buffers vector. BaseLoader::loadOperand() uses this
attacker-controlled index completely unchecked:
// runtime/onert/core/src/loader/BaseLoader.h:401
const auto *data = _domain_model->buffers()->Get(tensor->buffer())->data();
buffers()->Get(idx) is flatbuffers::Vector<T>::Get(), whose only bounds check is:
// flatbuffers/vector.h
return_type Get(SizeT i) const {
FLATBUFFERS_ASSERT(i < size());
...
}
and FLATBUFFERS_ASSERT expands to plain assert(), which is compiled to a no-op under
NDEBUG (i.e. any normal release build). A .circle file that passes
circle::VerifyModelBuffer() cleanly (FlatBuffers structural verification does not check this
semantic invariant) but sets Tensor.buffer = 0xFFFFFFFF against a buffers vector of length 1
causes Get() to compute a wild, out-of-bounds byte offset and dereference it.
A note on timing / how this was verified
runtime/onert was removed from the Samsung/ONE repository's master branch a few hours
before this PoC was finalized (commit 342395fc, "Remove source from repo", 2026-07-10). This
harness fetches BaseLoader.h from the immediately preceding commit,
de7f4736dc4c4f5e47f72a4022a9aa9ac6d1ad1a, i.e. the last version of the file as it existed in
the live repository, and verifies against the REAL, unmodified:
- generated Circle FlatBuffers schema accessors (
circle::Model::buffers(),circle::Buffer::data(),circle::Tensor::buffer()β reused from this campaign's already-confirmedcompiler/lucifinding, which regenerated them withflatcfrom the project's ownres/CircleSchema/0.10/circle_schema.fbs) - the REAL, unmodified system
flatbuffers::Vector<T>::Get()(flatbuffers 23.5.26)
repro_bufidx.cpp calls BaseLoader.h:401's exact expression verbatim
(model->buffers()->Get(tensor->buffer())->data()) against a crafted poc_malicious.circle
whose single Tensor.buffer field is 0xFFFFFFFF against a 1-entry buffers vector. No
onert-internal code is mocked or reimplemented for the vulnerable call itself β only the
surrounding ir::Graph/operand-creation machinery (irrelevant to this specific read) is not
built, since the crash occurs before any of that is reached.
Proof of concept
Release-mode run (NDEBUG, matches the project's shipped build configuration)
[load] read 184 bytes from poc_malicious.circle
[verify] circle::VerifyModelBuffer() => PASS
[model] subgraphs count = 1
[model] subgraph[0].tensors count = 1
[model] buffers vector size = 1
[model] tensor[0].buffer() (attacker-controlled index) = 4294967295
[VULN] executing the REAL BaseLoader.h:401 line verbatim:
[VULN] _domain_model->buffers()->Get(tensor->buffer())->data();
release exit code: 139 (SIGSEGV)
ASan run (asan_output.txt)
==1185549==ERROR: AddressSanitizer: SEGV on unknown address 0x7c1154be00e8
#0 ... in unsigned int flatbuffers::ReadScalar<unsigned int>(void const*) /tmp/flatbuffers-include/flatbuffers/base.h:427
#1 ... in flatbuffers::IndirectHelper<flatbuffers::Offset<circle::Buffer> >::Read(...) flatbuffers/buffer.h:123
#2 ... in flatbuffers::Vector<flatbuffers::Offset<circle::Buffer>, unsigned int>::Get(unsigned int) const flatbuffers/vector.h:177
#3 ... in main repro_bufidx.cpp:67
SUMMARY: AddressSanitizer: SEGV .../flatbuffers/base.h:427 in flatbuffers::ReadScalar<unsigned int>(void const*)
Both a real (unsanitized) segfault and a clean ASan diagnostic were captured from the same, unmodified vulnerable line.
Reachability
BaseLoader<LoaderDomain>::loadModel() calls LoaderDomain::VerifyModelBuffer() but discards
its return value (BaseLoader.h:1704-1705), then unconditionally walks every Tensor in
every SubGraph via loadOperand(). Any onert-based tool that loads a .circle model (the
on-device NNAPI/onert runtime itself, onert_run, etc.) hits this path.
Files
repro_bufidx.cppβ the verification harness (see file header comment for full methodology)poc_malicious.circleβ the crafted 184-byte malicious model (build_malicious_circle.py/simulate_baseloader.pyfrom the draft, kept for provenance β notesimulate_baseloader.pyonly hand-decoded the FlatBuffer bytes withstruct; it did not exercise real Samsung/ONE or real FlatBuffers code. This repo replaces that simulation with a real, dynamically-verified reproduction.)BaseLoader.hβ the real vulnerable header, fetched at the last commit before its removalgen/mio/circle/schema_generated.hβ real, flatc-generated Circle schema accessors (reused from this campaign's already-confirmedcompiler/lucifinding)asan_output.txtβ captured AddressSanitizer tracerepro_bufidx_release/repro_bufidx_asanβ compiled binaries for direct reproduction
Build & run
g++ -std=c++17 -O2 -DNDEBUG -I. -I<flatbuffers-include> -o repro_bufidx_release repro_bufidx.cpp
./repro_bufidx_release poc_malicious.circle # segfaults (exit 139)
g++ -std=c++17 -O0 -g -DNDEBUG -fsanitize=address,undefined \
-I. -I<flatbuffers-include> -o repro_bufidx_asan repro_bufidx.cpp
./repro_bufidx_asan poc_malicious.circle # clean ASan SEGV report