Skip to content

Commit 46adb06

Browse files
committed
Validate num_channels against tensor sizes in FILTER_BANK kernel
FilterBankInit reads num_channels from the op's init flexbuffer and allocates a work area of (num_channels + 1) * sizeof(uint64_t); FilterbankAccumulateChannels then loops num_channels + 1 times, indexing channel_frequency_starts[i], channel_weight_starts[i] and channel_widths[i] and writing num_channels output elements. FilterBankPrepare validated only the rank and type of each tensor, never that num_channels is consistent with the per-channel metadata tensor lengths or the output length, so a model whose flexbuffer declares a num_channels larger than those tensors caused an out-of-bounds read. On targets where size_t is 32 bits, a large num_channels also overflows the work-area size computation into a tiny allocation that the accumulation loop then writes past (out-of-bounds write). Both were confirmed with AddressSanitizer. Reject an out-of-range num_channels in FilterBankInit, and validate in FilterBankPrepare that num_channels > 0, that the metadata tensors each have num_channels + 1 elements, and that the output has num_channels elements (mirroring the recent fix for the sibling FilterBankSpectralSubtraction kernel), plus a regression test.
1 parent 074b75f commit 46adb06

2 files changed

Lines changed: 98 additions & 0 deletions

File tree

signal/micro/kernels/filter_bank.cc

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ void* FilterBankInit(TfLiteContext* context, const char* buffer,
6161
length);
6262
params->config.num_channels = fbw.ElementAsInt32(kNumChannelsIndex);
6363

64+
// Reject a num_channels that is non-positive or large enough to overflow the
65+
// work-area size computation below. Without this, a crafted model can wrap
66+
// the multiplication (notably where size_t is 32 bits) into a tiny
67+
// allocation, which FilterbankAccumulateChannels then writes past.
68+
if (params->config.num_channels <= 0 ||
69+
static_cast<size_t>(params->config.num_channels) >
70+
SIZE_MAX / sizeof(uint64_t) - 1) {
71+
return nullptr;
72+
}
73+
6474
params->work_area = static_cast<uint64_t*>(context->AllocatePersistentBuffer(
6575
context, (params->config.num_channels + 1) * sizeof(uint64_t)));
6676

@@ -99,27 +109,46 @@ TfLiteStatus FilterBankPrepare(TfLiteContext* context, TfLiteNode* node) {
99109
TF_LITE_ENSURE(context, input != nullptr);
100110
TF_LITE_ENSURE_EQ(context, NumDimensions(input), 1);
101111
TF_LITE_ENSURE_TYPES_EQ(context, input->type, kTfLiteInt16);
112+
const int channel_frequency_starts_size = ElementCount(*input->dims);
102113
micro_context->DeallocateTempTfLiteTensor(input);
103114

104115
input = micro_context->AllocateTempInputTensor(node, kChWeightStartsTensor);
105116
TF_LITE_ENSURE(context, input != nullptr);
106117
TF_LITE_ENSURE_EQ(context, NumDimensions(input), 1);
107118
TF_LITE_ENSURE_TYPES_EQ(context, input->type, kTfLiteInt16);
119+
const int channel_weight_starts_size = ElementCount(*input->dims);
108120
micro_context->DeallocateTempTfLiteTensor(input);
109121

110122
input = micro_context->AllocateTempInputTensor(node, kChannelWidthsTensor);
111123
TF_LITE_ENSURE(context, input != nullptr);
112124
TF_LITE_ENSURE_EQ(context, NumDimensions(input), 1);
113125
TF_LITE_ENSURE_TYPES_EQ(context, input->type, kTfLiteInt16);
126+
const int channel_widths_size = ElementCount(*input->dims);
114127
micro_context->DeallocateTempTfLiteTensor(input);
115128

116129
TfLiteTensor* output =
117130
micro_context->AllocateTempOutputTensor(node, kOutputTensor);
118131
TF_LITE_ENSURE(context, output != nullptr);
119132
TF_LITE_ENSURE_EQ(context, NumDimensions(output), 1);
120133
TF_LITE_ENSURE_TYPES_EQ(context, output->type, kTfLiteUInt64);
134+
const int output_size = ElementCount(*output->dims);
121135
micro_context->DeallocateTempTfLiteTensor(output);
122136

137+
// Validate that num_channels (from the init flexbuffer) is consistent with
138+
// the per-channel metadata tensors and the output, to prevent out-of-bounds
139+
// access in FilterbankAccumulateChannels. That loop reads indices
140+
// [0, num_channels] (num_channels + 1 elements) of channel_frequency_starts,
141+
// channel_weight_starts and channel_widths, and writes num_channels output
142+
// elements (work_area[1 .. num_channels]).
143+
auto* params = reinterpret_cast<TFLMSignalFilterBankParams*>(node->user_data);
144+
TF_LITE_ENSURE(context, params != nullptr);
145+
const int num_channels = params->config.num_channels;
146+
TF_LITE_ENSURE(context, num_channels > 0);
147+
TF_LITE_ENSURE_EQ(context, channel_frequency_starts_size, num_channels + 1);
148+
TF_LITE_ENSURE_EQ(context, channel_weight_starts_size, num_channels + 1);
149+
TF_LITE_ENSURE_EQ(context, channel_widths_size, num_channels + 1);
150+
TF_LITE_ENSURE_EQ(context, output_size, num_channels);
151+
123152
return kTfLiteOk;
124153
}
125154

