YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
Circle Buffer Length Decision Flip PoC
Summary
A Circle model with a truncated weight buffer silently produces wrong inference output, flipping a decision from ALLOW to DENY. The ONE Runtime (onert) does not validate that Buffer.data.length matches the byte count implied by the associated Tensor.shape and Tensor.type. When the buffer is shorter than expected, onert proceeds with inference despite the incomplete buffer, and the missing value is effectively treated as 0.0 in the tested runtime path, altering the effective weight values and changing the model's output.
Target
- Format: Circle (.circle) β Samsung on-device neural network model format (FlatBuffers-based, magic
CIR0) - Runtime: ONE Runtime (onert) v1.30.0
- Operator: FULLY_CONNECTED (builtin code 9), no bias
PoC Files
| File | Description |
|---|---|
baseline_decision.circle |
Baseline model (472 B). Weight [[-1.0, 1.0]], output=1.0, decision=ALLOW |
mut_short_buffer_decision_flip.circle |
Mutated model (464 B). Weight buffer truncated to 4 bytes (first float only: -1.0). Effective weight [[-1.0, 0.0]], output=-1.0, decision=DENY |
build_decision_flip_circle.py |
Builds baseline_decision.circle using FlatBuffers. Contains build_circle_model() used by both baseline and mutated model generation |
generate_short_buffer_model.py |
Generates the mutated model by calling build_circle_model() with a 4-byte weight buffer |
inspect_models.py |
Parses both models with circle_schema and compares 32 metadata fields. Confirms only tensor[1].actual_bytes differs (8β4) |
test_runtime.py |
Runs both models through onert C API (3 trials each), classifies output as ALLOW/DENY |
results.json |
Runtime results showing DECISION_FLIP_CONFIRMED=true |
inspection.json |
Full invariant comparison output |
SHA256SUMS.txt |
SHA-256 hashes for both .circle files |
Vulnerability Mechanics
Model structure (identical for baseline and mutated):
- Input tensor: shape
[1, 2], FLOAT32, buffer index 1 (runtime, empty) - Weight tensor: shape
[1, 2], FLOAT32, buffer index 2 (constant) - Output tensor: shape
[1, 1], FLOAT32, buffer index 3 (runtime, empty) - Operator: FULLY_CONNECTED, inputs=[input, weight, -1 (no bias)], output=[output]
The mutation:
The weight tensor declares shape [1, 2] FLOAT32, which requires 1 Γ 2 Γ 4 = 8 bytes. The baseline provides 8 bytes ([-1.0, 1.0]). The mutated model provides only 4 bytes ([-1.0]). The tensor metadata (shape, type, buffer index) is unchanged.
Why the decision flips:
onert does not check Buffer.data.length >= product(Tensor.shape) * sizeof(Tensor.type) at load or prepare time. It proceeds to inference using whatever data is in the buffer, with the missing second float effectively becoming 0.0.
Baseline: weight = [-1.0, 1.0] β 1.0Γ(-1.0) + 2.0Γ1.0 = +1.0 β ALLOW
Mutated: weight = [-1.0, 0.0] β 1.0Γ(-1.0) + 2.0Γ0.0 = -1.0 β DENY
No warning or error is emitted at any stage (load, prepare, run).
Reproduction
Prerequisites
- Python 3.8+
flatbuffersPython package (pip install flatbuffers)circle-schemaPython package (pip install circle-schema)numpy- onert 1.30.0 runtime (aarch64 release from Samsung ONE GitHub)
Step 1: Generate models
python3 build_decision_flip_circle.py
python3 generate_short_buffer_model.py
Step 2: Inspect models (no runtime needed)
python3 inspect_models.py
Verify in inspection.json that all_differences_expected is true β only tensor[1].actual_bytes differs (8 vs 4).
Step 3: Run through onert
export ONERT_LIB_PATH=/path/to/onert/lib
python3 test_runtime.py
Verify in results.json:
baseline_decision.circle: output=1.0, decision=ALLOW (3/3 trials)mut_short_buffer_decision_flip.circle: output=-1.0, decision=DENY (3/3 trials)DECISION_FLIP_CONFIRMED: trueWARNING_EMITTED: false
Expected Results
| Model | Buffer bytes | Expected bytes | Output | Decision |
|---|---|---|---|---|
| baseline_decision.circle | 8 | 8 | 1.0 | ALLOW |
| mut_short_buffer_decision_flip.circle | 4 | 8 | -1.0 | DENY |
Decision function: output > 0.0 β ALLOW, output β€ 0.0 β DENY
All results are deterministic across 3 trials. No warning or error is emitted by onert.
Non-Claims
This PoC demonstrates a silent wrong-output vulnerability leading to a deterministic decision flip. It does not claim:
- Remote code execution (RCE) or arbitrary code execution (ACE)
- ASAN-confirmed memory corruption (heap-buffer-overflow, use-after-free, etc.)
- Memory content disclosure or information leak
- Scanner/antivirus bypass
- Exploitability beyond incorrect inference output
The root cause is a missing validation: onert should verify that each constant tensor's buffer provides at least product(shape) * sizeof(type) bytes before proceeding to inference.
Patch Point
onert/core/src/loader/CircleLoader.cc β add a check at model load time that rejects models where Buffer.data.length < product(Tensor.shape) * sizeof(Tensor.type) for all non-empty constant buffers.
SHA256
58fb44e5756dbf22891cc2ef1b626fc7487f6b81e05561b1c79cb13337eeecad baseline_decision.circle
e5348de5a4e24323355ad255113667d45fecd1946b27b5a0f11799a8b6a21aa9 mut_short_buffer_decision_flip.circle