Fix size_t narrowing and pointer arithmetic overflow in absl::chunked_queue::resize In `absl::chunked_queue::resize(size_t)`, the difference between `new_size` and `size()` was being stored in a `ptrdiff_t`. When resizing to large values (such as `SIZE_MAX`), this narrowing conversion could overflow, resulting in a negative delta and causing an out-of-bounds heap write during element construction. This change fixes the issue by: - Keeping `to_add` as `size_t`. - Clamping `to_add` to the remaining element capacity of the current tail block before adding it to `start`, preventing pointer arithmetic overflow or wrap-around. - Updating `iterator_common::IncrBy(size_t)` to compare element counts rather than computing `ptr + n` against `limit`, avoiding potential pointer wrap-around. - Adding a unit test (`ResizeOverflowSafe`) using a custom limited allocator to verify safe handling of large resize requests. PiperOrigin-RevId: 945278731 Change-Id: Id400b3813f95d59ea738c58c8c1af3e6e8a836e4
diff --git a/absl/container/BUILD.bazel b/absl/container/BUILD.bazel index 22a0a23..9afef5d 100644 --- a/absl/container/BUILD.bazel +++ b/absl/container/BUILD.bazel
@@ -1426,6 +1426,7 @@ ":test_allocator", "//absl/base:core_headers", "//absl/base:hardening", + "//absl/base:throw_delegate", "//absl/strings", "@googletest//:gtest", "@googletest//:gtest_main",
diff --git a/absl/container/CMakeLists.txt b/absl/container/CMakeLists.txt index 005548f..5ffef3f 100644 --- a/absl/container/CMakeLists.txt +++ b/absl/container/CMakeLists.txt
@@ -1239,5 +1239,6 @@ absl::hardening absl::strings absl::test_allocator + absl::throw_delegate GTest::gmock_main )
diff --git a/absl/container/chunked_queue.h b/absl/container/chunked_queue.h index 2ec7184..0e686ca 100644 --- a/absl/container/chunked_queue.h +++ b/absl/container/chunked_queue.h
@@ -154,8 +154,8 @@ } void IncrBy(size_t n) { - while (ptr + n > limit) { - n -= limit - ptr; + while (n > static_cast<size_t>(limit - ptr)) { + n -= static_cast<size_t>(limit - ptr); *this = iterator_common(block->next()); } ptr += n; @@ -634,15 +634,16 @@ template <typename T, size_t BLo, size_t BHi, typename Allocator> void chunked_queue<T, BLo, BHi, Allocator>::resize(size_t new_size) { while (new_size > size()) { - ptrdiff_t to_add = new_size - size(); if (tail_.ptr == tail_.limit) { AddTailBlock(); } + size_t to_add = (std::min)(new_size - size(), + static_cast<size_t>(tail_.limit - tail_.ptr)); T* start = tail_.ptr; - T* limit = (std::min)(tail_.limit, start + to_add); + T* limit = start + to_add; Construct(start, limit); tail_.ptr = limit; - alloc_and_size_.size += limit - start; + alloc_and_size_.size += to_add; } if (size() == new_size) { return;
diff --git a/absl/container/chunked_queue_test.cc b/absl/container/chunked_queue_test.cc index cc50f6b..9540ff9 100644 --- a/absl/container/chunked_queue_test.cc +++ b/absl/container/chunked_queue_test.cc
@@ -20,8 +20,10 @@ #include <deque> #include <forward_list> #include <iterator> +#include <limits> #include <list> #include <memory> +#include <new> #include <string> #include <type_traits> #include <utility> @@ -31,6 +33,7 @@ #include "gtest/gtest.h" #include "absl/base/internal/hardening.h" #include "absl/base/macros.h" +#include "absl/base/throw_delegate.h" #include "absl/container/internal/test_allocator.h" #include "absl/strings/str_cat.h" @@ -435,6 +438,51 @@ EXPECT_EQ(2, q.size()); } +template <class T> +struct LimitedAllocator { + using value_type = T; + int* alloc_count; + int max_allocs; + + explicit LimitedAllocator(int* count, int max) + : alloc_count(count), max_allocs(max) {} + template <class U> + LimitedAllocator(const LimitedAllocator<U>& other) + : alloc_count(other.alloc_count), max_allocs(other.max_allocs) {} + + T* allocate(size_t n) { + if (*alloc_count >= max_allocs) { + absl::ThrowStdBadAlloc(); + } + ++*alloc_count; + return std::allocator<T>().allocate(n); + } + + void deallocate(T* p, size_t n) { + std::allocator<T>().deallocate(p, n); + } + + template <class U> + bool operator==(const LimitedAllocator<U>& other) const { + return alloc_count == other.alloc_count; + } + template <class U> + bool operator!=(const LimitedAllocator<U>& other) const { + return !(*this == other); + } +}; + +TEST(ChunkedQueue, ResizeOverflowSafe) { + int alloc_count = 0; + absl::chunked_queue<int64_t, 0, 0, LimitedAllocator<int64_t>> q( + LimitedAllocator<int64_t>(&alloc_count, 5)); +#ifdef ABSL_HAVE_EXCEPTIONS + EXPECT_THROW(q.resize(std::numeric_limits<size_t>::max()), std::bad_alloc); +#else + EXPECT_DEATH_IF_SUPPORTED(q.resize(std::numeric_limits<size_t>::max()), ""); +#endif +} + TEST(ChunkedQueue, MaxSize) { absl::chunked_queue<int64_t> q; EXPECT_GE(q.max_size(),