signal/micro/kernels/filter_bank_test.cc

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,4 +256,73 @@ TEST(FilterBankTest, FilterBankTest16Channel) {
256256
g_gen_data_size_filter_bank_16_channel, output));
257257
}
258258

259+
// PoC: 17-element (16-channel) side tensors but a flexbuffer declaring
260+
// num_channels = 32. FilterBankPrepare never validates num_channels against
261+
// the tensor element counts, so FilterbankAccumulateChannels loops
262+
// num_channels + 1 = 33 times and reads channel_weight_starts[i] /
263+
// channel_widths[i] past the end of the 17-element buffers (heap OOB read).
264+
TEST(FilterBankTest, FilterBankNumChannelsOverflowPoc) {
265+
int input1_shape[] = {1, 129};
266+
int input2_shape[] = {1, 59};
267+
int input3_shape[] = {1, 59};
268+
int input4_shape[] = {1, 17};
269+
int input5_shape[] = {1, 17};
270+
int input6_shape[] = {1, 17};
271+
int output_shape[] = {1, 16};
272+
273+
uint64_t output[16];
274+
275+
const uint32_t input1[] = {
276+
645050, 4644, 3653, 24262, 56660, 43260, 50584, 57902, 31702, 5401,
277+
45555, 34852, 8518, 43556, 13358, 19350, 40221, 18017, 27284, 64491,
278+
60099, 17863, 11001, 29076, 32666, 65268, 50947, 28694, 32377, 30014,
279+
25607, 22547, 45086, 10654, 46797, 8622, 47348, 43085, 5747, 51544,
280+
50364, 6208, 20696, 59782, 14429, 60125, 37079, 32673, 63457, 60142,
281+
34042, 11280, 1874, 33734, 62118, 13766, 54398, 47818, 50976, 46930,
282+
25906, 59441, 25958, 59136, 1756, 18652, 29213, 13379, 51845, 1207,
283+
55626, 27108, 43771, 35236, 3374, 40959, 47707, 41540, 34282, 27094,
284+
36329, 13593, 65257, 47006, 46857, 1114, 37106, 18738, 25969, 15461,
285+
2842, 36470, 32489, 61622, 23613, 29624, 32820, 30438, 9543, 6767,
286+
23037, 52896, 12059, 32264, 11575, 42400, 43344, 27511, 16712, 6877,
287+
4910, 50047, 61569, 57237, 48558, 2310, 22192, 7874, 46141, 64056,
288+
61997, 7298, 31372, 25316, 683, 58940, 18755, 17898, 19196};
289+
290+
const int16_t input2[] = {
291+
-2210, 1711, 3237, 1247, 2507, 61, 1019, 899, 206, 146, 2849, 2756,
292+
1260, 1280, 1951, 213, 617, 2047, 211, 347, 2821, 3747, 150, 1924,
293+
3962, 942, 1430, 2678, 993, 308, 3364, 2491, 954, 1308, 879, 3950,
294+
1, 3556, 3628, 2104, 78, 1298, 1080, 342, 1337, 1639, 2352, 829,
295+
1358, 2498, 1647, 2507, 3816, 3767, 3735, 1155, 2221, 2196, 1160};
296+
297+
const int16_t input3[] = {
298+
408, 3574, 1880, 2561, 2011, 3394, 1019, 445, 3901, 343, 1874, 3846,
299+
3566, 1830, 327, 111, 623, 1037, 2803, 1947, 1518, 661, 3239, 2351,
300+
1257, 269, 1574, 3431, 3972, 2487, 2181, 1458, 552, 717, 679, 1031,
301+
1738, 1782, 128, 2242, 353, 1460, 3305, 1424, 3813, 2895, 164, 272,
302+
3886, 3135, 141, 747, 3233, 1478, 2612, 3837, 3271, 73, 1746};
303+
304+
const int16_t input4[] = {5, 6, 7, 9, 11, 12, 14, 16, 18,
305+
20, 22, 25, 27, 30, 32, 35, 33};
306+
307+
const int16_t input5[] = {0, 1, 2, 4, 6, 7, 9, 11, 13,
308+
15, 17, 20, 22, 25, 27, 30, 33};
309+
310+
const int16_t input6[] = {1, 1, 2, 2, 1, 2, 2, 2, 2, 2, 3, 2, 3, 2, 3, 3, 3};
311+
312+
const uint64_t golden[] = {104199304, 407748384, 206363744, 200989269,
313+
52144406, 230780884, 174394190, 379684049,
314+
94840835, 57788823, 531528204, 318265707,
315+
263149795, 188110467, 501443259, 200747781};
316+
317+
// With the fix in FilterBankPrepare, the num_channels/tensor-size mismatch is
318+
// rejected before Eval, so no out-of-bounds access occurs.
319+
EXPECT_EQ(
320+
kTfLiteError,
321+
tflite::testing::TestFilterBank(
322+
input1_shape, input1, input2_shape, input2, input3_shape, input3,
323+
input4_shape, input4, input5_shape, input5, input6_shape, input6,
324+
output_shape, golden, g_gen_data_filter_bank_32_channel,
325+
g_gen_data_size_filter_bank_32_channel, output));
326+
}
327+
259328
TF_LITE_MICRO_TESTS_MAIN

0 commit comments

Comments
 (0)