Skip to content

Commit aa17211

Browse files
JacobSzwejbkafacebook-github-bot
authored andcommitted
Validate kTensorDimensionLimit (#12684)
Summary: Need to check this limit at deserialization since the ops rely on it Reviewed By: larryliu0820 Differential Revision: D78675986
1 parent 413dee4 commit aa17211

File tree

3 files changed

+236
-96
lines changed

3 files changed

+236
-96
lines changed

runtime/executor/targets.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ def define_common_targets():
133133
],
134134
deps = [
135135
"//executorch/schema:program",
136+
"//executorch/runtime/core/exec_aten/util:tensor_dimension_limit"
136137
],
137138
visibility = [
138139
"//executorch/runtime/executor/...",

runtime/executor/tensor_parser_aten.cpp

Lines changed: 227 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -8,126 +8,257 @@
88

99
#include <executorch/runtime/executor/tensor_parser.h>
1010

11-
#include <executorch/runtime/core/exec_aten/util/dim_order_util.h>
12-
#include <executorch/runtime/core/exec_aten/util/scalar_type_util.h>
13-
#include <executorch/runtime/core/named_data_map.h>
11+
#include <executorch/runtime/core/evalue.h>
12+
#include <executorch/runtime/core/exec_aten/exec_aten.h>
1413
#include <executorch/runtime/executor/memory_manager.h>
1514
#include <executorch/runtime/executor/program.h>
1615
#include <executorch/runtime/platform/profiler.h>
1716
#include <executorch/schema/program_generated.h>
1817

19-
#include <ATen/ATen.h> // @donotremove @manual=//caffe2/aten:ATen-core
20-
2118
namespace executorch {
22-
// This file is only used in ATen mode, so we use the runtime_aten namespace.
23-
namespace runtime {
24-
namespace aten {
19+
namespace ET_RUNTIME_NAMESPACE {
2520
namespace deserialization {
2621

22+
using executorch::aten::ScalarType;
23+
// Provides access to private Program methods.
24+
class TensorParser final {
25+
public:
26+
ET_NODISCARD static Error load_mutable_subsegment_into(
27+
const Program* program,
28+
size_t mutable_data_segments_index,
29+
size_t offset_index,
30+
size_t size,
31+
void* buffer) {
32+
return program->load_mutable_subsegment_into(
33+
mutable_data_segments_index, offset_index, size, buffer);
34+
}
35+
};
36+
2737
namespace {
2838

29-
void deleteNothing(void*);
30-
void deleteNothing(void*) {}
39+
// Retrieve the buffer specified by the allocation_info
40+
ET_NODISCARD Result<void*> getMemPlannedPtr(
41+
const executorch_flatbuffer::AllocationDetails* allocation_info,
42+
size_t nbytes,
43+
HierarchicalAllocator* allocator) {
44+
// Normal non-constant Tensor. Allocate data using mem_id and offset.
3145

46+
// TODO(T142455629): make the allocator actually id based and not indexed
47+
// based. -1 is a hack to get the memory ids 0 aligned because previously
48+
// 0 was reserved
49+
const uint32_t memory_id = allocation_info->memory_id() - 1;
50+
51+
// Originally this field was a single uint32_t, but we need 64 bits for
52+
// larger models. To preserve backwards compatibility, the high bits are
53+
// managed in a separate uint32_t field.
54+
const uint32_t memory_offset_low = allocation_info->memory_offset_low();
55+
const uint32_t memory_offset_high = allocation_info->memory_offset_high();
56+
57+
size_t memory_offset = memory_offset_low;
58+
if ((sizeof(size_t) > sizeof(uint32_t)) && (memory_offset_high > 0)) {
59+
// The compiler should remove this always-true check on 64-bit systems.
60+
ET_CHECK_OR_RETURN_ERROR(
61+
sizeof(size_t) >= sizeof(uint64_t),
62+
NotSupported,
63+
"size_t cannot hold memory offset 0x%08" PRIx32 ".%08" PRIx32,
64+
memory_offset_high,
65+
memory_offset_low);
66+
memory_offset |= static_cast<size_t>(memory_offset_high) << 32;
67+
}
68+
return allocator->get_offset_address(memory_id, memory_offset, nbytes);
69+
}
3270
} // namespace
3371

34-
Result<at::Tensor> parseTensor(
35-
const Program* program,
36-
MemoryManager* memory_manager,
37-
const executorch_flatbuffer::Tensor* s_tensor,
38-
const NamedDataMap* named_data_map,
39-
Span<NamedData> external_constants) {
40-
EXECUTORCH_SCOPE_PROF("TensorParser::parseTensor");
72+
ET_NODISCARD Result<BoxedEvalueList<executorch::aten::Tensor>> parseTensorList(
73+
const flatbuffers::Vector<int32_t>* tensor_indices,
74+
EValue* values,
75+
size_t values_len,
76+
MemoryManager* memory_manager) {
77+
EXECUTORCH_SCOPE_PROF("TensorParser::parseTensorList");
4178

42-
ET_CHECK_OR_RETURN_ERROR(
43-
s_tensor->storage_offset() == 0,
44-
NotSupported,
45-
"Non-zero storage offset %" PRId32 " not supported",
46-
s_tensor->storage_offset());
79+
auto* tensor_list =
80+
memory_manager->method_allocator()
81+
->allocateList<executorch::aten::Tensor>(tensor_indices->size());
82+
if (tensor_list == nullptr) {
83+
return Error::MemoryAllocationFailed;
84+
}
85+
auto* evalp_list = memory_manager->method_allocator()->allocateList<EValue*>(
86+
tensor_indices->size());
87+
if (evalp_list == nullptr) {
88+
return Error::MemoryAllocationFailed;
89+
}
4790

48-
// get metadata
49-
at::ScalarType type = static_cast<at::ScalarType>(s_tensor->scalar_type());
50-
ET_CHECK_OR_RETURN_ERROR(
51-
isValid(type),
52-
InvalidProgram,
53-
"Invalid ScalarType %" PRId8,
54-
static_cast<int8_t>(type));
55-
auto options = at::CPU(type).options();
91+
// For each tensor index look up the corresponding Tensor (which has been
92+
// already allocated) and stick it in the list.
93+
size_t output_idx = 0;
94+
for (int32_t tensor_index : *tensor_indices) {
95+
ET_CHECK_OR_RETURN_ERROR(
96+
tensor_index >= 0 && static_cast<size_t>(tensor_index) < values_len,
97+
InvalidProgram,
98+
"Invalid value index %" PRId32 " for TensorList",
99+
tensor_index);
56100

57-
ET_CHECK_OR_RETURN_ERROR(
58-
s_tensor->sizes() != nullptr, InvalidProgram, "Missing sizes field");
59-
size_t ndim = s_tensor->sizes()->size();
101+
// Placement new as the list elements are not initialized, so calling
102+
// copy assignment is not defined if it's non trivial.
103+
new (&tensor_list[output_idx]) executorch::aten::Tensor(
104+
values[static_cast<size_t>(tensor_index)].toTensor());
105+
evalp_list[output_idx] = &values[static_cast<size_t>(tensor_index)];
106+
output_idx++;
107+
}
108+
109+
return BoxedEvalueList<executorch::aten::Tensor>(
110+
evalp_list, tensor_list, tensor_indices->size());
111+
}
60112

113+
ET_NODISCARD Error validateTensorLayout(
114+
const executorch_flatbuffer::Tensor* s_tensor,
115+
const TensorLayout& expected_layout) {
61116
ET_CHECK_OR_RETURN_ERROR(
62-
s_tensor->dim_order() != nullptr,
63-
InvalidProgram,
64-
"Missing dim_order field");
117+
static_cast<ScalarType>(s_tensor->scalar_type()) ==
118+
expected_layout.scalar_type(),
119+
InvalidExternalData,
120+
"Scalar type mismatch. Expected %hhd, got %hhd.",
121+
static_cast<int8_t>(s_tensor->scalar_type()),
122+
static_cast<int8_t>(expected_layout.scalar_type()));
123+
int dim = s_tensor->sizes()->size();
65124
ET_CHECK_OR_RETURN_ERROR(
66-
s_tensor->dim_order()->size() == ndim,
67-
InvalidProgram,
68-
"dim_order size %" PRIu32 " != ndim %zu",
69-
s_tensor->dim_order()->size(),
70-
ndim);
71-
72-
// convert int32 in serialization to int64 for aten
73-
std::vector<int64_t> sizes(
74-
s_tensor->sizes()->begin(), s_tensor->sizes()->end());
75-
std::vector<int64_t> strides(ndim);
76-
auto status = dim_order_to_stride(
77-
s_tensor->sizes()->data(),
78-
s_tensor->dim_order()->data(),
79-
ndim,
80-
strides.data());
125+
dim >= 0, InvalidExternalData, "Dim is negative: %d", dim)
81126
ET_CHECK_OR_RETURN_ERROR(
82-
status == Error::Ok,
83-
Internal,
84-
"dim_order_to_stride returned invalid status");
85-
86-
// Create a tensor without data first so we can find its expected size before
87-
// getting its memory.
88-
at::Tensor tensor = at::from_blob(
89-
/*data=*/nullptr,
90-
sizes,
91-
strides,
92-
/*storage_offset=*/0,
93-
deleteNothing,
94-
options);
95-
96-
if (s_tensor->shape_dynamism() ==
97-
executorch_flatbuffer::TensorShapeDynamism::DYNAMIC_UNBOUND) {
98-
// Provide fully dynamic tensors with an allocator so they can be resized
99-
// within aten kernels.
100-
auto impl = tensor.unsafeGetTensorImpl();
101-
at::StorageImpl* storage = impl->unsafe_storage().unsafeGetStorageImpl();
102-
storage->set_allocator(at::getCPUAllocator());
103-
storage->set_resizable(true);
104-
storage->set_nbytes(0);
105-
impl->set_sizes_contiguous(0);
106-
// Leave the data as nullptr since it will be reallocated.
107-
} else {
108-
// Now that we know how big the tensor is, find and assign its memory.
109-
Result<void*> data_ptr = getTensorDataPtr(
110-
s_tensor,
111-
program,
112-
tensor.nbytes(),
113-
memory_manager->planned_memory(),
114-
named_data_map,
115-
external_constants);
116-
if (!data_ptr.ok()) {
117-
ET_LOG(
118-
Error,
119-
"getTensorDataPtr() failed: 0x%" PRIx32,
120-
static_cast<uint32_t>(data_ptr.error()));
121-
return data_ptr.error();
127+
static_cast<size_t>(dim) == expected_layout.sizes().size(),
128+
InvalidExternalData,
129+
"Dim mismatch. Expected %d, got %zu.",
130+
dim,
131+
expected_layout.sizes().size());
132+
for (int i = 0; i < dim; i++) {
133+
ET_CHECK_OR_RETURN_ERROR(
134+
s_tensor->sizes()->Get(i) == expected_layout.sizes()[i],
135+
InvalidExternalData,
136+
"Sizes mismatch. Expected %d, got %d for size at index %d.",
137+
s_tensor->sizes()->Get(i),
138+
expected_layout.sizes()[i],
139+
i);
140+
ET_CHECK_OR_RETURN_ERROR(
141+
s_tensor->dim_order()->Get(i) == expected_layout.dim_order()[i],
142+
InvalidExternalData,
143+
"Dim order mismatch. Expected %d, got %d for dim at index %d.",
144+
s_tensor->dim_order()->Get(i),
145+
expected_layout.dim_order()[i],
146+
i);
147+
}
148+
return Error::Ok;
149+
}
150+
151+
// Check if key exists in entries. If it does, return a pointer to the entry
152+
// otherwise return a nullptr.
153+
NamedData* get_data_by_key(const char* key, Span<NamedData> entries) {
154+
for (const auto i : c10::irange(entries.size())) {
155+
if (strcmp(key, entries[i].key) == 0) {
156+
return &entries[i];
122157
}
123-
tensor.unsafeGetTensorImpl()->unsafe_storage().set_data_ptr(
124-
at::DataPtr(data_ptr.get(), c10::DeviceType::CPU));
125158
}
159+
return nullptr;
160+
}
126161

127-
return tensor;
162+
ET_NODISCARD Result<void*> getTensorDataPtr(
163+
const executorch_flatbuffer::Tensor* s_tensor,
164+
const Program* program,
165+
size_t nbytes,
166+
HierarchicalAllocator* allocator,
167+
const NamedDataMap* named_data_map,
168+
Span<NamedData> external_constants) {
169+
auto data_buffer_idx = s_tensor->data_buffer_idx();
170+
const executorch_flatbuffer::AllocationDetails* allocation_info =
171+
s_tensor->allocation_info();
172+
173+
// External tensors.
174+
if (s_tensor->extra_tensor_info() != nullptr &&
175+
s_tensor->extra_tensor_info()->location() ==
176+
executorch_flatbuffer::TensorDataLocation::EXTERNAL) {
177+
// Check that fqn is not null.
178+
ET_CHECK_OR_RETURN_ERROR(
179+
s_tensor->extra_tensor_info()->fully_qualified_name() != nullptr,
180+
InvalidExternalData,
181+
"Fully qualified name of external tensor is null");
182+
const char* fqn =
183+
s_tensor->extra_tensor_info()->fully_qualified_name()->c_str();
184+
185+
// Constant value.
186+
if (allocation_info == nullptr) {
187+
NamedData* data = get_data_by_key(fqn, external_constants);
188+
if (data != nullptr) {
189+
return const_cast<void*>(data->buffer.data());
190+
}
191+
// Should never reach here; these tensors are resolved in
192+
// Method::parse_external_constants. Any errors should be caught there.
193+
return Error::Internal;
194+
} else {
195+
// Mutable value.
196+
// Look up tensor in named data map.
197+
ET_CHECK_OR_RETURN_ERROR(
198+
named_data_map != nullptr,
199+
InvalidExternalData,
200+
"Cannot retrieve external tensor with fqn: %s. The named_data_map is null; most likely no external .ptd file was provided.",
201+
fqn);
202+
Result<const TensorLayout> tensor_layout_res =
203+
named_data_map->get_tensor_layout(fqn);
204+
if (!tensor_layout_res.ok()) {
205+
return tensor_layout_res.error();
206+
}
207+
const TensorLayout& tensor_layout = tensor_layout_res.get();
208+
Error err = validateTensorLayout(s_tensor, tensor_layout);
209+
if (err != Error::Ok) {
210+
return err;
211+
}
212+
// Call load_into.
213+
auto planned_ptr = getMemPlannedPtr(allocation_info, nbytes, allocator);
214+
if (!planned_ptr.ok()) {
215+
return planned_ptr.error();
216+
}
217+
auto load_error =
218+
named_data_map->load_data_into(fqn, planned_ptr.get(), nbytes);
219+
if (load_error != Error::Ok) {
220+
return load_error;
221+
}
222+
223+
return planned_ptr;
224+
}
225+
226+
// Constant, stored in PTE file.
227+
} else if (data_buffer_idx > 0 && allocation_info == nullptr) {
228+
auto const_data =
229+
program->get_constant_buffer_data(data_buffer_idx, nbytes);
230+
if (!const_data.ok()) {
231+
return const_data.error();
232+
}
233+
234+
// The const_cast is 'ok' here because the program and runtime should
235+
// guarantee that this data is never modified.
236+
return const_cast<void*>(const_data.get());
237+
238+
// Memory Planned, with initial state
239+
} else if (data_buffer_idx > 0 && allocation_info != nullptr) {
240+
auto planned_ptr = getMemPlannedPtr(allocation_info, nbytes, allocator);
241+
if (!planned_ptr.ok()) {
242+
return planned_ptr.error();
243+
}
244+
auto err = TensorParser::load_mutable_subsegment_into(
245+
program, 0, s_tensor->data_buffer_idx(), nbytes, planned_ptr.get());
246+
247+
if (err != Error::Ok) {
248+
return err;
249+
}
250+
return planned_ptr;
251+
252+
// Memory planned, no initial state
253+
} else if (data_buffer_idx == 0 && allocation_info != nullptr) {
254+
return getMemPlannedPtr(allocation_info, nbytes, allocator);
255+
256+
// Pointer recived at runtime
257+
} else { // data_buffer_idx == 0 && allocation_info == nullptr,
258+
return nullptr;
259+
}
128260
}
129261

130262
} // namespace deserialization
131-
} // namespace aten
132-
} // namespace runtime
263+
} // namespace ET_RUNTIME_NAMESPACE
133264
} // namespace executorch

runtime/executor/tensor_parser_portable.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <executorch/runtime/core/exec_aten/exec_aten.h>
1212
#include <executorch/runtime/core/exec_aten/util/dim_order_util.h>
1313
#include <executorch/runtime/core/exec_aten/util/scalar_type_util.h>
14+
#include <executorch/runtime/core/exec_aten/util/tensor_dimension_limit.h>
1415
#include <executorch/runtime/core/named_data_map.h>
1516
#include <executorch/runtime/executor/memory_manager.h>
1617
#include <executorch/runtime/executor/program.h>
@@ -62,6 +63,13 @@ Result<Tensor> parseTensor(
6263
const auto serialized_sizes = s_tensor->sizes()->data();
6364
const auto dim = s_tensor->sizes()->size();
6465

66+
ET_CHECK_OR_RETURN_ERROR(
67+
dim <= kTensorDimensionLimit,
68+
InvalidProgram,
69+
"Tensor rank too large %" PRIu32 " > %zu",
70+
dim,
71+
kTensorDimensionLimit)
72+
6573
ET_CHECK_OR_RETURN_ERROR(
6674
s_tensor->dim_order() != nullptr,
6775
InvalidProgram,

0 commit comments

Comments
 (0)