Skip to content

check for overflow in calculate_nbytes #11217

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/pull.yml
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ jobs:
size=${arr[4]}
# threshold=48120 on devserver with gcc11.4
# todo(lfq): update once binary size is below 50kb.
threshold="51408"
threshold="55504"
if [[ "$size" -le "$threshold" ]]; then
echo "Success $size <= $threshold"
else
Expand Down Expand Up @@ -406,7 +406,7 @@ jobs:
output=$(ls -la cmake-out/test/size_test)
arr=($output)
size=${arr[4]}
threshold="47560"
threshold="51656"
if [[ "$size" -le "$threshold" ]]; then
echo "Success $size <= $threshold"
else
Expand Down
18 changes: 15 additions & 3 deletions runtime/executor/method_meta.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,24 @@ Result<Tag> get_tag(
size_t calculate_nbytes(
Span<const int32_t> sizes,
executorch::aten::ScalarType scalar_type) {
ssize_t n = 1;
size_t n = 1;
size_t prev_n = 1;
for (size_t i = 0; i < sizes.size(); i++) {
prev_n = n;
n *= sizes[i];
// Check for overflow
ET_CHECK(sizes[i] == 0 || n / sizes[i] == prev_n);
Comment on lines +63 to +64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it necessary to perform a division here? could we check n >= prev_n instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah. yes...

Copy link
Contributor Author

@JacobSzwejbka JacobSzwejbka Jun 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok no it doesnt work. I can give a counter example.

To get the numbers smaller lets say Im accumulating in uint16_t and Im taking the product of int8_t::max

n: 127 prev_n: 1

n: 16129 prev_n: 127

n: 16767 prev_n: 16129 (overflowed at 65535 from logical value 2048383)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm. I'm tempted to ask if we should just import c10/util/safe_numerics.h (which, note to self, requires llvmMathExtra.h) instead of doing division then. it is annoying that it only has 64-bit safe_multiplies functions, but we could add a size_t-based one (in executorch for now if you don't want to wait for pin bump, followed by adding it in pytorch, getting a pin bump, and deleting the executorch one).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
// Use the full namespace to disambiguate from c10::elementSize.
return n * executorch::runtime::elementSize(scalar_type);

size_t elem_size = executorch::runtime::elementSize(scalar_type);

prev_n = n;
n = n * elem_size;

// Check for overflow
ET_CHECK(elem_size == 0 || n / elem_size == prev_n);

return n;
}

} // namespace
Expand Down
5 changes: 5 additions & 0 deletions runtime/executor/method_meta.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ struct ExecutionPlan;

namespace executorch {
namespace ET_RUNTIME_NAMESPACE {
namespace testing {
// Provides test access to private Program methods.
class TensorInfoTestFriend;
} // namespace testing

/**
* Metadata about a specific tensor of an ExecuTorch Program.
Expand Down Expand Up @@ -71,6 +75,7 @@ class TensorInfo final {
private:
// Let MethodMeta create TensorInfo.
friend class MethodMeta;
friend class testing::TensorInfoTestFriend;

TensorInfo(
Span<const int32_t> sizes,
Expand Down
52 changes: 51 additions & 1 deletion runtime/executor/test/method_meta_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,48 @@
#include <executorch/runtime/executor/method_meta.h>

#include <cstdlib>
#include <filesystem>
#include <limits>
#include <vector>

#include <executorch/extension/data_loader/file_data_loader.h>
#include <executorch/runtime/core/exec_aten/exec_aten.h>
#include <executorch/runtime/executor/program.h>
#include <executorch/test/utils/DeathTest.h>
#include <gtest/gtest.h>

using namespace ::testing;
using executorch::runtime::Error;
using executorch::runtime::MethodMeta;
using executorch::runtime::Program;
using executorch::runtime::Result;
using executorch::runtime::Span;
using executorch::runtime::TensorInfo;
using torch::executor::util::FileDataLoader;

namespace executorch {
namespace runtime {
namespace testing {
// Provides access to private TensorInfo methods.
class TensorInfoTestFriend final {
public:
ET_NODISCARD static TensorInfo get(
Span<const int32_t> sizes,
Span<const uint8_t> dim_order,
executorch::aten::ScalarType scalar_type,
const bool is_memory_planned,
executorch::aten::string_view name) {
return TensorInfo(
Span<const int32_t>(sizes.data(), sizes.size()),
Span<const uint8_t>(dim_order.data(), dim_order.size()),
scalar_type,
is_memory_planned,
name);
}
};
} // namespace testing
} // namespace runtime
} // namespace executorch

class MethodMetaTest : public ::testing::Test {
protected:
void load_program(const char* path, const char* module_name) {
Expand Down Expand Up @@ -163,3 +190,26 @@ TEST_F(MethodMetaTest, MethodMetaAttribute) {
auto bad_access = method_meta->attribute_tensor_meta(1);
ASSERT_EQ(bad_access.error(), Error::InvalidArgument);
}

TEST_F(MethodMetaTest, TensorInfoSizeOverflow) {
// Create sizes that will cause overflow when multiplied
std::vector<int32_t> overflow_sizes = {
std::numeric_limits<int32_t>::max(),
std::numeric_limits<int32_t>::max(),
std::numeric_limits<int32_t>::max(),
std::numeric_limits<int32_t>::max(),
};

// Create a minimal dim_order
std::vector<uint8_t> dim_order = {0, 1, 2, 3};

// Create a TensorInfo with the overflow sizes and expect it to fail.
ET_EXPECT_DEATH(
executorch::runtime::testing::TensorInfoTestFriend::get(
Span<const int32_t>(overflow_sizes.data(), overflow_sizes.size()),
Span<const uint8_t>(dim_order.data(), dim_order.size()),
executorch::aten::ScalarType::Float,
false, // is_memory_planned
executorch::aten::string_view{nullptr, 0}),
"");
}
Loading