Repository navigation
security/fuzzing: add TFLite model load + interpreter invoke fuzz target - #123123
copybara-service[bot] merged 23 commits into
Conversation
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.
There was a problem hiding this comment.
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.
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.
ratgr
left a comment
There was a problem hiding this comment.
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.
ratgr
left a comment
There was a problem hiding this comment.
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.
dmiltr3
left a comment
There was a problem hiding this comment.
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.
|
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.
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. |
…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
…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
…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
…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
…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
left a comment
There was a problem hiding this comment.
Hi @cybrdude, thank you for your pull request!
During internal review, our reviewers had a few suggestions for your changes:
-
In
tensorflow/security/fuzzing/cc/tflite_interp_overflow_fuzz.ccaround line 49:
Reviewer feedback:VerifyAndBuildFromBufferandInterpreterBuilderdefault toDefaultErrorReporter(), which writes error messages tostderrwhenever a model fails validation or parsing. Because the vast majority of fuzz inputs are malformed, logging tostderron every iteration can cause significant I/O overhead and log spam.
Consider passing a no-opErrorReporter(similar toNullErrorReporterintensorflow/lite/testing/fuzzing/model_fuzzer_test.cc) to bothVerifyAndBuildFromBufferandInterpreterBuilder. -
In
tensorflow/security/fuzzing/cc/tflite_interp_overflow_fuzz.ccaround line 63:
Reviewer feedback: In addition totensor->allocation_type != kTfLiteMmapRo, consider also guarding againsttensor->bytes == 0and non-POD tensor types (kTfLiteString,kTfLiteResource,kTfLiteVariant) or read-only persistent allocations (kTfLitePersistentRo) before callingstd::memset(tensor->data.raw, 0, tensor->bytes). -
In
tensorflow/security/fuzzing/cc/tflite_interp_overflow_fuzz.cc:
Reviewer feedback: Consider providing a minimal valid.tfliteseed corpus via.WithSeeds(...)(for example, loadingtensorflow/lite/testdata/add.binas done intensorflow/lite/testing/fuzzing/model_fuzzer_test.cc) so the fuzzer can bypass the initial FlatBuffer structural check and immediately exerciseInterpreterBuilderandInvoke. -
In
tensorflow/lite/core/interpreter_builder.ccaround line 835:
Reviewer feedback: Optional nit: Consider initializingconst char* buffer_ptr = nullptr;sobuffer_ptris never left uninitialized ifget_readonly_datatakes a fallthrough path that returnskTfLiteOkwithout 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.
…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.
|
@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. |
…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
…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
…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
…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
What
Adds a FuzzTest target under
tensorflow/security/fuzzing/cc/that loads a serialized.tflitemodel, builds an interpreter with the builtin op resolver, allocates tensors, and invokes. The OSS-Fuzztensorflowbuild currently has no target that reaches TFLite model load / interpreter build / invoke, so this path is unfuzzed upstream.Why
FlatBufferModel->InterpreterBuilder->AllocateTensors->Invokeparses attacker-supplied model structure, including external-buffer descriptors handled ininterpreter_builder.cc(get_readonly_data/ParseTensors). That path performs auint64offset + sizeaddition and compares it againstallocation_->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 formoffset > bytes || size > bytes - offsetinstead ofoffset + size > bytes, so the uint64 sum can no longer wrap belowallocation_->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_testentry insecurity/fuzzing/cc/BUILD:The target uses only the public
//tensorflow/lite:framework,//tensorflow/lite/c:commonand//tensorflow/lite/kernels:builtin_ops, so no visibility changes are needed. The BUILD already loadstf_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
tensorflow/lite/core/*headers, I will adjust to match.