Fix potential 32-bit size_t overflow in SubstituteAndAppendArray() PiperOrigin-RevId: 944682504 Change-Id: I26722efac261f27a1e9eabf7e13c62aa578364b7
diff --git a/absl/strings/substitute.cc b/absl/strings/substitute.cc index f5d600b..244c8a5 100644 --- a/absl/strings/substitute.cc +++ b/absl/strings/substitute.cc
@@ -20,6 +20,7 @@ #include <cstdint> #include <limits> #include <string> +#include <type_traits> #include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" @@ -35,6 +36,19 @@ ABSL_NAMESPACE_BEGIN namespace substitute_internal { +// Checked addition to avoid overflow on 32-bit. (In 64-bit the callers wouldn't +// get anywhere close to the limit given that 2^64 bytes of memory is beyond +// anything physically possible.) +// Note that we don't declare the parameters as size_t in order to avoid +// accidental implicit conversions. +template <int&..., typename T> +[[nodiscard]] std::enable_if_t<std::is_same_v<T, size_t>, T> CheckedAdd(T a, + T b) { + ABSL_INTERNAL_CHECK(b <= (std::numeric_limits<T>::max)() - a, + "unsigned integer overflow"); + return a + b; +} + void SubstituteAndAppendArray(std::string* absl_nonnull output, absl::string_view format, const absl::string_view* absl_nullable args_array, @@ -64,10 +78,10 @@ #endif return; } - size += args_array[index].size(); + size = CheckedAdd(size, args_array[index].size()); ++i; // Skip next char. } else if (format[i + 1] == '$') { - ++size; + size = CheckedAdd(size, size_t{1}); ++i; // Skip next char. } else { #ifndef NDEBUG @@ -78,7 +92,7 @@ return; } } else { - ++size; + size = CheckedAdd(size, size_t{1}); } }
diff --git a/absl/strings/substitute_test.cc b/absl/strings/substitute_test.cc index 70f9119..e62a89a 100644 --- a/absl/strings/substitute_test.cc +++ b/absl/strings/substitute_test.cc
@@ -16,6 +16,7 @@ #include <cstdint> #include <cstring> +#include <limits> #include <string> #include <vector> @@ -283,6 +284,17 @@ "Invalid absl::Substitute\\(\\) format string: \"-\\$\""); } +TEST(SubstituteDeathTest, OverflowDeath) { + // HACK: We pretend the string_view is extremely long, in order to test an + // overflow condition that only occurs in 32-bit without using an impractical + // amount of memory. + EXPECT_DEATH(static_cast<void>(absl::Substitute( + "$0$0$0", + absl::string_view( + "abc", (std::numeric_limits<size_t>::max() / 3) + 100))), + "overflow"); +} + #endif // GTEST_HAS_DEATH_TEST } // namespace