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.

luci Circle importer β€” OOB heap read in STRING const-tensor offset-table deserialization

Target: Samsung/ONE β€” compiler/luci, Circle model importer File: compiler/luci/import/src/Nodes/CircleConst.cpp β€” copy_data<loco::DataType::STRING>() Version: master (reproduced against the current released Circle schema in res/CircleSchema; the vulnerable loop is present after the CVE-2026-6839 / PR #16481 offset-value hardening) Class: Out-of-bounds heap READ (CWE-125) β€” attacker-controlled model file Reachability: default-registered CircleConstNodeBuilder::build(), run for every constant tensor by luci::Importer::importModule() (circle2circle, circle-quantizer, record-minmax, …)


Summary

The STRING specialization of copy_data() deserializes a string offset table straight out of the tensor's backing Buffer via a raw int32_t pointer, reading num_elements + 1 offsets without any check that the Buffer is large enough to contain them.

  • num_elements is the product of the tensor's declared shape (attacker-controlled).
  • raw_data is the tensor's Buffer bytes (independently attacker-controlled).

Nothing verifies the Buffer is large enough to hold num_elements + 1 int32 offsets before they are read. A tiny Buffer paired with a huge declared shape drives the read far past the end of the heap allocation.

Root cause

// compiler/luci/import/src/Nodes/CircleConst.cpp
template <> void copy_data<loco::DataType::STRING>(const VectorWrapper<uint8_t> &raw_data,
                                                   uint32_t num_elements,
                                                   CircleConst *const_node)
{
  ...
  const auto *i32d = reinterpret_cast<const int32_t *>(raw_data.data());
  // assert(*i32d == num_elements);   // no-op under NDEBUG
  i32d++;                             // skip count

  std::vector<int32_t> offsets;
  offsets.push_back(*i32d++);
  for (uint32_t i = 0; i < num_elements; ++i)
    offsets.push_back(*i32d++);       // <-- OOB READ: no bound vs raw_data.size()
  ...
  // (PR #16481) validate offset VALUES -- runs only AFTER the OOB reads above
  for (uint32_t i = 0; i < offsets.size(); ++i) { ... }
}

num_elements + 1 int32 words (here 2,000,001 β†’ ~8 MB) are read from a Buffer that is only 8 bytes long.

Why existing defenses do not cover it

  • CVE-2026-6839 / PR #16481 added only offset-value validation (non-negative / monotonic / bounded by raw_data.size()). That loop runs after the offsets have already been read out of bounds, so it cannot prevent the over-read β€” the crash happens during the read loop, before validation is reached.
  • circle::VerifyModelBuffer() checks only structural flatbuffer validity, not this cross-field (shape vs. buffer-size) semantic invariant, so the crafted file passes verification.

Reachability

CircleConstNodeBuilder::build() is the default-registered builder run for every constant tensor by luci::Importer::importModule(). For a STRING tensor with num_elements > 0 it dispatches to copy_data<loco::DataType::STRING>(). Every luci-based tool that imports an untrusted .circle (circle2circle, circle-quantizer, record-minmax, …) is affected.

PoC

Faithful flatbuffer harness (same methodology as the two prior accepted Circle findings), built with the project's own Circle schema (flatc-generated from res/CircleSchema) and the real flatbuffers runtime. copy_data<STRING>() is copied verbatim from CircleConst.cpp; only the CircleConst IR node is replaced by a minimal size<>/at<> mock β€” irrelevant, since the crash occurs in the offset-read loop before the mock is ever touched.

Files:

  • build_malicious_string.cpp β†’ malicious_string.circle: one STRING const tensor shape=[2000000] (num_elements = 2,000,000) with an 8-byte Buffer. Passes circle::VerifyModelBuffer (244 bytes).
  • repro_string.cpp β†’ repro_string_asan / repro_string_release: verbatim copy_data<STRING> driven by real schema accessors.
  • benign_string.circle: negative control (Buffer large enough to hold all offsets).

Run

./build_malicious_string malicious_string.circle 2000000 2
./repro_string_asan malicious_string.circle     # ASan heap-buffer-overflow READ
./repro_string_release malicious_string.circle  # O2/NDEBUG -> SIGSEGV (139)
./repro_string_asan benign_string.circle        # negative control -> clean

Captured evidence (verbatim)

[load] read 244 bytes from malicious_string.circle
[verify] circle::VerifyModelBuffer() => PASS
[build] tensor type=STRING(5) shape dims=1 -> num_elements=2000000; buffer bytes=8
[copy_data<STRING>] raw_data.size()=8 bytes; about to read count+ (num_elements=2000000) offsets ...
=================================================================
==1365790==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7cbd6d3e0140 at pc 0x5613b421f7f9 bp 0x7ffe0c90b4f0 sp 0x7ffe0c90b4e8
READ of size 4 at 0x7cbd6d3e0140 thread T0
    #0 ... std::__new_allocator<int>::construct<int, int const&>(int*, int const&) new_allocator.h:191
    #2 ... std::vector<int, std::allocator<int> >::push_back(int const&) stl_vector.h:1421
    #3 ... copy_data_STRING(VectorWrapper<unsigned char> const&, unsigned int, MockCircleConst*) repro_string.cpp:71
    #4 ... main repro_string.cpp:122
0x7cbd6d3e0140 is located 0 bytes after 256-byte region [0x7cbd6d3e0040,0x7cbd6d3e0140)
SUMMARY: AddressSanitizer: heap-buffer-overflow repro_string.cpp:71 in copy_data_STRING(...)

Release build (g++ -O2 -DNDEBUG) on the same file:

[verify] circle::VerifyModelBuffer() => PASS
[build] tensor type=STRING(5) shape dims=1 -> num_elements=2000000; buffer bytes=8
[copy_data<STRING>] raw_data.size()=8 bytes; about to read count+ (num_elements=2000000) offsets ...
<process terminates: SIGSEGV, exit code 139>

Negative control (Buffer sized for all offsets):

[build] tensor type=STRING(5) shape dims=1 -> num_elements=2; buffer bytes=24
[copy_data<STRING>] raw_data.size()=24 bytes; about to read count+ (num_elements=2) offsets ...
[done] no crash (offsets read within bounds)

Deduplication

  • Distinct from CVE-2026-6839 / PR #16481. That fix validates offset values after they are read; this report is the read itself going out of bounds (a missing buffer-size bound on the read loop). The fix does not address it β€” verified by including PR #16481's exact validation loop in the harness: the crash occurs before it runs.
  • Distinct from the prior accepted Circle findings (opcode-index and other importer OOB reports); this is a separate code path (copy_data<STRING> offset-table read) in CircleConst.cpp.

Suggested fix

Before the read loop, require raw_data.size() >= (num_elements + 2) * sizeof(int32_t) (count + num_elements+1 offsets), throwing on failure β€” mirroring the existing offset-value checks but performed before the raw pointer is dereferenced.

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