Repository navigation
PR #123123: security/fuzzing: add TFLite model load + interpreter invoke fuzz target - #10506
Merged
Merged
Conversation
copybara-service
Bot
force-pushed
the
test_992803740
branch
8 times, most recently
from
October 8, 2026 06:03
b560b0d to
217e988
Compare
copybara-service
Bot
force-pushed
the
test_992803740
branch
3 times, most recently
from
October 15, 2026 06:32
eb13d71 to
9347035
Compare
…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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: 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 <[email protected]>: lite: initialize buffer_ptr before get_readonly_data Per internal review: buffer_ptr is never left uninitialized if a path through get_readonly_data returns kTfLiteOk without assigning it. Merging this change closes #123123 PiperOrigin-RevId: 999903734
copybara-service
Bot
force-pushed
the
test_992803740
branch
from
October 15, 2026 06:56
9347035 to
97fbc68
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR #123123: security/fuzzing: add TFLite model load + interpreter invoke fuzz target
Imported from GitHub PR tensorflow/tensorflow#123123
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.Copybara import of the project:
--
653c77178c4839ca340b7179aae7b3222f708698 by cybrdude [email protected]:
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 [email protected]:
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 [email protected]:
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 [email protected]:
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 [email protected]:
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 [email protected]:
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 [email protected]:
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 [email protected]:
security/fuzzing: include 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 [email protected]:
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 [email protected]:
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 [email protected]:
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 [email protected]:
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 [email protected]:
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 [email protected]:
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 [email protected]:
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 [email protected]:
security/fuzzing: drop unused include from the TFLite fuzz target
Per review (IWYU): the file never names std::vector.
e9612f26a1f95d898257ad3c0644fc31e8edeb1b by Layau Eulizier Jr [email protected]:
security/fuzzing: restore include in the TFLite fuzz target
Per the corrected review: interpreter->inputs() returns const std::vector&
and the range-for over it is a use, so IWYU wants the header named here.
0193d170dba8cca45f7189170265a85453fe324e by Layau Eulizier Jr [email protected]:
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 [email protected]:
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 [email protected]:
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 [email protected]:
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 [email protected]:
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