Skip to content

Commit 5b572b5

Browse files
dbortfacebook-github-bot
authored andcommitted
Fix allocation-size-too-big crash in prepare_input_tensors
Summary: (Adapted from an LLM-suggested fix for a fuzzer-discovered crash) The crash is an allocation-size-too-big error that occurs when the `prepare_input_tensors` function attempts to allocate an excessively large amount of memory for the `inputs` array. This crash is caused by the function's inability to handle large numbers of inputs, resulting in an attempt to allocate a huge amount of memory that exceeds the system's limits. The root cause of the crash is the lack of bounds checking on the `num_inputs` variable, which allows the function to attempt to allocate an arbitrarily large amount of memory. This is exacerbated by the fact that the function allocates memory for each input tensor separately, without checking the total size of all tensors before allocating memory for the `inputs` array. The patch fixes the crash by adding bounds checking on the `num_inputs` variable and calculating the total size of all tensors before allocating memory for the `inputs` array. Differential Revision: D68876117
1 parent 440a3ac commit 5b572b5

File tree

2 files changed

+55
-5
lines changed

2 files changed

+55
-5
lines changed

extension/runner_util/inputs.cpp

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,37 @@ using executorch::runtime::TensorInfo;
2222
namespace executorch {
2323
namespace extension {
2424

25-
Result<BufferCleanup> prepare_input_tensors(Method& method) {
25+
Result<BufferCleanup> prepare_input_tensors(
26+
Method& method,
27+
size_t max_total_allocation_size,
28+
size_t max_inputs) {
2629
MethodMeta method_meta = method.method_meta();
2730
size_t num_inputs = method_meta.num_inputs();
28-
size_t num_allocated = 0;
31+
32+
// A large number of small allocations could exhaust the heap even if the
33+
// total size is smaller than the limit.
34+
ET_CHECK_OR_RETURN_ERROR(
35+
num_inputs <= max_inputs,
36+
InvalidProgram,
37+
"Too many inputs: %zu > %zu",
38+
num_inputs,
39+
max_inputs);
40+
41+
// Allocate memory for the inputs array
2942
void** inputs = (void**)malloc(num_inputs * sizeof(void*));
43+
ET_CHECK_OR_RETURN_ERROR(
44+
inputs != nullptr,
45+
MemoryAllocationFailed,
46+
"malloc(%zd) failed",
47+
num_inputs * sizeof(void*));
3048

49+
// Allocate memory for each input tensor.
50+
size_t total_size = 0;
51+
size_t num_allocated = 0;
3152
for (size_t i = 0; i < num_inputs; i++) {
3253
auto tag = method_meta.input_tag(i);
3354
if (!tag.ok()) {
55+
// The BufferCleanup will free the inputs when it goes out of scope.
3456
BufferCleanup cleanup({inputs, num_allocated});
3557
return tag.error();
3658
}
@@ -40,10 +62,29 @@ Result<BufferCleanup> prepare_input_tensors(Method& method) {
4062
}
4163
Result<TensorInfo> tensor_meta = method_meta.input_tensor_meta(i);
4264
if (!tensor_meta.ok()) {
65+
BufferCleanup cleanup({inputs, num_allocated});
4366
return tensor_meta.error();
4467
}
4568
// This input is a tensor. Allocate a buffer for it.
46-
void* data_ptr = malloc(tensor_meta->nbytes());
69+
size_t tensor_size = tensor_meta->nbytes();
70+
total_size += tensor_size;
71+
if (total_size > max_total_allocation_size) {
72+
ET_LOG(
73+
Error,
74+
"Allocating %zu bytes for input %zu would exceed "
75+
"max_total_allocation_size %zu",
76+
tensor_size,
77+
i,
78+
max_total_allocation_size);
79+
BufferCleanup cleanup({inputs, num_allocated});
80+
return Error::InvalidProgram;
81+
}
82+
void* data_ptr = malloc(tensor_size);
83+
if (data_ptr == nullptr) {
84+
ET_LOG(Error, "malloc(%zu) failed for input %zu", tensor_size, i);
85+
BufferCleanup cleanup({inputs, num_allocated});
86+
return Error::MemoryAllocationFailed;
87+
}
4788
inputs[num_allocated++] = data_ptr;
4889

4990
// Create the tensor and set it as the input.
@@ -52,11 +93,11 @@ Result<BufferCleanup> prepare_input_tensors(Method& method) {
5293
if (err != Error::Ok) {
5394
ET_LOG(
5495
Error, "Failed to prepare input %zu: 0x%" PRIx32, i, (uint32_t)err);
55-
// The BufferCleanup will free the inputs when it goes out of scope.
5696
BufferCleanup cleanup({inputs, num_allocated});
5797
return err;
5898
}
5999
}
100+
60101
return BufferCleanup({inputs, num_allocated});
61102
}
62103

extension/runner_util/inputs.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,22 @@ class BufferCleanup final {
5656
* not modify inputs that are not Tensors.
5757
*
5858
* @param[in] method The Method that owns the inputs to prepare.
59+
* @param[in] max_total_allocation_size The maximum total size in bytes of all
60+
* input tensors. If the total size of all inputs exceeds this, an error is
61+
* returned. This prevents allocating too much memory if the PTE file is
62+
* malformed.
63+
* @param[in] max_inputs The maximum number of inputs to allocate. If the number
64+
* of inputs exceeds this, an error is returned. This prevents allocating
65+
* too much memory if the PTE file is malformed.
5966
*
6067
* @returns On success, an object that owns any allocated tensor memory. It must
6168
* remain alive when calling `method->execute()`.
6269
* @returns An error on failure.
6370
*/
6471
executorch::runtime::Result<BufferCleanup> prepare_input_tensors(
65-
executorch::runtime::Method& method);
72+
executorch::runtime::Method& method,
73+
size_t max_total_allocation_size = 1024 * 1024 * 1024,
74+
size_t max_inputs = 1024);
6675

6776
namespace internal {
6877
/**

0 commit comments

Comments
 (0)