Skip to content

security/fuzzing: add TFLite model load + interpreter invoke fuzz target - #123123

Merged
copybara-service[bot] merged 23 commits into
tensorflow:masterfrom
cybrdude:fuzz/tflite-interp-overflow-target
Oct 15, 2026
Merged

copybara-service[bot] merged 23 commits into
tensorflow:masterfrom
cybrdude:fuzz/tflite-interp-overflow-target

Conversation

@cybrdude

@cybrdude cybrdude commented Jul 13, 2026 •

Copy link
Copy Markdown
Contributor

What

Adds a FuzzTest target under tensorflow/security/fuzzing/cc/ that loads a serialized .tflite model, builds an interpreter with the builtin op resolver, allocates tensors, and invokes. The OSS-Fuzz tensorflow build currently has no target that reaches TFLite model load / interpreter build / invoke, so this path is unfuzzed upstream.

Why

FlatBufferModel -> InterpreterBuilder -> AllocateTensors -> Invoke parses attacker-supplied model structure, including external-buffer descriptors handled in interpreter_builder.cc (get_readonly_data / ParseTensors). That path performs a uint64 offset + size addition and compares it against allocation_->bytes(). Adding coverage here lets OSS-Fuzz continuously exercise external-offset handling, tensor wiring, and builtin-kernel eval on malformed models, which is a class of input the existing core fuzzers do not reach.

Change

tensorflow/lite/core/interpreter_builder.cc: the two external-offset bounds checks (ParseNodes, large_custom_options; ParseTensors, Buffer offset) use the subtraction form offset > bytes || size > bytes - offset instead of offset + size > bytes, so the uint64 sum can no longer wrap below allocation_->bytes(). The offset, size and allocation size are read once into locals. Requested in review (2026-09-16, 2026-10-03).

One new file plus a tf_cc_fuzz_test entry in security/fuzzing/cc/BUILD:

tf_cc_fuzz_test(
    name = "tflite_interp_overflow_fuzz",
    srcs = ["tflite_interp_overflow_fuzz.cc"],
    deps = [
        "//tensorflow/lite:framework",
        "//tensorflow/lite/c:common",
        "//tensorflow/lite/kernels:builtin_ops",
    ],
)

The target uses only the public //tensorflow/lite:framework, //tensorflow/lite/c:common and //tensorflow/lite/kernels:builtin_ops, so no visibility changes are needed. The BUILD already loads tf_cc_fuzz_test (sibling targets use it). The harness returns early on any non-OK status so benign malformed inputs are not treated as findings; only a genuine memory-safety violation surfaces as a crash.

Notes for review

  • Dep closure is the public framework + builtin ops; if CI's linker wants a narrower or additional core target for the tensorflow/lite/core/* headers, I will adjust to match.
  • Happy to add a seed corpus entry in a follow-up if the fuzzing infra owners prefer a seeded start.

The OSS-Fuzz tensorflow build has no fuzz target that reaches TFLite model load, interpreter build, and invoke. This adds one under security/fuzzing/cc, exercising interpreter_builder.cc external-buffer handling (get_readonly_data / ParseTensors), where an unchecked uint64 offset+size addition can bypass the allocation bounds check and set a wild read-only tensor pointer that is dereferenced during Invoke. Deps use the public //tensorflow/lite:framework and //tensorflow/lite/kernels:builtin_ops.
@google-ml-butler google-ml-butler Bot added the size:M CL Change Size: Medium label Jul 13, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds a new fuzz test target, tflite_interp_overflow_fuzz, to test TFLite model loading, interpreter building, and invocation for potential integer overflow vulnerabilities. The review feedback recommends including <cstring> and zero-initializing the input tensors after allocation to prevent MemorySanitizer (MSan) false-positive errors during fuzzing.

Comment thread tensorflow/security/fuzzing/cc/tflite_interp_overflow_fuzz.cc
Comment thread tensorflow/security/fuzzing/cc/tflite_interp_overflow_fuzz.cc
cybrdude and others added 2 commits July 13, 2026 12:35
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…se positives

Addresses gemini-code-assist review. TFLite's arena allocator does not zero tensor memory, so kernels reading uninitialized runtime inputs raise MemorySanitizer use-of-uninitialized-value reports. Only runtime inputs are zeroed, not the crafted constant buffer that carries the finding.
@keerthanakadiri
keerthanakadiri requested a review from a team July 14, 2026 06:28
@google-ml-butler google-ml-butler Bot added the awaiting review Pull request awaiting review label Jul 14, 2026
@github-project-automation github-project-automation Bot moved this to Assigned Reviewer in PR Queue Jul 14, 2026

@ratgr ratgr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution! The addition of the TFLite interpreter build fuzz target is a great addition to the fuzzing suite, and zeroing runtime input tensors to prevent MSan false positives looks clean and complete.

@google-ml-butler google-ml-butler Bot added kokoro:force-run Tests on submitted change ready to pull PR ready for merge process labels Jul 22, 2026
@github-project-automation github-project-automation Bot moved this from Assigned Reviewer to Approved by Reviewer in PR Queue Jul 22, 2026
@kokoro-team kokoro-team removed the kokoro:force-run Tests on submitted change label Jul 22, 2026
@nithyak0204 nithyak0204 removed the awaiting review Pull request awaiting review label Jul 23, 2026

@ratgr ratgr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @cybrdude , thank you for this PR!

The build is failing because the new fuzz target is including internal implementation headers directly from the tensorflow/lite/core/ subdirectory.

Please update the includes in tflite_interp_overflow_fuzz.cc to use the public forwarding headers under tensorflow/lite/ instead:

#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/interpreter_builder.h"
#include "tensorflow/lite/model_builder.h"

The build system requires consumers of the public framework to use these top-level headers rather than reaching into the core/ internal package. The existing dependencies you specified are correct for these public headers.

@github-project-automation github-project-automation Bot moved this from Approved by Reviewer to Reviewer Requested Changes in PR Queue Aug 25, 2026
@nithyak0204 nithyak0204 added stat:awaiting response Status - Awaiting response from author and removed ready to pull PR ready for merge process labels Aug 26, 2026
@dmiltr3
dmiltr3 self-requested a review September 1, 2026 17:36
@google-ml-butler google-ml-butler Bot added the awaiting review Pull request awaiting review label Sep 1, 2026

@dmiltr3 dmiltr3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update the header includes in tensorflow/security/fuzzing/cc/tflite_interp_overflow_fuzz.cc to use the public TFLite API header paths exported by //tensorflow/lite:framework:

#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/interpreter_builder.h"
#include "tensorflow/lite/kernels/register.h"
#include "tensorflow/lite/model_builder.h"

