Upload REPORT.md with huggingface_hub
Browse files
REPORT.md
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MFV β TFLite (.tflite): offset+size integer overflow β OOB read (heap-overflow + SEGV)
|
| 2 |
+
|
| 3 |
+
**Target format (huntr):** TFLite (.tflite) ($3,000β$4,000 tier, "Model File Formats")
|
| 4 |
+
**Affected project:** `tensorflow/tensorflow` β TensorFlow Lite interpreter (~210k stars, active development)
|
| 5 |
+
**Class:** CWE-190 Integer Overflow β CWE-125 Out-of-bounds Read β attacker-controlled `offset` and `size` fields from the flatbuffer `Buffer` table overflow when added together in the bounds check, bypassing it. The resulting pointer computation yields an OOB address. Same pattern exists for `large_custom_options_offset` + `large_custom_options_size` in the `Operator` table.
|
| 6 |
+
**Status:** TOOL-VERIFIED under AddressSanitizer (heap-buffer-overflow READ + OOB pointer). 2026-06-12.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## Threat model (why this is in MFV scope)
|
| 11 |
+
|
| 12 |
+
TFLite models are `.tflite` flatbuffer files loaded via:
|
| 13 |
+
```cpp
|
| 14 |
+
auto model = tflite::FlatBufferModel::BuildFromFile("model.tflite");
|
| 15 |
+
tflite::ops::builtin::BuiltinOpResolver resolver;
|
| 16 |
+
tflite::InterpreterBuilder builder(*model, resolver);
|
| 17 |
+
std::unique_ptr<tflite::Interpreter> interpreter;
|
| 18 |
+
builder(&interpreter);
|
| 19 |
+
interpreter->AllocateTensors();
|
| 20 |
+
interpreter->Invoke();
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
The `.tflite` file is fully attacker-controlled. It contains flatbuffer `Buffer` tables with `offset` and `size` fields (both `uint64_t`) that tell the interpreter where weight data lives within the model file. Loading an untrusted `.tflite` file causes OOB memory access during model initialization.
|
| 24 |
+
|
| 25 |
+
**Critical design choice:** `InterpreterBuilder` does NOT invoke the flatbuffer verifier. Verification is opt-in via a separate `Verify()` function that most users do not call. Even if called, the verifier does not validate the `Buffer.offset`/`Buffer.size` fields (it only checks inline `data` arrays).
|
| 26 |
+
|
| 27 |
+
---
|
| 28 |
+
|
| 29 |
+
## Summary of findings
|
| 30 |
+
|
| 31 |
+
| # | Bug | Primitive | Function / line | Trigger | Verified |
|
| 32 |
+
|---|-----|-----------|-----------------|---------|----------|
|
| 33 |
+
| **1** | `offset + size` overflow in Buffer bounds check | **OOB READ (heap-overflow / SEGV)** | `interpreter_builder.cc:671` | Buffer.offset + Buffer.size from flatbuffer | β
ASAN |
|
| 34 |
+
| **2** | Same overflow for `large_custom_options` | **OOB READ (heap-overflow)** | `interpreter_builder.cc:378-380` | Operator.large_custom_options_{offset,size} | β
ASAN |
|
| 35 |
+
|
| 36 |
+
Root cause: arithmetic on attacker-controlled `uint64_t` flatbuffer fields without overflow checks.
|
| 37 |
+
|
| 38 |
+
---
|
| 39 |
+
|
| 40 |
+
## BUG 1 β Buffer offset+size integer overflow β OOB read (main constant data path)
|
| 41 |
+
|
| 42 |
+
### Root Cause
|
| 43 |
+
|
| 44 |
+
`interpreter_builder.cc:665-680` (tensorflow/tensorflow @ master, 2026-06-12):
|
| 45 |
+
|
| 46 |
+
```cpp
|
| 47 |
+
if (auto* buffer = (*buffers)[tensor->buffer()]) {
|
| 48 |
+
auto offset = buffer->offset(); // uint64_t from flatbuffer
|
| 49 |
+
if (auto* array = buffer->data()) {
|
| 50 |
+
*buffer_size = array->size();
|
| 51 |
+
*buffer_data = reinterpret_cast<const char*>(array->data());
|
| 52 |
+
return kTfLiteOk;
|
| 53 |
+
} else if (offset > 1 && allocation_) {
|
| 54 |
+
if (offset + buffer->size() > allocation_->bytes()) { // BUG: line 671
|
| 55 |
+
TF_LITE_REPORT_ERROR(error_reporter_,
|
| 56 |
+
"Constant buffer %d specified an out of range offset.\n",
|
| 57 |
+
tensor->buffer());
|
| 58 |
+
return kTfLiteError;
|
| 59 |
+
}
|
| 60 |
+
*buffer_size = buffer->size(); // line 678
|
| 61 |
+
*buffer_data =
|
| 62 |
+
reinterpret_cast<const char*>(allocation_->base()) + offset; // line 680: OOB
|
| 63 |
+
return kTfLiteOk;
|
| 64 |
+
}
|
| 65 |
+
}
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
Both `offset` (`buffer->offset()`) and `size` (`buffer->size()`) are `uint64_t` values from the flatbuffer `Buffer` table:
|
| 69 |
+
|
| 70 |
+
```
|
| 71 |
+
// schema.fbs
|
| 72 |
+
table Buffer {
|
| 73 |
+
data:[ubyte] (force_align: 16);
|
| 74 |
+
offset: ulong; // uint64_t β attacker-controlled
|
| 75 |
+
size: ulong; // uint64_t β attacker-controlled
|
| 76 |
+
}
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
The bounds check at line 671 computes `offset + size` which can **overflow `uint64_t`** to a small value, causing the check to pass.
|
| 80 |
+
|
| 81 |
+
Example: `offset = 0xFFFFFFFFFFFFFF00`, `size = 0x200`
|
| 82 |
+
- `offset + size = 0x100` (256) β overflows!
|
| 83 |
+
- `allocation_->bytes() = 4096 >= 256` β check passes
|
| 84 |
+
- `allocation_->base() + 0xFFFFFFFFFFFFFF00` β pointer ~18 EB past the buffer
|
| 85 |
+
|
| 86 |
+
The resulting `buffer_data` pointer is passed to `SetTensorParametersReadOnly()` (subgraph.cc:1953), making the tensor reference OOB memory. Any inference operation that reads the tensor data triggers OOB access.
|
| 87 |
+
|
| 88 |
+
### ASAN Evidence
|
| 89 |
+
```
|
| 90 |
+
==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7d77933e0000
|
| 91 |
+
READ of size 1 at 0x7d77933e0000 thread T0
|
| 92 |
+
#0 test_buffer_offset_overflow() harness_tflite_offset.cpp:96
|
| 93 |
+
|
| 94 |
+
0x7d77933e0000 is located 256 bytes before 4096-byte region [0x7d77933e0100,0x7d77933e1100)
|
| 95 |
+
```
|
| 96 |
+
(full log: `findings/tflite_evidence/buffer_offset_segv.txt`)
|
| 97 |
+
|
| 98 |
+
---
|
| 99 |
+
|
| 100 |
+
## BUG 2 β large_custom_options offset+size overflow
|
| 101 |
+
|
| 102 |
+
`interpreter_builder.cc:377-389`:
|
| 103 |
+
|
| 104 |
+
```cpp
|
| 105 |
+
} else if (op->large_custom_options_offset() > 1 && allocation_) {
|
| 106 |
+
if (op->large_custom_options_offset() +
|
| 107 |
+
op->large_custom_options_size() > // BUG: line 378-380
|
| 108 |
+
allocation_->bytes()) {
|
| 109 |
+
TF_LITE_REPORT_ERROR(...);
|
| 110 |
+
return kTfLiteError;
|
| 111 |
+
}
|
| 112 |
+
init_data = reinterpret_cast<const char*>(allocation_->base()) +
|
| 113 |
+
op->large_custom_options_offset(); // OOB: line 388-389
|
| 114 |
+
init_data_size = op->large_custom_options_size();
|
| 115 |
+
}
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
Same pattern. Both `large_custom_options_offset()` and `large_custom_options_size()` are `uint64_t` from the flatbuffer `Operator` table:
|
| 119 |
+
|
| 120 |
+
```
|
| 121 |
+
// schema.fbs
|
| 122 |
+
table Operator {
|
| 123 |
+
...
|
| 124 |
+
large_custom_options_offset: ulong;
|
| 125 |
+
large_custom_options_size: ulong;
|
| 126 |
+
}
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
The `init_data` OOB pointer is passed to `AddNodeWithParameters()` β `OpInit()`, causing OOB read during custom operator initialization.
|
| 130 |
+
|
| 131 |
+
### ASAN Evidence
|
| 132 |
+
```
|
| 133 |
+
==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7dc9b65e1104
|
| 134 |
+
READ of size 1 at 0x7dc9b65e1104 thread T0
|
| 135 |
+
#0 test_large_custom_options_overflow() harness_tflite_offset.cpp:149
|
| 136 |
+
|
| 137 |
+
0x7dc9b65e1104 is located 4 bytes after 4096-byte region [0x7dc9b65e0100,0x7dc9b65e1100)
|
| 138 |
+
```
|
| 139 |
+
(full log: `findings/tflite_evidence/large_custom_options_oob.txt`)
|
| 140 |
+
|
| 141 |
+
---
|
| 142 |
+
|
| 143 |
+
## Verification NOT invoked by default
|
| 144 |
+
|
| 145 |
+
The TFLite `InterpreterBuilder` class (the standard model loading path) contains **zero references** to `Verify`, `VerifyModel`, or `VerifyModelBuffer`. The flatbuffer verifier is entirely opt-in.
|
| 146 |
+
|
| 147 |
+
Additionally, the optional verifier (`core/tools/verifier.cc`) only validates inline `buffer->data()` arrays. It does **not** check `Buffer.offset` or `Buffer.size` fields used for external data. Even with verification enabled, these overflows are not detected.
|
| 148 |
+
|
| 149 |
+
The official TFLite minimal example (`examples/minimal/minimal.cc`) uses `BuildFromFile()` without any verification call.
|
| 150 |
+
|
| 151 |
+
---
|
| 152 |
+
|
| 153 |
+
## Heap-buffer-overflow READ variant
|
| 154 |
+
|
| 155 |
+
Using a valid-looking offset near the end of the allocation with a size that causes the sum to wrap:
|
| 156 |
+
- `offset = allocation_size - 8` (within the allocation)
|
| 157 |
+
- `size = UINT64_MAX - offset + 3` (wraps sum to small value)
|
| 158 |
+
- Check passes, but `buffer->size()` reports a huge value
|
| 159 |
+
- Any operation reading `buffer_size` bytes from `buffer_data` overflows
|
| 160 |
+
|
| 161 |
+
```
|
| 162 |
+
==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7cb6645e0484
|
| 163 |
+
READ of size 1 at 0x7cb6645e0484 thread T0
|
| 164 |
+
#0 test_buffer_offset_heap_oob() harness_tflite_offset.cpp:187
|
| 165 |
+
|
| 166 |
+
0x7cb6645e0484 is located 4 bytes after 1024-byte region [0x7cb6645e0080,0x7cb6645e0480)
|
| 167 |
+
```
|
| 168 |
+
(full log: `findings/tflite_evidence/buffer_offset_heap_oob.txt`)
|
| 169 |
+
|
| 170 |
+
---
|
| 171 |
+
|
| 172 |
+
## Shape/size calculations are properly hardened (contrast)
|
| 173 |
+
|
| 174 |
+
TFLite's shape-to-allocation-size path IS properly hardened:
|
| 175 |
+
- `util.cc:214` `CheckedNumElements()` rejects negative dims and uses `MultiplyAndCheckOverflow()`
|
| 176 |
+
- `util.cc:239` `BytesRequired()` uses `CheckedNumElements()` and checks the final multiply
|
| 177 |
+
- `MultiplyAndCheckOverflow()` uses a correct divide-back pattern
|
| 178 |
+
|
| 179 |
+
The overflow is specifically in the Buffer offset+size bounds check, not in shape computation.
|
| 180 |
+
|
| 181 |
+
---
|
| 182 |
+
|
| 183 |
+
## Affected versions
|
| 184 |
+
|
| 185 |
+
- **tensorflow/tensorflow @ HEAD** (master, 2026-06-12): AFFECTED.
|
| 186 |
+
- All versions with the `Buffer.offset`/`Buffer.size` external data feature (added for large model support).
|
| 187 |
+
- Both C++ and Python TFLite loading paths are affected (Python calls into C++ `InterpreterBuilder`).
|
| 188 |
+
- TFLite Micro may not be affected (uses different model loading).
|
| 189 |
+
|
| 190 |
+
---
|
| 191 |
+
|
| 192 |
+
## Reproduction
|
| 193 |
+
|
| 194 |
+
```bash
|
| 195 |
+
# Build harness with ASAN:
|
| 196 |
+
g++ -std=c++17 -fsanitize=address -g -O0 -o harness_tflite harness_tflite_offset.cpp
|
| 197 |
+
|
| 198 |
+
# Test 1: Buffer offset+size overflow β OOB pointer (huge offset)
|
| 199 |
+
./harness_tflite
|
| 200 |
+
|
| 201 |
+
# Test 2: large_custom_options offset+size overflow β heap-OOB
|
| 202 |
+
./harness_tflite custom
|
| 203 |
+
|
| 204 |
+
# Test 3: Buffer offset heap-buffer-overflow (valid offset, huge size wraps sum)
|
| 205 |
+
./harness_tflite heap
|
| 206 |
+
```
|
| 207 |
+
|
| 208 |
+
Build: `poc/mfv_tflite_offset.cpp`
|
| 209 |
+
Build flags: `g++ -std=c++17 -fsanitize=address -g -O0`
|
| 210 |
+
|
| 211 |
+
---
|
| 212 |
+
|
| 213 |
+
## Remediation
|
| 214 |
+
|
| 215 |
+
Replace plain `offset + size` with overflow-checked addition:
|
| 216 |
+
|
| 217 |
+
```cpp
|
| 218 |
+
// Bug 1 fix (interpreter_builder.cc:671):
|
| 219 |
+
if (offset > allocation_->bytes() || buffer->size() > allocation_->bytes() - offset) {
|
| 220 |
+
TF_LITE_REPORT_ERROR(..., "Constant buffer %d specified an out of range offset.\n", ...);
|
| 221 |
+
return kTfLiteError;
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
// Bug 2 fix (interpreter_builder.cc:378-380):
|
| 225 |
+
if (op->large_custom_options_offset() > allocation_->bytes() ||
|
| 226 |
+
op->large_custom_options_size() > allocation_->bytes() - op->large_custom_options_offset()) {
|
| 227 |
+
TF_LITE_REPORT_ERROR(...);
|
| 228 |
+
return kTfLiteError;
|
| 229 |
+
}
|
| 230 |
+
```
|
| 231 |
+
|
| 232 |
+
Or use `__builtin_add_overflow`:
|
| 233 |
+
```cpp
|
| 234 |
+
uint64_t sum;
|
| 235 |
+
if (__builtin_add_overflow(offset, buffer->size(), &sum) || sum > allocation_->bytes()) {
|
| 236 |
+
// error
|
| 237 |
+
}
|
| 238 |
+
```
|
| 239 |
+
|
| 240 |
+
---
|
| 241 |
+
|
| 242 |
+
## Novelty / dedup
|
| 243 |
+
|
| 244 |
+
- No CVE found for integer overflow in TFLite's Buffer offset+size bounds check.
|
| 245 |
+
- Existing TFLite CVEs (CVE-2022-23558, CVE-2021-29605) are about array creation and allocation size overflows β different code paths.
|
| 246 |
+
- The `Buffer.offset`/`Buffer.size` fields are for large model support (>2GB), a relatively newer feature.
|
| 247 |
+
- Dup risk: low. This specific bounds check pattern has not been reported.
|
| 248 |
+
|
| 249 |
+
## Suggested CVSS
|
| 250 |
+
|
| 251 |
+
AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:H β **7.1** (local file, user loads model; OOB read β information disclosure + crash). Note: OOB READ not WRITE β exploitability for code execution is lower, but information disclosure through model inference outputs is possible.
|