Including the internal tensorflow/lite/core/* paths directly causes dependency layering check failures when building against //tensorflow/lite:framework. Updating to the public headers resolves the build failure.

Note: The CI/build-windows-x86 failure in GitHub Actions is due to an infrastructure runner issue and is not caused by your changes.

@nithyak0204 nithyak0204 added stat:awaiting response Status - Awaiting response from author and removed stat:awaiting response Status - Awaiting response from author awaiting review Pull request awaiting review labels Sep 10, 2026
@cybrdude

Copy link
Copy Markdown
Contributor Author

Both review items are addressed in the pushed commits. Flagging it since the PR is still labeled stat:awaiting response and I do not think anything is actually waiting on me now.

  1. cstring is included for std::memset.
  2. The input tensors are zero-initialized after AllocateTensors and before Invoke.

On the second one, to be explicit about what is and is not zeroed: only the runtime input tensors are cleared. The fuzzer-supplied model buffer is passed through untouched, because that is the attacker-controlled data carrying the condition this target exists to reach. Zeroing it would defeat the harness.

The reason it matters is that TFLite's default allocator does not zero the arena, so invoking on uninitialized input buffers produces MemorySanitizer use-of-uninitialized-value reports unrelated to the bug being hunted. A target that trips a sanitizer on benign input buries real findings in noise.

Could a maintainer re-review, or flip the label if something is still outstanding on my side? Happy to make further changes.

@google-ml-butler google-ml-butler Bot removed the stat:awaiting response Status - Awaiting response from author label Sep 16, 2026
@cybrdude
cybrdude requested a review from ratgr September 16, 2026 03:38
@google-ml-butler google-ml-butler Bot added the awaiting review Pull request awaiting review label Sep 16, 2026
copybara-service Bot pushed a commit to google-ai-edge/LiteRT that referenced this pull request Oct 4, 2026
…oke fuzz target

Imported from GitHub PR tensorflow/tensorflow#123123

### What
Adds a FuzzTest target under `tensorflow/security/fuzzing/cc/` that loads a serialized `.tflite` model, builds an interpreter with the builtin op resolver, allocates tensors, and invokes. The OSS-Fuzz `tensorflow` build currently has no target that reaches TFLite model load / interpreter build / invoke, so this path is unfuzzed upstream.

### Why
`FlatBufferModel` -> `InterpreterBuilder` -> `AllocateTensors` -> `Invoke` parses attacker-supplied model structure, including external-buffer descriptors handled in `interpreter_builder.cc` (`get_readonly_data` / `ParseTensors`). That path performs a `uint64` `offset + size` addition and compares it against `allocation_->bytes()`. Adding coverage here lets OSS-Fuzz continuously exercise external-offset handling, tensor wiring, and builtin-kernel eval on malformed models, which is a class of input the existing core fuzzers do not reach.

### Change
`tensorflow/lite/core/interpreter_builder.cc`: the two external-offset bounds checks (`ParseNodes`, large_custom_options; `ParseTensors`, Buffer offset) use the subtraction form `offset > bytes || size > bytes - offset` instead of `offset + size > bytes`, so the uint64 sum can no longer wrap below `allocation_->bytes()`. The offset, size and allocation size are read once into locals. Requested in review (2026-09-16, 2026-10-03).

One new file plus a `tf_cc_fuzz_test` entry in `security/fuzzing/cc/BUILD`:

```python
tf_cc_fuzz_test(
    name = "tflite_interp_overflow_fuzz",
    srcs = ["tflite_interp_overflow_fuzz.cc"],
    deps = [
        "//tensorflow/lite:framework",
        "//tensorflow/lite/c:common",
        "//tensorflow/lite/kernels:builtin_ops",
    ],
)
```

The target uses only the public `//tensorflow/lite:framework`, `//tensorflow/lite/c:common` and `//tensorflow/lite/kernels:builtin_ops`, so no visibility changes are needed. The BUILD already loads `tf_cc_fuzz_test` (sibling targets use it). The harness returns early on any non-OK status so benign malformed inputs are not treated as findings; only a genuine memory-safety violation surfaces as a crash.

### Notes for review
- Dep closure is the public framework + builtin ops; if CI's linker wants a narrower or additional core target for the `tensorflow/lite/core/*` headers, I will adjust to match.
- Happy to add a seed corpus entry in a follow-up if the fuzzing infra owners prefer a seeded start.
Copybara import of the project:

--
653c77178c4839ca340b7179aae7b3222f708698 by cybrdude <layau@netguard24-7.com>:

Add TFLite interpreter-build fuzz target (model load + invoke)

The OSS-Fuzz tensorflow build has no fuzz target that reaches TFLite model load, interpreter build, and invoke. This adds one under security/fuzzing/cc, exercising interpreter_builder.cc external-buffer handling (get_readonly_data / ParseTensors), where an unchecked uint64 offset+size addition can bypass the allocation bounds check and set a wild read-only tensor pointer that is dereferenced during Invoke. Deps use the public //tensorflow/lite:framework and //tensorflow/lite/kernels:builtin_ops.

--
3eb7b68f504251c397ee07e253798d545d8027fc by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

Update tensorflow/security/fuzzing/cc/tflite_interp_overflow_fuzz.cc

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
--
141f0b41e740c9e29e7696597a5155a183b760cc by cybrdude <layau@netguard24-7.com>:

fuzz: zero-init runtime input tensors before Invoke to avoid MSan false positives

Addresses gemini-code-assist review. TFLite's arena allocator does not zero tensor memory, so kernels reading uninitialized runtime inputs raise MemorySanitizer use-of-uninitialized-value reports. Only runtime inputs are zeroed, not the crafted constant buffer that carries the finding.

--
75365a8d636f7ec401b59abd3575c5bef90b6814 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

fuzz(tflite): use public TFLite headers instead of core internal paths

Switches the three lite/core includes to the public forwarding headers exported by //tensorflow/lite:framework, per review. Including the core internal package directly caused the dependency layering check to fail. Include block re-sorted. No functional change to the target.
--
1c832e34eabd2b20398be3b12cf879b1fb8656f7 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: verify the model before building it in the TFLite fuzz target

Puts the harness on the supported ingress path for untrusted models: call
tflite::Verify() first and stop on rejection, then build and invoke. The
verifier owns the bounds checks on model metadata (tensorflow/tensorflow
#116631 review, Sep 18; checks landed in #123910), so inputs like the
uint64 external-offset wrap are rejected before ParseTensors, and the
verifier itself gets fuzz coverage. No change to interpreter_builder.cc.

VerifyAndBuildFromBuffer alone would not do this: without an extra_verifier
it runs only the flatbuffers structural check.
--
0111afab059556333fd8514eb5a833007636db66 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: depend on //tensorflow/lite/tools:verifier for the TFLite fuzz target

The target now calls tflite::Verify() before building the model.
--
f1ef1e37f86accdc21240fa43881db9d9019f633 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: do not zero kTfLiteMmapRo inputs in the TFLite fuzz target

A crafted model can declare a subgraph input backed by a constant buffer;
that tensor is kTfLiteMmapRo and points into the read-only model mapping,
so the MSan zero-fill would fault on it. Skip those; they are initialized
by the model already. Per review.
--
d481802c00127f44d8607a4841a620972fbb6e45 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: include <vector> and tensorflow/lite/c/common.h directly

The target uses TfLiteTensor, kTfLiteMmapRo, kTfLiteOk and std::vector;
include their headers instead of relying on transitive includes. Per review.
--
ae5b26db83123e460eb2658796ebce1601b9586e by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: depend on //tensorflow/lite/c:common for the TFLite fuzz target

Matches the direct include of tensorflow/lite/c/common.h. Per review.
--
72272479b6209adccbc3f7ba7660af22cb607769 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop the tflite::Verify() gate from the TFLite fuzz target

Per the Oct 3 review: malformed models go straight to InterpreterBuilder
so its own handling of model metadata is what gets fuzzed. Removes the
Verify() early return and its include; keeps the direct includes and the
kTfLiteMmapRo exclusion from the earlier rounds.
--
fd2f77c447ec35203d6a6db8c0e672ba68033507 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop the verifier dep from the TFLite fuzz target

The target no longer calls tflite::Verify(). Per the Oct 3 review.
--
db0ba16e36ff663983f49a9e5a7b544d227dd84b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

tflite: overflow-safe external-offset bounds checks in InterpreterBuilder

Both sites compared offset + size against allocation_->bytes() with two
uint64_t operands; a pair whose sum wraps passed the check and the pointer
computed from base + offset wrapped with it. Compare in the subtraction
form instead, so the check cannot overflow. Same predicate for every valid
(offset, size); only the wrapping pairs change outcome.

Requested on this PR on Sep 16 and again on Oct 3 so the fuzz target in
the same change does not report a known crash. Re-applies the change from
#116631 that was reverted on Sep 18; the verifier-side check from #123910
stays, so both layers reject the wrap.
--
72567388959f68b271686cd87b6721845457bdee by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

tflite: restore master ParseQuantization dropped by the previous commit

db0ba16 was applied to a stale copy of interpreter_builder.cc and removed
56 lines of ParseQuantization that master had added. This puts the file
back to master plus only the two bounds-check hunks.
--
c624d970b2667b4e96aa54579552600a8ed38e44 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: enter through VerifyAndBuildFromBuffer in the TFLite fuzz target

Per review: models now pass the flatbuffers structural verifier before
InterpreterBuilder sees them. No extra_verifier, so tflite::Verify() is not
on this path; the external-offset bounds checks in interpreter_builder.cc
remain the check exercised for wrapped offsets.
--
6b637521dbde57526747589a95a68d9e0bcba52d by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: cache offset, size and allocation bytes in the external-offset checks

Per review: hoist op->large_custom_options_offset(), the option size and
allocation_->bytes() into locals in ParseNodes, and allocation_->bytes() and
buffer->size() in ParseTensors, so the checks do not repeat the flatbuffer
accessors and the virtual call. No behaviour change.
--
267042d6b57f914b92baca79a17dcb2b5bc4ca63 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop unused <vector> include from the TFLite fuzz target

Per review (IWYU): the file never names std::vector.
--
e9612f26a1f95d898257ad3c0644fc31e8edeb1b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: restore <vector> include in the TFLite fuzz target

Per the corrected review: interpreter->inputs() returns const std::vector<int>&
and the range-for over it is a use, so IWYU wants the header named here.
--
0193d170dba8cca45f7189170265a85453fe324e by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: regression tests for wrapping external offsets in InterpreterBuilder

Per review: two deterministic tests in model_test.cc. Each builds a model whose
(offset, size) pair sums past uint64 to 9, loads it with BuildFromBuffer so the
verifier is not in the way, and expects InterpreterBuilder to reject it. One
covers the Buffer offset path in ParseTensors (tensor bytes equal the claimed
size so the size check cannot mask the result), the other the
large_custom_options path in ParseNodes.
--
2c10c04377c30d05add06b08c3c1371bfd94f54b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: mark the test registration CUSTOM so the ParseNodes case reaches the check

Per review: ParseNodes derives the op type from registration->builtin_code,
and dummy_reg carries 0 (ADD), so the large_custom_options branch never ran
and the test passed vacuously. The test now resolves to a copy of dummy_reg
marked BuiltinOperator_CUSTOM with custom_name WRAP.

Merging this change closes #123123

FUTURE_COPYBARA_INTEGRATE_REVIEW=tensorflow/tensorflow#123123 from cybrdude:fuzz/tflite-interp-overflow-target 2c10c04377c30d05add06b08c3c1371bfd94f54b
PiperOrigin-RevId: 992803740
@nithyak0204 nithyak0204 removed awaiting review Pull request awaiting review kokoro:force-run Tests on submitted change labels Oct 5, 2026
copybara-service Bot pushed a commit to google-ai-edge/LiteRT that referenced this pull request Oct 5, 2026
…oke fuzz target

Imported from GitHub PR tensorflow/tensorflow#123123

### What
Adds a FuzzTest target under `tensorflow/security/fuzzing/cc/` that loads a serialized `.tflite` model, builds an interpreter with the builtin op resolver, allocates tensors, and invokes. The OSS-Fuzz `tensorflow` build currently has no target that reaches TFLite model load / interpreter build / invoke, so this path is unfuzzed upstream.

### Why
`FlatBufferModel` -> `InterpreterBuilder` -> `AllocateTensors` -> `Invoke` parses attacker-supplied model structure, including external-buffer descriptors handled in `interpreter_builder.cc` (`get_readonly_data` / `ParseTensors`). That path performs a `uint64` `offset + size` addition and compares it against `allocation_->bytes()`. Adding coverage here lets OSS-Fuzz continuously exercise external-offset handling, tensor wiring, and builtin-kernel eval on malformed models, which is a class of input the existing core fuzzers do not reach.

### Change
`tensorflow/lite/core/interpreter_builder.cc`: the two external-offset bounds checks (`ParseNodes`, large_custom_options; `ParseTensors`, Buffer offset) use the subtraction form `offset > bytes || size > bytes - offset` instead of `offset + size > bytes`, so the uint64 sum can no longer wrap below `allocation_->bytes()`. The offset, size and allocation size are read once into locals. Requested in review (2026-09-16, 2026-10-03).

One new file plus a `tf_cc_fuzz_test` entry in `security/fuzzing/cc/BUILD`:

```python
tf_cc_fuzz_test(
    name = "tflite_interp_overflow_fuzz",
    srcs = ["tflite_interp_overflow_fuzz.cc"],
    deps = [
        "//tensorflow/lite:framework",
        "//tensorflow/lite/c:common",
        "//tensorflow/lite/kernels:builtin_ops",
    ],
)
```

The target uses only the public `//tensorflow/lite:framework`, `//tensorflow/lite/c:common` and `//tensorflow/lite/kernels:builtin_ops`, so no visibility changes are needed. The BUILD already loads `tf_cc_fuzz_test` (sibling targets use it). The harness returns early on any non-OK status so benign malformed inputs are not treated as findings; only a genuine memory-safety violation surfaces as a crash.

### Notes for review
- Dep closure is the public framework + builtin ops; if CI's linker wants a narrower or additional core target for the `tensorflow/lite/core/*` headers, I will adjust to match.
- Happy to add a seed corpus entry in a follow-up if the fuzzing infra owners prefer a seeded start.
Copybara import of the project:

--
653c77178c4839ca340b7179aae7b3222f708698 by cybrdude <layau@netguard24-7.com>:

Add TFLite interpreter-build fuzz target (model load + invoke)

The OSS-Fuzz tensorflow build has no fuzz target that reaches TFLite model load, interpreter build, and invoke. This adds one under security/fuzzing/cc, exercising interpreter_builder.cc external-buffer handling (get_readonly_data / ParseTensors), where an unchecked uint64 offset+size addition can bypass the allocation bounds check and set a wild read-only tensor pointer that is dereferenced during Invoke. Deps use the public //tensorflow/lite:framework and //tensorflow/lite/kernels:builtin_ops.

--
3eb7b68f504251c397ee07e253798d545d8027fc by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

Update tensorflow/security/fuzzing/cc/tflite_interp_overflow_fuzz.cc

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
--
141f0b41e740c9e29e7696597a5155a183b760cc by cybrdude <layau@netguard24-7.com>:

fuzz: zero-init runtime input tensors before Invoke to avoid MSan false positives

Addresses gemini-code-assist review. TFLite's arena allocator does not zero tensor memory, so kernels reading uninitialized runtime inputs raise MemorySanitizer use-of-uninitialized-value reports. Only runtime inputs are zeroed, not the crafted constant buffer that carries the finding.

--
75365a8d636f7ec401b59abd3575c5bef90b6814 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

fuzz(tflite): use public TFLite headers instead of core internal paths

Switches the three lite/core includes to the public forwarding headers exported by //tensorflow/lite:framework, per review. Including the core internal package directly caused the dependency layering check to fail. Include block re-sorted. No functional change to the target.
--
1c832e34eabd2b20398be3b12cf879b1fb8656f7 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: verify the model before building it in the TFLite fuzz target

Puts the harness on the supported ingress path for untrusted models: call
tflite::Verify() first and stop on rejection, then build and invoke. The
verifier owns the bounds checks on model metadata (tensorflow/tensorflow
#116631 review, Sep 18; checks landed in #123910), so inputs like the
uint64 external-offset wrap are rejected before ParseTensors, and the
verifier itself gets fuzz coverage. No change to interpreter_builder.cc.

VerifyAndBuildFromBuffer alone would not do this: without an extra_verifier
it runs only the flatbuffers structural check.
--
0111afab059556333fd8514eb5a833007636db66 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: depend on //tensorflow/lite/tools:verifier for the TFLite fuzz target

The target now calls tflite::Verify() before building the model.
--
f1ef1e37f86accdc21240fa43881db9d9019f633 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: do not zero kTfLiteMmapRo inputs in the TFLite fuzz target

A crafted model can declare a subgraph input backed by a constant buffer;
that tensor is kTfLiteMmapRo and points into the read-only model mapping,
so the MSan zero-fill would fault on it. Skip those; they are initialized
by the model already. Per review.
--
d481802c00127f44d8607a4841a620972fbb6e45 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: include <vector> and tensorflow/lite/c/common.h directly

The target uses TfLiteTensor, kTfLiteMmapRo, kTfLiteOk and std::vector;
include their headers instead of relying on transitive includes. Per review.
--
ae5b26db83123e460eb2658796ebce1601b9586e by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: depend on //tensorflow/lite/c:common for the TFLite fuzz target

Matches the direct include of tensorflow/lite/c/common.h. Per review.
--
72272479b6209adccbc3f7ba7660af22cb607769 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop the tflite::Verify() gate from the TFLite fuzz target

Per the Oct 3 review: malformed models go straight to InterpreterBuilder
so its own handling of model metadata is what gets fuzzed. Removes the
Verify() early return and its include; keeps the direct includes and the
kTfLiteMmapRo exclusion from the earlier rounds.
--
fd2f77c447ec35203d6a6db8c0e672ba68033507 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop the verifier dep from the TFLite fuzz target

The target no longer calls tflite::Verify(). Per the Oct 3 review.
--
db0ba16e36ff663983f49a9e5a7b544d227dd84b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

tflite: overflow-safe external-offset bounds checks in InterpreterBuilder

Both sites compared offset + size against allocation_->bytes() with two
uint64_t operands; a pair whose sum wraps passed the check and the pointer
computed from base + offset wrapped with it. Compare in the subtraction
form instead, so the check cannot overflow. Same predicate for every valid
(offset, size); only the wrapping pairs change outcome.

Requested on this PR on Sep 16 and again on Oct 3 so the fuzz target in
the same change does not report a known crash. Re-applies the change from
#116631 that was reverted on Sep 18; the verifier-side check from #123910
stays, so both layers reject the wrap.
--
72567388959f68b271686cd87b6721845457bdee by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

tflite: restore master ParseQuantization dropped by the previous commit

db0ba16 was applied to a stale copy of interpreter_builder.cc and removed
56 lines of ParseQuantization that master had added. This puts the file
back to master plus only the two bounds-check hunks.
--
c624d970b2667b4e96aa54579552600a8ed38e44 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: enter through VerifyAndBuildFromBuffer in the TFLite fuzz target

Per review: models now pass the flatbuffers structural verifier before
InterpreterBuilder sees them. No extra_verifier, so tflite::Verify() is not
on this path; the external-offset bounds checks in interpreter_builder.cc
remain the check exercised for wrapped offsets.
--
6b637521dbde57526747589a95a68d9e0bcba52d by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: cache offset, size and allocation bytes in the external-offset checks

Per review: hoist op->large_custom_options_offset(), the option size and
allocation_->bytes() into locals in ParseNodes, and allocation_->bytes() and
buffer->size() in ParseTensors, so the checks do not repeat the flatbuffer
accessors and the virtual call. No behaviour change.
--
267042d6b57f914b92baca79a17dcb2b5bc4ca63 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop unused <vector> include from the TFLite fuzz target

Per review (IWYU): the file never names std::vector.
--
e9612f26a1f95d898257ad3c0644fc31e8edeb1b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: restore <vector> include in the TFLite fuzz target

Per the corrected review: interpreter->inputs() returns const std::vector<int>&
and the range-for over it is a use, so IWYU wants the header named here.
--
0193d170dba8cca45f7189170265a85453fe324e by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: regression tests for wrapping external offsets in InterpreterBuilder

Per review: two deterministic tests in model_test.cc. Each builds a model whose
(offset, size) pair sums past uint64 to 9, loads it with BuildFromBuffer so the
verifier is not in the way, and expects InterpreterBuilder to reject it. One
covers the Buffer offset path in ParseTensors (tensor bytes equal the claimed
size so the size check cannot mask the result), the other the
large_custom_options path in ParseNodes.
--
2c10c04377c30d05add06b08c3c1371bfd94f54b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: mark the test registration CUSTOM so the ParseNodes case reaches the check

Per review: ParseNodes derives the op type from registration->builtin_code,
and dummy_reg carries 0 (ADD), so the large_custom_options branch never ran
and the test passed vacuously. The test now resolves to a copy of dummy_reg
marked BuiltinOperator_CUSTOM with custom_name WRAP.

Merging this change closes #123123

FUTURE_COPYBARA_INTEGRATE_REVIEW=tensorflow/tensorflow#123123 from cybrdude:fuzz/tflite-interp-overflow-target 2c10c04377c30d05add06b08c3c1371bfd94f54b
PiperOrigin-RevId: 992803740
copybara-service Bot pushed a commit to google-ai-edge/LiteRT that referenced this pull request Oct 6, 2026
…oke fuzz target

Imported from GitHub PR tensorflow/tensorflow#123123

### What
Adds a FuzzTest target under `tensorflow/security/fuzzing/cc/` that loads a serialized `.tflite` model, builds an interpreter with the builtin op resolver, allocates tensors, and invokes. The OSS-Fuzz `tensorflow` build currently has no target that reaches TFLite model load / interpreter build / invoke, so this path is unfuzzed upstream.

### Why
`FlatBufferModel` -> `InterpreterBuilder` -> `AllocateTensors` -> `Invoke` parses attacker-supplied model structure, including external-buffer descriptors handled in `interpreter_builder.cc` (`get_readonly_data` / `ParseTensors`). That path performs a `uint64` `offset + size` addition and compares it against `allocation_->bytes()`. Adding coverage here lets OSS-Fuzz continuously exercise external-offset handling, tensor wiring, and builtin-kernel eval on malformed models, which is a class of input the existing core fuzzers do not reach.

### Change
`tensorflow/lite/core/interpreter_builder.cc`: the two external-offset bounds checks (`ParseNodes`, large_custom_options; `ParseTensors`, Buffer offset) use the subtraction form `offset > bytes || size > bytes - offset` instead of `offset + size > bytes`, so the uint64 sum can no longer wrap below `allocation_->bytes()`. The offset, size and allocation size are read once into locals. Requested in review (2026-09-16, 2026-10-03).

One new file plus a `tf_cc_fuzz_test` entry in `security/fuzzing/cc/BUILD`:

```python
tf_cc_fuzz_test(
    name = "tflite_interp_overflow_fuzz",
    srcs = ["tflite_interp_overflow_fuzz.cc"],
    deps = [
        "//tensorflow/lite:framework",
        "//tensorflow/lite/c:common",
        "//tensorflow/lite/kernels:builtin_ops",
    ],
)
```

The target uses only the public `//tensorflow/lite:framework`, `//tensorflow/lite/c:common` and `//tensorflow/lite/kernels:builtin_ops`, so no visibility changes are needed. The BUILD already loads `tf_cc_fuzz_test` (sibling targets use it). The harness returns early on any non-OK status so benign malformed inputs are not treated as findings; only a genuine memory-safety violation surfaces as a crash.

### Notes for review
- Dep closure is the public framework + builtin ops; if CI's linker wants a narrower or additional core target for the `tensorflow/lite/core/*` headers, I will adjust to match.
- Happy to add a seed corpus entry in a follow-up if the fuzzing infra owners prefer a seeded start.
Copybara import of the project:

--
653c77178c4839ca340b7179aae7b3222f708698 by cybrdude <layau@netguard24-7.com>:

Add TFLite interpreter-build fuzz target (model load + invoke)

The OSS-Fuzz tensorflow build has no fuzz target that reaches TFLite model load, interpreter build, and invoke. This adds one under security/fuzzing/cc, exercising interpreter_builder.cc external-buffer handling (get_readonly_data / ParseTensors), where an unchecked uint64 offset+size addition can bypass the allocation bounds check and set a wild read-only tensor pointer that is dereferenced during Invoke. Deps use the public //tensorflow/lite:framework and //tensorflow/lite/kernels:builtin_ops.

--
3eb7b68f504251c397ee07e253798d545d8027fc by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

Update tensorflow/security/fuzzing/cc/tflite_interp_overflow_fuzz.cc

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
--
141f0b41e740c9e29e7696597a5155a183b760cc by cybrdude <layau@netguard24-7.com>:

fuzz: zero-init runtime input tensors before Invoke to avoid MSan false positives

Addresses gemini-code-assist review. TFLite's arena allocator does not zero tensor memory, so kernels reading uninitialized runtime inputs raise MemorySanitizer use-of-uninitialized-value reports. Only runtime inputs are zeroed, not the crafted constant buffer that carries the finding.

--
75365a8d636f7ec401b59abd3575c5bef90b6814 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

fuzz(tflite): use public TFLite headers instead of core internal paths

Switches the three lite/core includes to the public forwarding headers exported by //tensorflow/lite:framework, per review. Including the core internal package directly caused the dependency layering check to fail. Include block re-sorted. No functional change to the target.
--
1c832e34eabd2b20398be3b12cf879b1fb8656f7 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: verify the model before building it in the TFLite fuzz target

Puts the harness on the supported ingress path for untrusted models: call
tflite::Verify() first and stop on rejection, then build and invoke. The
verifier owns the bounds checks on model metadata (tensorflow/tensorflow
#116631 review, Sep 18; checks landed in #123910), so inputs like the
uint64 external-offset wrap are rejected before ParseTensors, and the
verifier itself gets fuzz coverage. No change to interpreter_builder.cc.

VerifyAndBuildFromBuffer alone would not do this: without an extra_verifier
it runs only the flatbuffers structural check.
--
0111afab059556333fd8514eb5a833007636db66 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: depend on //tensorflow/lite/tools:verifier for the TFLite fuzz target

The target now calls tflite::Verify() before building the model.
--
f1ef1e37f86accdc21240fa43881db9d9019f633 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: do not zero kTfLiteMmapRo inputs in the TFLite fuzz target

A crafted model can declare a subgraph input backed by a constant buffer;
that tensor is kTfLiteMmapRo and points into the read-only model mapping,
so the MSan zero-fill would fault on it. Skip those; they are initialized
by the model already. Per review.
--
d481802c00127f44d8607a4841a620972fbb6e45 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: include <vector> and tensorflow/lite/c/common.h directly

The target uses TfLiteTensor, kTfLiteMmapRo, kTfLiteOk and std::vector;
include their headers instead of relying on transitive includes. Per review.
--
ae5b26db83123e460eb2658796ebce1601b9586e by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: depend on //tensorflow/lite/c:common for the TFLite fuzz target

Matches the direct include of tensorflow/lite/c/common.h. Per review.
--
72272479b6209adccbc3f7ba7660af22cb607769 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop the tflite::Verify() gate from the TFLite fuzz target

Per the Oct 3 review: malformed models go straight to InterpreterBuilder
so its own handling of model metadata is what gets fuzzed. Removes the
Verify() early return and its include; keeps the direct includes and the
kTfLiteMmapRo exclusion from the earlier rounds.
--
fd2f77c447ec35203d6a6db8c0e672ba68033507 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop the verifier dep from the TFLite fuzz target

The target no longer calls tflite::Verify(). Per the Oct 3 review.
--
db0ba16e36ff663983f49a9e5a7b544d227dd84b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

tflite: overflow-safe external-offset bounds checks in InterpreterBuilder

Both sites compared offset + size against allocation_->bytes() with two
uint64_t operands; a pair whose sum wraps passed the check and the pointer
computed from base + offset wrapped with it. Compare in the subtraction
form instead, so the check cannot overflow. Same predicate for every valid
(offset, size); only the wrapping pairs change outcome.

Requested on this PR on Sep 16 and again on Oct 3 so the fuzz target in
the same change does not report a known crash. Re-applies the change from
#116631 that was reverted on Sep 18; the verifier-side check from #123910
stays, so both layers reject the wrap.
--
72567388959f68b271686cd87b6721845457bdee by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

tflite: restore master ParseQuantization dropped by the previous commit

db0ba16 was applied to a stale copy of interpreter_builder.cc and removed
56 lines of ParseQuantization that master had added. This puts the file
back to master plus only the two bounds-check hunks.
--
c624d970b2667b4e96aa54579552600a8ed38e44 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: enter through VerifyAndBuildFromBuffer in the TFLite fuzz target

Per review: models now pass the flatbuffers structural verifier before
InterpreterBuilder sees them. No extra_verifier, so tflite::Verify() is not
on this path; the external-offset bounds checks in interpreter_builder.cc
remain the check exercised for wrapped offsets.
--
6b637521dbde57526747589a95a68d9e0bcba52d by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: cache offset, size and allocation bytes in the external-offset checks

Per review: hoist op->large_custom_options_offset(), the option size and
allocation_->bytes() into locals in ParseNodes, and allocation_->bytes() and
buffer->size() in ParseTensors, so the checks do not repeat the flatbuffer
accessors and the virtual call. No behaviour change.
--
267042d6b57f914b92baca79a17dcb2b5bc4ca63 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop unused <vector> include from the TFLite fuzz target

Per review (IWYU): the file never names std::vector.
--
e9612f26a1f95d898257ad3c0644fc31e8edeb1b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: restore <vector> include in the TFLite fuzz target

Per the corrected review: interpreter->inputs() returns const std::vector<int>&
and the range-for over it is a use, so IWYU wants the header named here.
--
0193d170dba8cca45f7189170265a85453fe324e by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: regression tests for wrapping external offsets in InterpreterBuilder

Per review: two deterministic tests in model_test.cc. Each builds a model whose
(offset, size) pair sums past uint64 to 9, loads it with BuildFromBuffer so the
verifier is not in the way, and expects InterpreterBuilder to reject it. One
covers the Buffer offset path in ParseTensors (tensor bytes equal the claimed
size so the size check cannot mask the result), the other the
large_custom_options path in ParseNodes.
--
2c10c04377c30d05add06b08c3c1371bfd94f54b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: mark the test registration CUSTOM so the ParseNodes case reaches the check

Per review: ParseNodes derives the op type from registration->builtin_code,
and dummy_reg carries 0 (ADD), so the large_custom_options branch never ran
and the test passed vacuously. The test now resolves to a copy of dummy_reg
marked BuiltinOperator_CUSTOM with custom_name WRAP.

Merging this change closes #123123

FUTURE_COPYBARA_INTEGRATE_REVIEW=tensorflow/tensorflow#123123 from cybrdude:fuzz/tflite-interp-overflow-target 2c10c04377c30d05add06b08c3c1371bfd94f54b
PiperOrigin-RevId: 992803740
@dmiltr3 dmiltr3 added ready to pull PR ready for merge process and removed ready to pull PR ready for merge process labels Oct 8, 2026
copybara-service Bot pushed a commit to google-ai-edge/LiteRT that referenced this pull request Oct 8, 2026
…oke fuzz target

Imported from GitHub PR tensorflow/tensorflow#123123

### What
Adds a FuzzTest target under `tensorflow/security/fuzzing/cc/` that loads a serialized `.tflite` model, builds an interpreter with the builtin op resolver, allocates tensors, and invokes. The OSS-Fuzz `tensorflow` build currently has no target that reaches TFLite model load / interpreter build / invoke, so this path is unfuzzed upstream.

### Why
`FlatBufferModel` -> `InterpreterBuilder` -> `AllocateTensors` -> `Invoke` parses attacker-supplied model structure, including external-buffer descriptors handled in `interpreter_builder.cc` (`get_readonly_data` / `ParseTensors`). That path performs a `uint64` `offset + size` addition and compares it against `allocation_->bytes()`. Adding coverage here lets OSS-Fuzz continuously exercise external-offset handling, tensor wiring, and builtin-kernel eval on malformed models, which is a class of input the existing core fuzzers do not reach.

### Change
`tensorflow/lite/core/interpreter_builder.cc`: the two external-offset bounds checks (`ParseNodes`, large_custom_options; `ParseTensors`, Buffer offset) use the subtraction form `offset > bytes || size > bytes - offset` instead of `offset + size > bytes`, so the uint64 sum can no longer wrap below `allocation_->bytes()`. The offset, size and allocation size are read once into locals. Requested in review (2026-09-16, 2026-10-03).

One new file plus a `tf_cc_fuzz_test` entry in `security/fuzzing/cc/BUILD`:

```python
tf_cc_fuzz_test(
    name = "tflite_interp_overflow_fuzz",
    srcs = ["tflite_interp_overflow_fuzz.cc"],
    deps = [
        "//tensorflow/lite:framework",
        "//tensorflow/lite/c:common",
        "//tensorflow/lite/kernels:builtin_ops",
    ],
)
```

The target uses only the public `//tensorflow/lite:framework`, `//tensorflow/lite/c:common` and `//tensorflow/lite/kernels:builtin_ops`, so no visibility changes are needed. The BUILD already loads `tf_cc_fuzz_test` (sibling targets use it). The harness returns early on any non-OK status so benign malformed inputs are not treated as findings; only a genuine memory-safety violation surfaces as a crash.

### Notes for review
- Dep closure is the public framework + builtin ops; if CI's linker wants a narrower or additional core target for the `tensorflow/lite/core/*` headers, I will adjust to match.
- Happy to add a seed corpus entry in a follow-up if the fuzzing infra owners prefer a seeded start.
Copybara import of the project:

--
653c77178c4839ca340b7179aae7b3222f708698 by cybrdude <layau@netguard24-7.com>:

Add TFLite interpreter-build fuzz target (model load + invoke)

The OSS-Fuzz tensorflow build has no fuzz target that reaches TFLite model load, interpreter build, and invoke. This adds one under security/fuzzing/cc, exercising interpreter_builder.cc external-buffer handling (get_readonly_data / ParseTensors), where an unchecked uint64 offset+size addition can bypass the allocation bounds check and set a wild read-only tensor pointer that is dereferenced during Invoke. Deps use the public //tensorflow/lite:framework and //tensorflow/lite/kernels:builtin_ops.

--
3eb7b68f504251c397ee07e253798d545d8027fc by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

Update tensorflow/security/fuzzing/cc/tflite_interp_overflow_fuzz.cc

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
--
141f0b41e740c9e29e7696597a5155a183b760cc by cybrdude <layau@netguard24-7.com>:

fuzz: zero-init runtime input tensors before Invoke to avoid MSan false positives

Addresses gemini-code-assist review. TFLite's arena allocator does not zero tensor memory, so kernels reading uninitialized runtime inputs raise MemorySanitizer use-of-uninitialized-value reports. Only runtime inputs are zeroed, not the crafted constant buffer that carries the finding.

--
75365a8d636f7ec401b59abd3575c5bef90b6814 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

fuzz(tflite): use public TFLite headers instead of core internal paths

Switches the three lite/core includes to the public forwarding headers exported by //tensorflow/lite:framework, per review. Including the core internal package directly caused the dependency layering check to fail. Include block re-sorted. No functional change to the target.
--
1c832e34eabd2b20398be3b12cf879b1fb8656f7 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: verify the model before building it in the TFLite fuzz target

Puts the harness on the supported ingress path for untrusted models: call
tflite::Verify() first and stop on rejection, then build and invoke. The
verifier owns the bounds checks on model metadata (tensorflow/tensorflow
#116631 review, Sep 18; checks landed in #123910), so inputs like the
uint64 external-offset wrap are rejected before ParseTensors, and the
verifier itself gets fuzz coverage. No change to interpreter_builder.cc.

VerifyAndBuildFromBuffer alone would not do this: without an extra_verifier
it runs only the flatbuffers structural check.
--
0111afab059556333fd8514eb5a833007636db66 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: depend on //tensorflow/lite/tools:verifier for the TFLite fuzz target

The target now calls tflite::Verify() before building the model.
--
f1ef1e37f86accdc21240fa43881db9d9019f633 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: do not zero kTfLiteMmapRo inputs in the TFLite fuzz target

A crafted model can declare a subgraph input backed by a constant buffer;
that tensor is kTfLiteMmapRo and points into the read-only model mapping,
so the MSan zero-fill would fault on it. Skip those; they are initialized
by the model already. Per review.
--
d481802c00127f44d8607a4841a620972fbb6e45 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: include <vector> and tensorflow/lite/c/common.h directly

The target uses TfLiteTensor, kTfLiteMmapRo, kTfLiteOk and std::vector;
include their headers instead of relying on transitive includes. Per review.
--
ae5b26db83123e460eb2658796ebce1601b9586e by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: depend on //tensorflow/lite/c:common for the TFLite fuzz target

Matches the direct include of tensorflow/lite/c/common.h. Per review.
--
72272479b6209adccbc3f7ba7660af22cb607769 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop the tflite::Verify() gate from the TFLite fuzz target

Per the Oct 3 review: malformed models go straight to InterpreterBuilder
so its own handling of model metadata is what gets fuzzed. Removes the
Verify() early return and its include; keeps the direct includes and the
kTfLiteMmapRo exclusion from the earlier rounds.
--
fd2f77c447ec35203d6a6db8c0e672ba68033507 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop the verifier dep from the TFLite fuzz target

The target no longer calls tflite::Verify(). Per the Oct 3 review.
--
db0ba16e36ff663983f49a9e5a7b544d227dd84b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

tflite: overflow-safe external-offset bounds checks in InterpreterBuilder

Both sites compared offset + size against allocation_->bytes() with two
uint64_t operands; a pair whose sum wraps passed the check and the pointer
computed from base + offset wrapped with it. Compare in the subtraction
form instead, so the check cannot overflow. Same predicate for every valid
(offset, size); only the wrapping pairs change outcome.

Requested on this PR on Sep 16 and again on Oct 3 so the fuzz target in
the same change does not report a known crash. Re-applies the change from
#116631 that was reverted on Sep 18; the verifier-side check from #123910
stays, so both layers reject the wrap.
--
72567388959f68b271686cd87b6721845457bdee by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

tflite: restore master ParseQuantization dropped by the previous commit

db0ba16 was applied to a stale copy of interpreter_builder.cc and removed
56 lines of ParseQuantization that master had added. This puts the file
back to master plus only the two bounds-check hunks.
--
c624d970b2667b4e96aa54579552600a8ed38e44 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: enter through VerifyAndBuildFromBuffer in the TFLite fuzz target

Per review: models now pass the flatbuffers structural verifier before
InterpreterBuilder sees them. No extra_verifier, so tflite::Verify() is not
on this path; the external-offset bounds checks in interpreter_builder.cc
remain the check exercised for wrapped offsets.
--
6b637521dbde57526747589a95a68d9e0bcba52d by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: cache offset, size and allocation bytes in the external-offset checks

Per review: hoist op->large_custom_options_offset(), the option size and
allocation_->bytes() into locals in ParseNodes, and allocation_->bytes() and
buffer->size() in ParseTensors, so the checks do not repeat the flatbuffer
accessors and the virtual call. No behaviour change.
--
267042d6b57f914b92baca79a17dcb2b5bc4ca63 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop unused <vector> include from the TFLite fuzz target

Per review (IWYU): the file never names std::vector.
--
e9612f26a1f95d898257ad3c0644fc31e8edeb1b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: restore <vector> include in the TFLite fuzz target

Per the corrected review: interpreter->inputs() returns const std::vector<int>&
and the range-for over it is a use, so IWYU wants the header named here.
--
0193d170dba8cca45f7189170265a85453fe324e by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: regression tests for wrapping external offsets in InterpreterBuilder

Per review: two deterministic tests in model_test.cc. Each builds a model whose
(offset, size) pair sums past uint64 to 9, loads it with BuildFromBuffer so the
verifier is not in the way, and expects InterpreterBuilder to reject it. One
covers the Buffer offset path in ParseTensors (tensor bytes equal the claimed
size so the size check cannot mask the result), the other the
large_custom_options path in ParseNodes.
--
2c10c04377c30d05add06b08c3c1371bfd94f54b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: mark the test registration CUSTOM so the ParseNodes case reaches the check

Per review: ParseNodes derives the op type from registration->builtin_code,
and dummy_reg carries 0 (ADD), so the large_custom_options branch never ran
and the test passed vacuously. The test now resolves to a copy of dummy_reg
marked BuiltinOperator_CUSTOM with custom_name WRAP.

Merging this change closes #123123

FUTURE_COPYBARA_INTEGRATE_REVIEW=tensorflow/tensorflow#123123 from cybrdude:fuzz/tflite-interp-overflow-target 2c10c04377c30d05add06b08c3c1371bfd94f54b
PiperOrigin-RevId: 992803740
copybara-service Bot pushed a commit to google-ai-edge/LiteRT that referenced this pull request Oct 8, 2026
…oke fuzz target

Imported from GitHub PR tensorflow/tensorflow#123123

### What
Adds a FuzzTest target under `tensorflow/security/fuzzing/cc/` that loads a serialized `.tflite` model, builds an interpreter with the builtin op resolver, allocates tensors, and invokes. The OSS-Fuzz `tensorflow` build currently has no target that reaches TFLite model load / interpreter build / invoke, so this path is unfuzzed upstream.

### Why
`FlatBufferModel` -> `InterpreterBuilder` -> `AllocateTensors` -> `Invoke` parses attacker-supplied model structure, including external-buffer descriptors handled in `interpreter_builder.cc` (`get_readonly_data` / `ParseTensors`). That path performs a `uint64` `offset + size` addition and compares it against `allocation_->bytes()`. Adding coverage here lets OSS-Fuzz continuously exercise external-offset handling, tensor wiring, and builtin-kernel eval on malformed models, which is a class of input the existing core fuzzers do not reach.

### Change
`tensorflow/lite/core/interpreter_builder.cc`: the two external-offset bounds checks (`ParseNodes`, large_custom_options; `ParseTensors`, Buffer offset) use the subtraction form `offset > bytes || size > bytes - offset` instead of `offset + size > bytes`, so the uint64 sum can no longer wrap below `allocation_->bytes()`. The offset, size and allocation size are read once into locals. Requested in review (2026-09-16, 2026-10-03).

One new file plus a `tf_cc_fuzz_test` entry in `security/fuzzing/cc/BUILD`:

```python
tf_cc_fuzz_test(
    name = "tflite_interp_overflow_fuzz",
    srcs = ["tflite_interp_overflow_fuzz.cc"],
    deps = [
        "//tensorflow/lite:framework",
        "//tensorflow/lite/c:common",
        "//tensorflow/lite/kernels:builtin_ops",
    ],
)
```

The target uses only the public `//tensorflow/lite:framework`, `//tensorflow/lite/c:common` and `//tensorflow/lite/kernels:builtin_ops`, so no visibility changes are needed. The BUILD already loads `tf_cc_fuzz_test` (sibling targets use it). The harness returns early on any non-OK status so benign malformed inputs are not treated as findings; only a genuine memory-safety violation surfaces as a crash.

### Notes for review
- Dep closure is the public framework + builtin ops; if CI's linker wants a narrower or additional core target for the `tensorflow/lite/core/*` headers, I will adjust to match.
- Happy to add a seed corpus entry in a follow-up if the fuzzing infra owners prefer a seeded start.
Copybara import of the project:

--
653c77178c4839ca340b7179aae7b3222f708698 by cybrdude <layau@netguard24-7.com>:

Add TFLite interpreter-build fuzz target (model load + invoke)

The OSS-Fuzz tensorflow build has no fuzz target that reaches TFLite model load, interpreter build, and invoke. This adds one under security/fuzzing/cc, exercising interpreter_builder.cc external-buffer handling (get_readonly_data / ParseTensors), where an unchecked uint64 offset+size addition can bypass the allocation bounds check and set a wild read-only tensor pointer that is dereferenced during Invoke. Deps use the public //tensorflow/lite:framework and //tensorflow/lite/kernels:builtin_ops.

--
3eb7b68f504251c397ee07e253798d545d8027fc by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

Update tensorflow/security/fuzzing/cc/tflite_interp_overflow_fuzz.cc

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
--
141f0b41e740c9e29e7696597a5155a183b760cc by cybrdude <layau@netguard24-7.com>:

fuzz: zero-init runtime input tensors before Invoke to avoid MSan false positives

Addresses gemini-code-assist review. TFLite's arena allocator does not zero tensor memory, so kernels reading uninitialized runtime inputs raise MemorySanitizer use-of-uninitialized-value reports. Only runtime inputs are zeroed, not the crafted constant buffer that carries the finding.

--
75365a8d636f7ec401b59abd3575c5bef90b6814 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

fuzz(tflite): use public TFLite headers instead of core internal paths

Switches the three lite/core includes to the public forwarding headers exported by //tensorflow/lite:framework, per review. Including the core internal package directly caused the dependency layering check to fail. Include block re-sorted. No functional change to the target.
--
1c832e34eabd2b20398be3b12cf879b1fb8656f7 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: verify the model before building it in the TFLite fuzz target

Puts the harness on the supported ingress path for untrusted models: call
tflite::Verify() first and stop on rejection, then build and invoke. The
verifier owns the bounds checks on model metadata (tensorflow/tensorflow
#116631 review, Sep 18; checks landed in #123910), so inputs like the
uint64 external-offset wrap are rejected before ParseTensors, and the
verifier itself gets fuzz coverage. No change to interpreter_builder.cc.

VerifyAndBuildFromBuffer alone would not do this: without an extra_verifier
it runs only the flatbuffers structural check.
--
0111afab059556333fd8514eb5a833007636db66 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: depend on //tensorflow/lite/tools:verifier for the TFLite fuzz target

The target now calls tflite::Verify() before building the model.
--
f1ef1e37f86accdc21240fa43881db9d9019f633 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: do not zero kTfLiteMmapRo inputs in the TFLite fuzz target

A crafted model can declare a subgraph input backed by a constant buffer;
that tensor is kTfLiteMmapRo and points into the read-only model mapping,
so the MSan zero-fill would fault on it. Skip those; they are initialized
by the model already. Per review.
--
d481802c00127f44d8607a4841a620972fbb6e45 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: include <vector> and tensorflow/lite/c/common.h directly

The target uses TfLiteTensor, kTfLiteMmapRo, kTfLiteOk and std::vector;
include their headers instead of relying on transitive includes. Per review.
--
ae5b26db83123e460eb2658796ebce1601b9586e by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: depend on //tensorflow/lite/c:common for the TFLite fuzz target

Matches the direct include of tensorflow/lite/c/common.h. Per review.
--
72272479b6209adccbc3f7ba7660af22cb607769 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop the tflite::Verify() gate from the TFLite fuzz target

Per the Oct 3 review: malformed models go straight to InterpreterBuilder
so its own handling of model metadata is what gets fuzzed. Removes the
Verify() early return and its include; keeps the direct includes and the
kTfLiteMmapRo exclusion from the earlier rounds.
--
fd2f77c447ec35203d6a6db8c0e672ba68033507 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop the verifier dep from the TFLite fuzz target

The target no longer calls tflite::Verify(). Per the Oct 3 review.
--
db0ba16e36ff663983f49a9e5a7b544d227dd84b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

tflite: overflow-safe external-offset bounds checks in InterpreterBuilder

Both sites compared offset + size against allocation_->bytes() with two
uint64_t operands; a pair whose sum wraps passed the check and the pointer
computed from base + offset wrapped with it. Compare in the subtraction
form instead, so the check cannot overflow. Same predicate for every valid
(offset, size); only the wrapping pairs change outcome.

Requested on this PR on Sep 16 and again on Oct 3 so the fuzz target in
the same change does not report a known crash. Re-applies the change from
#116631 that was reverted on Sep 18; the verifier-side check from #123910
stays, so both layers reject the wrap.
--
72567388959f68b271686cd87b6721845457bdee by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

tflite: restore master ParseQuantization dropped by the previous commit

db0ba16 was applied to a stale copy of interpreter_builder.cc and removed
56 lines of ParseQuantization that master had added. This puts the file
back to master plus only the two bounds-check hunks.
--
c624d970b2667b4e96aa54579552600a8ed38e44 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: enter through VerifyAndBuildFromBuffer in the TFLite fuzz target

Per review: models now pass the flatbuffers structural verifier before
InterpreterBuilder sees them. No extra_verifier, so tflite::Verify() is not
on this path; the external-offset bounds checks in interpreter_builder.cc
remain the check exercised for wrapped offsets.
--
6b637521dbde57526747589a95a68d9e0bcba52d by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: cache offset, size and allocation bytes in the external-offset checks

Per review: hoist op->large_custom_options_offset(), the option size and
allocation_->bytes() into locals in ParseNodes, and allocation_->bytes() and
buffer->size() in ParseTensors, so the checks do not repeat the flatbuffer
accessors and the virtual call. No behaviour change.
--
267042d6b57f914b92baca79a17dcb2b5bc4ca63 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop unused <vector> include from the TFLite fuzz target

Per review (IWYU): the file never names std::vector.
--
e9612f26a1f95d898257ad3c0644fc31e8edeb1b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: restore <vector> include in the TFLite fuzz target

Per the corrected review: interpreter->inputs() returns const std::vector<int>&
and the range-for over it is a use, so IWYU wants the header named here.
--
0193d170dba8cca45f7189170265a85453fe324e by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: regression tests for wrapping external offsets in InterpreterBuilder

Per review: two deterministic tests in model_test.cc. Each builds a model whose
(offset, size) pair sums past uint64 to 9, loads it with BuildFromBuffer so the
verifier is not in the way, and expects InterpreterBuilder to reject it. One
covers the Buffer offset path in ParseTensors (tensor bytes equal the claimed
size so the size check cannot mask the result), the other the
large_custom_options path in ParseNodes.
--
2c10c04377c30d05add06b08c3c1371bfd94f54b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: mark the test registration CUSTOM so the ParseNodes case reaches the check

Per review: ParseNodes derives the op type from registration->builtin_code,
and dummy_reg carries 0 (ADD), so the large_custom_options branch never ran
and the test passed vacuously. The test now resolves to a copy of dummy_reg
marked BuiltinOperator_CUSTOM with custom_name WRAP.

Merging this change closes #123123

FUTURE_COPYBARA_INTEGRATE_REVIEW=tensorflow/tensorflow#123123 from cybrdude:fuzz/tflite-interp-overflow-target 2c10c04377c30d05add06b08c3c1371bfd94f54b
PiperOrigin-RevId: 992803740

@dmiltr3 dmiltr3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @cybrdude, thank you for your pull request!

During internal review, our reviewers had a few suggestions for your changes:

  1. In tensorflow/security/fuzzing/cc/tflite_interp_overflow_fuzz.cc around line 49:
    Reviewer feedback: VerifyAndBuildFromBuffer and InterpreterBuilder default to DefaultErrorReporter(), which writes error messages to stderr whenever a model fails validation or parsing. Because the vast majority of fuzz inputs are malformed, logging to stderr on every iteration can cause significant I/O overhead and log spam.
    Consider passing a no-op ErrorReporter (similar to NullErrorReporter in tensorflow/lite/testing/fuzzing/model_fuzzer_test.cc) to both VerifyAndBuildFromBuffer and InterpreterBuilder.

  2. In tensorflow/security/fuzzing/cc/tflite_interp_overflow_fuzz.cc around line 63:
    Reviewer feedback: In addition to tensor->allocation_type != kTfLiteMmapRo, consider also guarding against tensor->bytes == 0 and non-POD tensor types (kTfLiteString, kTfLiteResource, kTfLiteVariant) or read-only persistent allocations (kTfLitePersistentRo) before calling std::memset(tensor->data.raw, 0, tensor->bytes).

  3. In tensorflow/security/fuzzing/cc/tflite_interp_overflow_fuzz.cc:
    Reviewer feedback: Consider providing a minimal valid .tflite seed corpus via .WithSeeds(...) (for example, loading tensorflow/lite/testdata/add.bin as done in tensorflow/lite/testing/fuzzing/model_fuzzer_test.cc) so the fuzzer can bypass the initial FlatBuffer structural check and immediately exercise InterpreterBuilder and Invoke.

  4. In tensorflow/lite/core/interpreter_builder.cc around line 835:
    Reviewer feedback: Optional nit: Consider initializing const char* buffer_ptr = nullptr; so buffer_ptr is never left uninitialized if get_readonly_data takes a fallthrough path that returns kTfLiteOk without assigning *buffer_data.

Could you please address these suggestions? Let us know if you have any questions!

Per internal review: the model (and through it the interpreter builder) report
into a no-op ErrorReporter so malformed inputs do not write to stderr on every
iteration; tensorflow/lite/testdata/add.bin is loaded as a seed so the fuzzer
starts from a model that passes the structural check; the MSan zeroing skips
empty, persistent read-only, string, resource and variant inputs as well as
mmap-backed constants.
@google-ml-butler google-ml-butler Bot removed the ready to pull PR ready for merge process label Oct 15, 2026
…zz target

add.bin from //tensorflow/lite is the seed corpus; resource_loader resolves its
runfiles path, the same way checkpoint_reader_fuzz does in this package.
Per internal review: buffer_ptr is never left uninitialized if a path through
get_readonly_data returns kTfLiteOk without assigning it.
@cybrdude

Copy link
Copy Markdown
Contributor Author

@dmiltr3 all four in c0f8e26, ba730ad and c1be76e. (1) The model is built with a no-op ErrorReporter; the InterpreterBuilder(const FlatBufferModel&) overload takes its reporter from the model, so the builder is silent through the same object. (2) The MSan zeroing now also skips bytes == 0, kTfLitePersistentRo, and string, resource and variant inputs. (3) tensorflow/lite/testdata/add.bin is loaded through GetDataDependencyFilepath as a seed (.WithSeeds), with the data entry and resource_loader dep in BUILD, following checkpoint_reader_fuzz in the same package. (4) buffer_ptr is initialized to nullptr. One note: the model_fuzzer_test.cc your reviewers pointed at is not in the public tree, so the reporter is a local three-line class rather than a reuse.

@cybrdude
cybrdude requested a review from dmiltr3 October 15, 2026 02:28
@google-ml-butler google-ml-butler Bot added the awaiting review Pull request awaiting review label Oct 15, 2026
@google-ml-butler google-ml-butler Bot added kokoro:force-run Tests on submitted change ready to pull PR ready for merge process labels Oct 15, 2026
copybara-service Bot pushed a commit to google-ai-edge/LiteRT that referenced this pull request Oct 15, 2026
…oke fuzz target

Imported from GitHub PR tensorflow/tensorflow#123123

### What
Adds a FuzzTest target under `tensorflow/security/fuzzing/cc/` that loads a serialized `.tflite` model, builds an interpreter with the builtin op resolver, allocates tensors, and invokes. The OSS-Fuzz `tensorflow` build currently has no target that reaches TFLite model load / interpreter build / invoke, so this path is unfuzzed upstream.

### Why
`FlatBufferModel` -> `InterpreterBuilder` -> `AllocateTensors` -> `Invoke` parses attacker-supplied model structure, including external-buffer descriptors handled in `interpreter_builder.cc` (`get_readonly_data` / `ParseTensors`). That path performs a `uint64` `offset + size` addition and compares it against `allocation_->bytes()`. Adding coverage here lets OSS-Fuzz continuously exercise external-offset handling, tensor wiring, and builtin-kernel eval on malformed models, which is a class of input the existing core fuzzers do not reach.

### Change
`tensorflow/lite/core/interpreter_builder.cc`: the two external-offset bounds checks (`ParseNodes`, large_custom_options; `ParseTensors`, Buffer offset) use the subtraction form `offset > bytes || size > bytes - offset` instead of `offset + size > bytes`, so the uint64 sum can no longer wrap below `allocation_->bytes()`. The offset, size and allocation size are read once into locals. Requested in review (2026-09-16, 2026-10-03).

One new file plus a `tf_cc_fuzz_test` entry in `security/fuzzing/cc/BUILD`:

```python
tf_cc_fuzz_test(
    name = "tflite_interp_overflow_fuzz",
    srcs = ["tflite_interp_overflow_fuzz.cc"],
    deps = [
        "//tensorflow/lite:framework",
        "//tensorflow/lite/c:common",
        "//tensorflow/lite/kernels:builtin_ops",
    ],
)
```

The target uses only the public `//tensorflow/lite:framework`, `//tensorflow/lite/c:common` and `//tensorflow/lite/kernels:builtin_ops`, so no visibility changes are needed. The BUILD already loads `tf_cc_fuzz_test` (sibling targets use it). The harness returns early on any non-OK status so benign malformed inputs are not treated as findings; only a genuine memory-safety violation surfaces as a crash.

### Notes for review
- Dep closure is the public framework + builtin ops; if CI's linker wants a narrower or additional core target for the `tensorflow/lite/core/*` headers, I will adjust to match.
- Happy to add a seed corpus entry in a follow-up if the fuzzing infra owners prefer a seeded start.
Copybara import of the project:

--
653c77178c4839ca340b7179aae7b3222f708698 by cybrdude <layau@netguard24-7.com>:

Add TFLite interpreter-build fuzz target (model load + invoke)

The OSS-Fuzz tensorflow build has no fuzz target that reaches TFLite model load, interpreter build, and invoke. This adds one under security/fuzzing/cc, exercising interpreter_builder.cc external-buffer handling (get_readonly_data / ParseTensors), where an unchecked uint64 offset+size addition can bypass the allocation bounds check and set a wild read-only tensor pointer that is dereferenced during Invoke. Deps use the public //tensorflow/lite:framework and //tensorflow/lite/kernels:builtin_ops.

--
3eb7b68f504251c397ee07e253798d545d8027fc by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

Update tensorflow/security/fuzzing/cc/tflite_interp_overflow_fuzz.cc

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
--
141f0b41e740c9e29e7696597a5155a183b760cc by cybrdude <layau@netguard24-7.com>:

fuzz: zero-init runtime input tensors before Invoke to avoid MSan false positives

Addresses gemini-code-assist review. TFLite's arena allocator does not zero tensor memory, so kernels reading uninitialized runtime inputs raise MemorySanitizer use-of-uninitialized-value reports. Only runtime inputs are zeroed, not the crafted constant buffer that carries the finding.

--
75365a8d636f7ec401b59abd3575c5bef90b6814 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

fuzz(tflite): use public TFLite headers instead of core internal paths

Switches the three lite/core includes to the public forwarding headers exported by //tensorflow/lite:framework, per review. Including the core internal package directly caused the dependency layering check to fail. Include block re-sorted. No functional change to the target.
--
1c832e34eabd2b20398be3b12cf879b1fb8656f7 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: verify the model before building it in the TFLite fuzz target

Puts the harness on the supported ingress path for untrusted models: call
tflite::Verify() first and stop on rejection, then build and invoke. The
verifier owns the bounds checks on model metadata (tensorflow/tensorflow
#116631 review, Sep 18; checks landed in #123910), so inputs like the
uint64 external-offset wrap are rejected before ParseTensors, and the
verifier itself gets fuzz coverage. No change to interpreter_builder.cc.

VerifyAndBuildFromBuffer alone would not do this: without an extra_verifier
it runs only the flatbuffers structural check.
--
0111afab059556333fd8514eb5a833007636db66 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: depend on //tensorflow/lite/tools:verifier for the TFLite fuzz target

The target now calls tflite::Verify() before building the model.
--
f1ef1e37f86accdc21240fa43881db9d9019f633 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: do not zero kTfLiteMmapRo inputs in the TFLite fuzz target

A crafted model can declare a subgraph input backed by a constant buffer;
that tensor is kTfLiteMmapRo and points into the read-only model mapping,
so the MSan zero-fill would fault on it. Skip those; they are initialized
by the model already. Per review.
--
d481802c00127f44d8607a4841a620972fbb6e45 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: include <vector> and tensorflow/lite/c/common.h directly

The target uses TfLiteTensor, kTfLiteMmapRo, kTfLiteOk and std::vector;
include their headers instead of relying on transitive includes. Per review.
--
ae5b26db83123e460eb2658796ebce1601b9586e by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: depend on //tensorflow/lite/c:common for the TFLite fuzz target

Matches the direct include of tensorflow/lite/c/common.h. Per review.
--
72272479b6209adccbc3f7ba7660af22cb607769 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop the tflite::Verify() gate from the TFLite fuzz target

Per the Oct 3 review: malformed models go straight to InterpreterBuilder
so its own handling of model metadata is what gets fuzzed. Removes the
Verify() early return and its include; keeps the direct includes and the
kTfLiteMmapRo exclusion from the earlier rounds.
--
fd2f77c447ec35203d6a6db8c0e672ba68033507 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop the verifier dep from the TFLite fuzz target

The target no longer calls tflite::Verify(). Per the Oct 3 review.
--
db0ba16e36ff663983f49a9e5a7b544d227dd84b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

tflite: overflow-safe external-offset bounds checks in InterpreterBuilder

Both sites compared offset + size against allocation_->bytes() with two
uint64_t operands; a pair whose sum wraps passed the check and the pointer
computed from base + offset wrapped with it. Compare in the subtraction
form instead, so the check cannot overflow. Same predicate for every valid
(offset, size); only the wrapping pairs change outcome.

Requested on this PR on Sep 16 and again on Oct 3 so the fuzz target in
the same change does not report a known crash. Re-applies the change from
#116631 that was reverted on Sep 18; the verifier-side check from #123910
stays, so both layers reject the wrap.
--
72567388959f68b271686cd87b6721845457bdee by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

tflite: restore master ParseQuantization dropped by the previous commit

db0ba16 was applied to a stale copy of interpreter_builder.cc and removed
56 lines of ParseQuantization that master had added. This puts the file
back to master plus only the two bounds-check hunks.
--
c624d970b2667b4e96aa54579552600a8ed38e44 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: enter through VerifyAndBuildFromBuffer in the TFLite fuzz target

Per review: models now pass the flatbuffers structural verifier before
InterpreterBuilder sees them. No extra_verifier, so tflite::Verify() is not
on this path; the external-offset bounds checks in interpreter_builder.cc
remain the check exercised for wrapped offsets.
--
6b637521dbde57526747589a95a68d9e0bcba52d by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: cache offset, size and allocation bytes in the external-offset checks

Per review: hoist op->large_custom_options_offset(), the option size and
allocation_->bytes() into locals in ParseNodes, and allocation_->bytes() and
buffer->size() in ParseTensors, so the checks do not repeat the flatbuffer
accessors and the virtual call. No behaviour change.
--
267042d6b57f914b92baca79a17dcb2b5bc4ca63 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop unused <vector> include from the TFLite fuzz target

Per review (IWYU): the file never names std::vector.
--
e9612f26a1f95d898257ad3c0644fc31e8edeb1b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: restore <vector> include in the TFLite fuzz target

Per the corrected review: interpreter->inputs() returns const std::vector<int>&
and the range-for over it is a use, so IWYU wants the header named here.
--
0193d170dba8cca45f7189170265a85453fe324e by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: regression tests for wrapping external offsets in InterpreterBuilder

Per review: two deterministic tests in model_test.cc. Each builds a model whose
(offset, size) pair sums past uint64 to 9, loads it with BuildFromBuffer so the
verifier is not in the way, and expects InterpreterBuilder to reject it. One
covers the Buffer offset path in ParseTensors (tensor bytes equal the claimed
size so the size check cannot mask the result), the other the
large_custom_options path in ParseNodes.
--
2c10c04377c30d05add06b08c3c1371bfd94f54b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: mark the test registration CUSTOM so the ParseNodes case reaches the check

Per review: ParseNodes derives the op type from registration->builtin_code,
and dummy_reg carries 0 (ADD), so the large_custom_options branch never ran
and the test passed vacuously. The test now resolves to a copy of dummy_reg
marked BuiltinOperator_CUSTOM with custom_name WRAP.
--
c0f8e2696af0b17230c690c689316f769c20b7c4 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: silent reporter, add.bin seed, stricter input zeroing

Per internal review: the model (and through it the interpreter builder) report
into a no-op ErrorReporter so malformed inputs do not write to stderr on every
iteration; tensorflow/lite/testdata/add.bin is loaded as a seed so the fuzzer
starts from a model that passes the structural check; the MSan zeroing skips
empty, persistent read-only, string, resource and variant inputs as well as
mmap-backed constants.
--
ba730ad80897d79270609605cc8027d7faec8a54 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: seed data and resource_loader dep for the TFLite fuzz target

add.bin from //tensorflow/lite is the seed corpus; resource_loader resolves its
runfiles path, the same way checkpoint_reader_fuzz does in this package.
--
c1be76e06447c1643b6b7f94f315b05a86eae532 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: initialize buffer_ptr before get_readonly_data

Per internal review: buffer_ptr is never left uninitialized if a path through
get_readonly_data returns kTfLiteOk without assigning it.

Merging this change closes #123123

FUTURE_COPYBARA_INTEGRATE_REVIEW=tensorflow/tensorflow#123123 from cybrdude:fuzz/tflite-interp-overflow-target c1be76e06447c1643b6b7f94f315b05a86eae532
PiperOrigin-RevId: 992803740
@nithyak0204 nithyak0204 removed awaiting review Pull request awaiting review kokoro:force-run Tests on submitted change labels Oct 15, 2026
copybara-service Bot pushed a commit to google-ai-edge/LiteRT that referenced this pull request Oct 15, 2026
…oke fuzz target

Imported from GitHub PR tensorflow/tensorflow#123123

### What
Adds a FuzzTest target under `tensorflow/security/fuzzing/cc/` that loads a serialized `.tflite` model, builds an interpreter with the builtin op resolver, allocates tensors, and invokes. The OSS-Fuzz `tensorflow` build currently has no target that reaches TFLite model load / interpreter build / invoke, so this path is unfuzzed upstream.

### Why
`FlatBufferModel` -> `InterpreterBuilder` -> `AllocateTensors` -> `Invoke` parses attacker-supplied model structure, including external-buffer descriptors handled in `interpreter_builder.cc` (`get_readonly_data` / `ParseTensors`). That path performs a `uint64` `offset + size` addition and compares it against `allocation_->bytes()`. Adding coverage here lets OSS-Fuzz continuously exercise external-offset handling, tensor wiring, and builtin-kernel eval on malformed models, which is a class of input the existing core fuzzers do not reach.

### Change
`tensorflow/lite/core/interpreter_builder.cc`: the two external-offset bounds checks (`ParseNodes`, large_custom_options; `ParseTensors`, Buffer offset) use the subtraction form `offset > bytes || size > bytes - offset` instead of `offset + size > bytes`, so the uint64 sum can no longer wrap below `allocation_->bytes()`. The offset, size and allocation size are read once into locals. Requested in review (2026-09-16, 2026-10-03).

One new file plus a `tf_cc_fuzz_test` entry in `security/fuzzing/cc/BUILD`:

```python
tf_cc_fuzz_test(
    name = "tflite_interp_overflow_fuzz",
    srcs = ["tflite_interp_overflow_fuzz.cc"],
    deps = [
        "//tensorflow/lite:framework",
        "//tensorflow/lite/c:common",
        "//tensorflow/lite/kernels:builtin_ops",
    ],
)
```

The target uses only the public `//tensorflow/lite:framework`, `//tensorflow/lite/c:common` and `//tensorflow/lite/kernels:builtin_ops`, so no visibility changes are needed. The BUILD already loads `tf_cc_fuzz_test` (sibling targets use it). The harness returns early on any non-OK status so benign malformed inputs are not treated as findings; only a genuine memory-safety violation surfaces as a crash.

### Notes for review
- Dep closure is the public framework + builtin ops; if CI's linker wants a narrower or additional core target for the `tensorflow/lite/core/*` headers, I will adjust to match.
- Happy to add a seed corpus entry in a follow-up if the fuzzing infra owners prefer a seeded start.
Copybara import of the project:

--
653c77178c4839ca340b7179aae7b3222f708698 by cybrdude <layau@netguard24-7.com>:

Add TFLite interpreter-build fuzz target (model load + invoke)

The OSS-Fuzz tensorflow build has no fuzz target that reaches TFLite model load, interpreter build, and invoke. This adds one under security/fuzzing/cc, exercising interpreter_builder.cc external-buffer handling (get_readonly_data / ParseTensors), where an unchecked uint64 offset+size addition can bypass the allocation bounds check and set a wild read-only tensor pointer that is dereferenced during Invoke. Deps use the public //tensorflow/lite:framework and //tensorflow/lite/kernels:builtin_ops.

--
3eb7b68f504251c397ee07e253798d545d8027fc by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

Update tensorflow/security/fuzzing/cc/tflite_interp_overflow_fuzz.cc

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
--
141f0b41e740c9e29e7696597a5155a183b760cc by cybrdude <layau@netguard24-7.com>:

fuzz: zero-init runtime input tensors before Invoke to avoid MSan false positives

Addresses gemini-code-assist review. TFLite's arena allocator does not zero tensor memory, so kernels reading uninitialized runtime inputs raise MemorySanitizer use-of-uninitialized-value reports. Only runtime inputs are zeroed, not the crafted constant buffer that carries the finding.

--
75365a8d636f7ec401b59abd3575c5bef90b6814 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

fuzz(tflite): use public TFLite headers instead of core internal paths

Switches the three lite/core includes to the public forwarding headers exported by //tensorflow/lite:framework, per review. Including the core internal package directly caused the dependency layering check to fail. Include block re-sorted. No functional change to the target.
--
1c832e34eabd2b20398be3b12cf879b1fb8656f7 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: verify the model before building it in the TFLite fuzz target

Puts the harness on the supported ingress path for untrusted models: call
tflite::Verify() first and stop on rejection, then build and invoke. The
verifier owns the bounds checks on model metadata (tensorflow/tensorflow
#116631 review, Sep 18; checks landed in #123910), so inputs like the
uint64 external-offset wrap are rejected before ParseTensors, and the
verifier itself gets fuzz coverage. No change to interpreter_builder.cc.

VerifyAndBuildFromBuffer alone would not do this: without an extra_verifier
it runs only the flatbuffers structural check.
--
0111afab059556333fd8514eb5a833007636db66 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: depend on //tensorflow/lite/tools:verifier for the TFLite fuzz target

The target now calls tflite::Verify() before building the model.
--
f1ef1e37f86accdc21240fa43881db9d9019f633 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: do not zero kTfLiteMmapRo inputs in the TFLite fuzz target

A crafted model can declare a subgraph input backed by a constant buffer;
that tensor is kTfLiteMmapRo and points into the read-only model mapping,
so the MSan zero-fill would fault on it. Skip those; they are initialized
by the model already. Per review.
--
d481802c00127f44d8607a4841a620972fbb6e45 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: include <vector> and tensorflow/lite/c/common.h directly

The target uses TfLiteTensor, kTfLiteMmapRo, kTfLiteOk and std::vector;
include their headers instead of relying on transitive includes. Per review.
--
ae5b26db83123e460eb2658796ebce1601b9586e by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: depend on //tensorflow/lite/c:common for the TFLite fuzz target

Matches the direct include of tensorflow/lite/c/common.h. Per review.
--
72272479b6209adccbc3f7ba7660af22cb607769 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop the tflite::Verify() gate from the TFLite fuzz target

Per the Oct 3 review: malformed models go straight to InterpreterBuilder
so its own handling of model metadata is what gets fuzzed. Removes the
Verify() early return and its include; keeps the direct includes and the
kTfLiteMmapRo exclusion from the earlier rounds.
--
fd2f77c447ec35203d6a6db8c0e672ba68033507 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop the verifier dep from the TFLite fuzz target

The target no longer calls tflite::Verify(). Per the Oct 3 review.
--
db0ba16e36ff663983f49a9e5a7b544d227dd84b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

tflite: overflow-safe external-offset bounds checks in InterpreterBuilder

Both sites compared offset + size against allocation_->bytes() with two
uint64_t operands; a pair whose sum wraps passed the check and the pointer
computed from base + offset wrapped with it. Compare in the subtraction
form instead, so the check cannot overflow. Same predicate for every valid
(offset, size); only the wrapping pairs change outcome.

Requested on this PR on Sep 16 and again on Oct 3 so the fuzz target in
the same change does not report a known crash. Re-applies the change from
#116631 that was reverted on Sep 18; the verifier-side check from #123910
stays, so both layers reject the wrap.
--
72567388959f68b271686cd87b6721845457bdee by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

tflite: restore master ParseQuantization dropped by the previous commit

db0ba16 was applied to a stale copy of interpreter_builder.cc and removed
56 lines of ParseQuantization that master had added. This puts the file
back to master plus only the two bounds-check hunks.
--
c624d970b2667b4e96aa54579552600a8ed38e44 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: enter through VerifyAndBuildFromBuffer in the TFLite fuzz target

Per review: models now pass the flatbuffers structural verifier before
InterpreterBuilder sees them. No extra_verifier, so tflite::Verify() is not
on this path; the external-offset bounds checks in interpreter_builder.cc
remain the check exercised for wrapped offsets.
--
6b637521dbde57526747589a95a68d9e0bcba52d by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: cache offset, size and allocation bytes in the external-offset checks

Per review: hoist op->large_custom_options_offset(), the option size and
allocation_->bytes() into locals in ParseNodes, and allocation_->bytes() and
buffer->size() in ParseTensors, so the checks do not repeat the flatbuffer
accessors and the virtual call. No behaviour change.
--
267042d6b57f914b92baca79a17dcb2b5bc4ca63 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop unused <vector> include from the TFLite fuzz target

Per review (IWYU): the file never names std::vector.
--
e9612f26a1f95d898257ad3c0644fc31e8edeb1b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: restore <vector> include in the TFLite fuzz target

Per the corrected review: interpreter->inputs() returns const std::vector<int>&
and the range-for over it is a use, so IWYU wants the header named here.
--
0193d170dba8cca45f7189170265a85453fe324e by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: regression tests for wrapping external offsets in InterpreterBuilder

Per review: two deterministic tests in model_test.cc. Each builds a model whose
(offset, size) pair sums past uint64 to 9, loads it with BuildFromBuffer so the
verifier is not in the way, and expects InterpreterBuilder to reject it. One
covers the Buffer offset path in ParseTensors (tensor bytes equal the claimed
size so the size check cannot mask the result), the other the
large_custom_options path in ParseNodes.
--
2c10c04377c30d05add06b08c3c1371bfd94f54b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: mark the test registration CUSTOM so the ParseNodes case reaches the check

Per review: ParseNodes derives the op type from registration->builtin_code,
and dummy_reg carries 0 (ADD), so the large_custom_options branch never ran
and the test passed vacuously. The test now resolves to a copy of dummy_reg
marked BuiltinOperator_CUSTOM with custom_name WRAP.
--
c0f8e2696af0b17230c690c689316f769c20b7c4 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: silent reporter, add.bin seed, stricter input zeroing

Per internal review: the model (and through it the interpreter builder) report
into a no-op ErrorReporter so malformed inputs do not write to stderr on every
iteration; tensorflow/lite/testdata/add.bin is loaded as a seed so the fuzzer
starts from a model that passes the structural check; the MSan zeroing skips
empty, persistent read-only, string, resource and variant inputs as well as
mmap-backed constants.
--
ba730ad80897d79270609605cc8027d7faec8a54 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: seed data and resource_loader dep for the TFLite fuzz target

add.bin from //tensorflow/lite is the seed corpus; resource_loader resolves its
runfiles path, the same way checkpoint_reader_fuzz does in this package.
--
c1be76e06447c1643b6b7f94f315b05a86eae532 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: initialize buffer_ptr before get_readonly_data

Per internal review: buffer_ptr is never left uninitialized if a path through
get_readonly_data returns kTfLiteOk without assigning it.

Merging this change closes #123123

FUTURE_COPYBARA_INTEGRATE_REVIEW=tensorflow/tensorflow#123123 from cybrdude:fuzz/tflite-interp-overflow-target c1be76e06447c1643b6b7f94f315b05a86eae532
PiperOrigin-RevId: 992803740
copybara-service Bot pushed a commit to google-ai-edge/LiteRT that referenced this pull request Oct 15, 2026
…oke fuzz target

Imported from GitHub PR tensorflow/tensorflow#123123

### What
Adds a FuzzTest target under `tensorflow/security/fuzzing/cc/` that loads a serialized `.tflite` model, builds an interpreter with the builtin op resolver, allocates tensors, and invokes. The OSS-Fuzz `tensorflow` build currently has no target that reaches TFLite model load / interpreter build / invoke, so this path is unfuzzed upstream.

### Why
`FlatBufferModel` -> `InterpreterBuilder` -> `AllocateTensors` -> `Invoke` parses attacker-supplied model structure, including external-buffer descriptors handled in `interpreter_builder.cc` (`get_readonly_data` / `ParseTensors`). That path performs a `uint64` `offset + size` addition and compares it against `allocation_->bytes()`. Adding coverage here lets OSS-Fuzz continuously exercise external-offset handling, tensor wiring, and builtin-kernel eval on malformed models, which is a class of input the existing core fuzzers do not reach.

### Change
`tensorflow/lite/core/interpreter_builder.cc`: the two external-offset bounds checks (`ParseNodes`, large_custom_options; `ParseTensors`, Buffer offset) use the subtraction form `offset > bytes || size > bytes - offset` instead of `offset + size > bytes`, so the uint64 sum can no longer wrap below `allocation_->bytes()`. The offset, size and allocation size are read once into locals. Requested in review (2026-09-16, 2026-10-03).

One new file plus a `tf_cc_fuzz_test` entry in `security/fuzzing/cc/BUILD`:

```python
tf_cc_fuzz_test(
    name = "tflite_interp_overflow_fuzz",
    srcs = ["tflite_interp_overflow_fuzz.cc"],
    deps = [
        "//tensorflow/lite:framework",
        "//tensorflow/lite/c:common",
        "//tensorflow/lite/kernels:builtin_ops",
    ],
)
```

The target uses only the public `//tensorflow/lite:framework`, `//tensorflow/lite/c:common` and `//tensorflow/lite/kernels:builtin_ops`, so no visibility changes are needed. The BUILD already loads `tf_cc_fuzz_test` (sibling targets use it). The harness returns early on any non-OK status so benign malformed inputs are not treated as findings; only a genuine memory-safety violation surfaces as a crash.

### Notes for review
- Dep closure is the public framework + builtin ops; if CI's linker wants a narrower or additional core target for the `tensorflow/lite/core/*` headers, I will adjust to match.
- Happy to add a seed corpus entry in a follow-up if the fuzzing infra owners prefer a seeded start.
Copybara import of the project:

--
653c77178c4839ca340b7179aae7b3222f708698 by cybrdude <layau@netguard24-7.com>:

Add TFLite interpreter-build fuzz target (model load + invoke)

The OSS-Fuzz tensorflow build has no fuzz target that reaches TFLite model load, interpreter build, and invoke. This adds one under security/fuzzing/cc, exercising interpreter_builder.cc external-buffer handling (get_readonly_data / ParseTensors), where an unchecked uint64 offset+size addition can bypass the allocation bounds check and set a wild read-only tensor pointer that is dereferenced during Invoke. Deps use the public //tensorflow/lite:framework and //tensorflow/lite/kernels:builtin_ops.

--
3eb7b68f504251c397ee07e253798d545d8027fc by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

Update tensorflow/security/fuzzing/cc/tflite_interp_overflow_fuzz.cc

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
--
141f0b41e740c9e29e7696597a5155a183b760cc by cybrdude <layau@netguard24-7.com>:

fuzz: zero-init runtime input tensors before Invoke to avoid MSan false positives

Addresses gemini-code-assist review. TFLite's arena allocator does not zero tensor memory, so kernels reading uninitialized runtime inputs raise MemorySanitizer use-of-uninitialized-value reports. Only runtime inputs are zeroed, not the crafted constant buffer that carries the finding.

--
75365a8d636f7ec401b59abd3575c5bef90b6814 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

fuzz(tflite): use public TFLite headers instead of core internal paths

Switches the three lite/core includes to the public forwarding headers exported by //tensorflow/lite:framework, per review. Including the core internal package directly caused the dependency layering check to fail. Include block re-sorted. No functional change to the target.
--
1c832e34eabd2b20398be3b12cf879b1fb8656f7 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: verify the model before building it in the TFLite fuzz target

Puts the harness on the supported ingress path for untrusted models: call
tflite::Verify() first and stop on rejection, then build and invoke. The
verifier owns the bounds checks on model metadata (tensorflow/tensorflow
#116631 review, Sep 18; checks landed in #123910), so inputs like the
uint64 external-offset wrap are rejected before ParseTensors, and the
verifier itself gets fuzz coverage. No change to interpreter_builder.cc.

VerifyAndBuildFromBuffer alone would not do this: without an extra_verifier
it runs only the flatbuffers structural check.
--
0111afab059556333fd8514eb5a833007636db66 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: depend on //tensorflow/lite/tools:verifier for the TFLite fuzz target

The target now calls tflite::Verify() before building the model.
--
f1ef1e37f86accdc21240fa43881db9d9019f633 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: do not zero kTfLiteMmapRo inputs in the TFLite fuzz target

A crafted model can declare a subgraph input backed by a constant buffer;
that tensor is kTfLiteMmapRo and points into the read-only model mapping,
so the MSan zero-fill would fault on it. Skip those; they are initialized
by the model already. Per review.
--
d481802c00127f44d8607a4841a620972fbb6e45 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: include <vector> and tensorflow/lite/c/common.h directly

The target uses TfLiteTensor, kTfLiteMmapRo, kTfLiteOk and std::vector;
include their headers instead of relying on transitive includes. Per review.
--
ae5b26db83123e460eb2658796ebce1601b9586e by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: depend on //tensorflow/lite/c:common for the TFLite fuzz target

Matches the direct include of tensorflow/lite/c/common.h. Per review.
--
72272479b6209adccbc3f7ba7660af22cb607769 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop the tflite::Verify() gate from the TFLite fuzz target

Per the Oct 3 review: malformed models go straight to InterpreterBuilder
so its own handling of model metadata is what gets fuzzed. Removes the
Verify() early return and its include; keeps the direct includes and the
kTfLiteMmapRo exclusion from the earlier rounds.
--
fd2f77c447ec35203d6a6db8c0e672ba68033507 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop the verifier dep from the TFLite fuzz target

The target no longer calls tflite::Verify(). Per the Oct 3 review.
--
db0ba16e36ff663983f49a9e5a7b544d227dd84b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

tflite: overflow-safe external-offset bounds checks in InterpreterBuilder

Both sites compared offset + size against allocation_->bytes() with two
uint64_t operands; a pair whose sum wraps passed the check and the pointer
computed from base + offset wrapped with it. Compare in the subtraction
form instead, so the check cannot overflow. Same predicate for every valid
(offset, size); only the wrapping pairs change outcome.

Requested on this PR on Sep 16 and again on Oct 3 so the fuzz target in
the same change does not report a known crash. Re-applies the change from
#116631 that was reverted on Sep 18; the verifier-side check from #123910
stays, so both layers reject the wrap.
--
72567388959f68b271686cd87b6721845457bdee by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

tflite: restore master ParseQuantization dropped by the previous commit

db0ba16 was applied to a stale copy of interpreter_builder.cc and removed
56 lines of ParseQuantization that master had added. This puts the file
back to master plus only the two bounds-check hunks.
--
c624d970b2667b4e96aa54579552600a8ed38e44 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: enter through VerifyAndBuildFromBuffer in the TFLite fuzz target

Per review: models now pass the flatbuffers structural verifier before
InterpreterBuilder sees them. No extra_verifier, so tflite::Verify() is not
on this path; the external-offset bounds checks in interpreter_builder.cc
remain the check exercised for wrapped offsets.
--
6b637521dbde57526747589a95a68d9e0bcba52d by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: cache offset, size and allocation bytes in the external-offset checks

Per review: hoist op->large_custom_options_offset(), the option size and
allocation_->bytes() into locals in ParseNodes, and allocation_->bytes() and
buffer->size() in ParseTensors, so the checks do not repeat the flatbuffer
accessors and the virtual call. No behaviour change.
--
267042d6b57f914b92baca79a17dcb2b5bc4ca63 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop unused <vector> include from the TFLite fuzz target

Per review (IWYU): the file never names std::vector.
--
e9612f26a1f95d898257ad3c0644fc31e8edeb1b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: restore <vector> include in the TFLite fuzz target

Per the corrected review: interpreter->inputs() returns const std::vector<int>&
and the range-for over it is a use, so IWYU wants the header named here.
--
0193d170dba8cca45f7189170265a85453fe324e by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: regression tests for wrapping external offsets in InterpreterBuilder

Per review: two deterministic tests in model_test.cc. Each builds a model whose
(offset, size) pair sums past uint64 to 9, loads it with BuildFromBuffer so the
verifier is not in the way, and expects InterpreterBuilder to reject it. One
covers the Buffer offset path in ParseTensors (tensor bytes equal the claimed
size so the size check cannot mask the result), the other the
large_custom_options path in ParseNodes.
--
2c10c04377c30d05add06b08c3c1371bfd94f54b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: mark the test registration CUSTOM so the ParseNodes case reaches the check

Per review: ParseNodes derives the op type from registration->builtin_code,
and dummy_reg carries 0 (ADD), so the large_custom_options branch never ran
and the test passed vacuously. The test now resolves to a copy of dummy_reg
marked BuiltinOperator_CUSTOM with custom_name WRAP.
--
c0f8e2696af0b17230c690c689316f769c20b7c4 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: silent reporter, add.bin seed, stricter input zeroing

Per internal review: the model (and through it the interpreter builder) report
into a no-op ErrorReporter so malformed inputs do not write to stderr on every
iteration; tensorflow/lite/testdata/add.bin is loaded as a seed so the fuzzer
starts from a model that passes the structural check; the MSan zeroing skips
empty, persistent read-only, string, resource and variant inputs as well as
mmap-backed constants.
--
ba730ad80897d79270609605cc8027d7faec8a54 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: seed data and resource_loader dep for the TFLite fuzz target

add.bin from //tensorflow/lite is the seed corpus; resource_loader resolves its
runfiles path, the same way checkpoint_reader_fuzz does in this package.
--
c1be76e06447c1643b6b7f94f315b05a86eae532 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: initialize buffer_ptr before get_readonly_data

Per internal review: buffer_ptr is never left uninitialized if a path through
get_readonly_data returns kTfLiteOk without assigning it.

Merging this change closes #123123

FUTURE_COPYBARA_INTEGRATE_REVIEW=tensorflow/tensorflow#123123 from cybrdude:fuzz/tflite-interp-overflow-target c1be76e06447c1643b6b7f94f315b05a86eae532
PiperOrigin-RevId: 992803740
copybara-service Bot pushed a commit to google-ai-edge/LiteRT that referenced this pull request Oct 15, 2026
…oke fuzz target

Imported from GitHub PR tensorflow/tensorflow#123123

### What
Adds a FuzzTest target under `tensorflow/security/fuzzing/cc/` that loads a serialized `.tflite` model, builds an interpreter with the builtin op resolver, allocates tensors, and invokes. The OSS-Fuzz `tensorflow` build currently has no target that reaches TFLite model load / interpreter build / invoke, so this path is unfuzzed upstream.

### Why
`FlatBufferModel` -> `InterpreterBuilder` -> `AllocateTensors` -> `Invoke` parses attacker-supplied model structure, including external-buffer descriptors handled in `interpreter_builder.cc` (`get_readonly_data` / `ParseTensors`). That path performs a `uint64` `offset + size` addition and compares it against `allocation_->bytes()`. Adding coverage here lets OSS-Fuzz continuously exercise external-offset handling, tensor wiring, and builtin-kernel eval on malformed models, which is a class of input the existing core fuzzers do not reach.

### Change
`tensorflow/lite/core/interpreter_builder.cc`: the two external-offset bounds checks (`ParseNodes`, large_custom_options; `ParseTensors`, Buffer offset) use the subtraction form `offset > bytes || size > bytes - offset` instead of `offset + size > bytes`, so the uint64 sum can no longer wrap below `allocation_->bytes()`. The offset, size and allocation size are read once into locals. Requested in review (2026-09-16, 2026-10-03).

One new file plus a `tf_cc_fuzz_test` entry in `security/fuzzing/cc/BUILD`:

```python
tf_cc_fuzz_test(
    name = "tflite_interp_overflow_fuzz",
    srcs = ["tflite_interp_overflow_fuzz.cc"],
    deps = [
        "//tensorflow/lite:framework",
        "//tensorflow/lite/c:common",
        "//tensorflow/lite/kernels:builtin_ops",
    ],
)
```

The target uses only the public `//tensorflow/lite:framework`, `//tensorflow/lite/c:common` and `//tensorflow/lite/kernels:builtin_ops`, so no visibility changes are needed. The BUILD already loads `tf_cc_fuzz_test` (sibling targets use it). The harness returns early on any non-OK status so benign malformed inputs are not treated as findings; only a genuine memory-safety violation surfaces as a crash.

### Notes for review
- Dep closure is the public framework + builtin ops; if CI's linker wants a narrower or additional core target for the `tensorflow/lite/core/*` headers, I will adjust to match.
- Happy to add a seed corpus entry in a follow-up if the fuzzing infra owners prefer a seeded start.
Copybara import of the project:

--
653c77178c4839ca340b7179aae7b3222f708698 by cybrdude <layau@netguard24-7.com>:

Add TFLite interpreter-build fuzz target (model load + invoke)

The OSS-Fuzz tensorflow build has no fuzz target that reaches TFLite model load, interpreter build, and invoke. This adds one under security/fuzzing/cc, exercising interpreter_builder.cc external-buffer handling (get_readonly_data / ParseTensors), where an unchecked uint64 offset+size addition can bypass the allocation bounds check and set a wild read-only tensor pointer that is dereferenced during Invoke. Deps use the public //tensorflow/lite:framework and //tensorflow/lite/kernels:builtin_ops.

--
3eb7b68f504251c397ee07e253798d545d8027fc by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

Update tensorflow/security/fuzzing/cc/tflite_interp_overflow_fuzz.cc

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
--
141f0b41e740c9e29e7696597a5155a183b760cc by cybrdude <layau@netguard24-7.com>:

fuzz: zero-init runtime input tensors before Invoke to avoid MSan false positives

Addresses gemini-code-assist review. TFLite's arena allocator does not zero tensor memory, so kernels reading uninitialized runtime inputs raise MemorySanitizer use-of-uninitialized-value reports. Only runtime inputs are zeroed, not the crafted constant buffer that carries the finding.

--
75365a8d636f7ec401b59abd3575c5bef90b6814 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

fuzz(tflite): use public TFLite headers instead of core internal paths

Switches the three lite/core includes to the public forwarding headers exported by //tensorflow/lite:framework, per review. Including the core internal package directly caused the dependency layering check to fail. Include block re-sorted. No functional change to the target.
--
1c832e34eabd2b20398be3b12cf879b1fb8656f7 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: verify the model before building it in the TFLite fuzz target

Puts the harness on the supported ingress path for untrusted models: call
tflite::Verify() first and stop on rejection, then build and invoke. The
verifier owns the bounds checks on model metadata (tensorflow/tensorflow
#116631 review, Sep 18; checks landed in #123910), so inputs like the
uint64 external-offset wrap are rejected before ParseTensors, and the
verifier itself gets fuzz coverage. No change to interpreter_builder.cc.

VerifyAndBuildFromBuffer alone would not do this: without an extra_verifier
it runs only the flatbuffers structural check.
--
0111afab059556333fd8514eb5a833007636db66 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: depend on //tensorflow/lite/tools:verifier for the TFLite fuzz target

The target now calls tflite::Verify() before building the model.
--
f1ef1e37f86accdc21240fa43881db9d9019f633 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: do not zero kTfLiteMmapRo inputs in the TFLite fuzz target

A crafted model can declare a subgraph input backed by a constant buffer;
that tensor is kTfLiteMmapRo and points into the read-only model mapping,
so the MSan zero-fill would fault on it. Skip those; they are initialized
by the model already. Per review.
--
d481802c00127f44d8607a4841a620972fbb6e45 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: include <vector> and tensorflow/lite/c/common.h directly

The target uses TfLiteTensor, kTfLiteMmapRo, kTfLiteOk and std::vector;
include their headers instead of relying on transitive includes. Per review.
--
ae5b26db83123e460eb2658796ebce1601b9586e by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: depend on //tensorflow/lite/c:common for the TFLite fuzz target

Matches the direct include of tensorflow/lite/c/common.h. Per review.
--
72272479b6209adccbc3f7ba7660af22cb607769 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop the tflite::Verify() gate from the TFLite fuzz target

Per the Oct 3 review: malformed models go straight to InterpreterBuilder
so its own handling of model metadata is what gets fuzzed. Removes the
Verify() early return and its include; keeps the direct includes and the
kTfLiteMmapRo exclusion from the earlier rounds.
--
fd2f77c447ec35203d6a6db8c0e672ba68033507 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop the verifier dep from the TFLite fuzz target

The target no longer calls tflite::Verify(). Per the Oct 3 review.
--
db0ba16e36ff663983f49a9e5a7b544d227dd84b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

tflite: overflow-safe external-offset bounds checks in InterpreterBuilder

Both sites compared offset + size against allocation_->bytes() with two
uint64_t operands; a pair whose sum wraps passed the check and the pointer
computed from base + offset wrapped with it. Compare in the subtraction
form instead, so the check cannot overflow. Same predicate for every valid
(offset, size); only the wrapping pairs change outcome.

Requested on this PR on Sep 16 and again on Oct 3 so the fuzz target in
the same change does not report a known crash. Re-applies the change from
#116631 that was reverted on Sep 18; the verifier-side check from #123910
stays, so both layers reject the wrap.
--
72567388959f68b271686cd87b6721845457bdee by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

tflite: restore master ParseQuantization dropped by the previous commit

db0ba16 was applied to a stale copy of interpreter_builder.cc and removed
56 lines of ParseQuantization that master had added. This puts the file
back to master plus only the two bounds-check hunks.
--
c624d970b2667b4e96aa54579552600a8ed38e44 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: enter through VerifyAndBuildFromBuffer in the TFLite fuzz target

Per review: models now pass the flatbuffers structural verifier before
InterpreterBuilder sees them. No extra_verifier, so tflite::Verify() is not
on this path; the external-offset bounds checks in interpreter_builder.cc
remain the check exercised for wrapped offsets.
--
6b637521dbde57526747589a95a68d9e0bcba52d by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: cache offset, size and allocation bytes in the external-offset checks

Per review: hoist op->large_custom_options_offset(), the option size and
allocation_->bytes() into locals in ParseNodes, and allocation_->bytes() and
buffer->size() in ParseTensors, so the checks do not repeat the flatbuffer
accessors and the virtual call. No behaviour change.
--
267042d6b57f914b92baca79a17dcb2b5bc4ca63 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: drop unused <vector> include from the TFLite fuzz target

Per review (IWYU): the file never names std::vector.
--
e9612f26a1f95d898257ad3c0644fc31e8edeb1b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: restore <vector> include in the TFLite fuzz target

Per the corrected review: interpreter->inputs() returns const std::vector<int>&
and the range-for over it is a use, so IWYU wants the header named here.
--
0193d170dba8cca45f7189170265a85453fe324e by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: regression tests for wrapping external offsets in InterpreterBuilder

Per review: two deterministic tests in model_test.cc. Each builds a model whose
(offset, size) pair sums past uint64 to 9, loads it with BuildFromBuffer so the
verifier is not in the way, and expects InterpreterBuilder to reject it. One
covers the Buffer offset path in ParseTensors (tensor bytes equal the claimed
size so the size check cannot mask the result), the other the
large_custom_options path in ParseNodes.
--
2c10c04377c30d05add06b08c3c1371bfd94f54b by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: mark the test registration CUSTOM so the ParseNodes case reaches the check

Per review: ParseNodes derives the op type from registration->builtin_code,
and dummy_reg carries 0 (ADD), so the large_custom_options branch never ran
and the test passed vacuously. The test now resolves to a copy of dummy_reg
marked BuiltinOperator_CUSTOM with custom_name WRAP.
--
c0f8e2696af0b17230c690c689316f769c20b7c4 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: silent reporter, add.bin seed, stricter input zeroing

Per internal review: the model (and through it the interpreter builder) report
into a no-op ErrorReporter so malformed inputs do not write to stderr on every
iteration; tensorflow/lite/testdata/add.bin is loaded as a seed so the fuzzer
starts from a model that passes the structural check; the MSan zeroing skips
empty, persistent read-only, string, resource and variant inputs as well as
mmap-backed constants.
--
ba730ad80897d79270609605cc8027d7faec8a54 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

security/fuzzing: seed data and resource_loader dep for the TFLite fuzz target

add.bin from //tensorflow/lite is the seed corpus; resource_loader resolves its
runfiles path, the same way checkpoint_reader_fuzz does in this package.
--
c1be76e06447c1643b6b7f94f315b05a86eae532 by Layau Eulizier Jr <130326664+cybrdude@users.noreply.github.com>:

lite: initialize buffer_ptr before get_readonly_data

Per internal review: buffer_ptr is never left uninitialized if a path through
get_readonly_data returns kTfLiteOk without assigning it.

Merging this change closes #123123

PiperOrigin-RevId: 999903734
@copybara-service
copybara-service Bot merged commit 0cc39b9 into tensorflow:master Oct 15, 2026
17 checks passed
@github-project-automation github-project-automation Bot moved this from Reviewer Requested Changes to Merged in PR Queue Oct 15, 2026
@cybrdude
cybrdude deleted the fuzz/tflite-interp-overflow-target branch October 15, 2026 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready to pull PR ready for merge process size:M CL Change Size: Medium

Projects

Status: Merged

Development

Successfully merging this pull request may close these issues.

8 participants