In debug mode, after clearing oneof messages on arenas, poison them if ASAN.

PiperOrigin-RevId: 621886314
pull/16297/head
Protobuf Team Bot 8 months ago committed by Copybara-Service
parent b4bf6b22e5
commit 8826bafcf5
  1. 16
      src/google/protobuf/arena.cc
  2. 43
      src/google/protobuf/arena_unittest.cc
  3. 30
      src/google/protobuf/compiler/cpp/field_generators/message_field.cc
  4. 2
      src/google/protobuf/compiler/cpp/unittest.inc
  5. 6
      src/google/protobuf/generated_message_reflection.cc
  6. 18
      src/google/protobuf/message.h
  7. 3
      src/google/protobuf/port.h
  8. 8
      src/google/protobuf/proto3_arena_unittest.cc
  9. 1
      src/google/protobuf/reflection_visit_fields.h
  10. 2
      src/google/protobuf/thread_safe_arena.h

@ -92,9 +92,6 @@ class GetDeallocator {
space_allocated_(space_allocated) {} space_allocated_(space_allocated) {}
void operator()(SizedPtr mem) const { void operator()(SizedPtr mem) const {
// This memory was provided by the underlying allocator as unpoisoned,
// so return it in an unpoisoned state.
PROTOBUF_UNPOISON_MEMORY_REGION(mem.p, mem.n);
if (dealloc_) { if (dealloc_) {
dealloc_(mem.p, mem.n); dealloc_(mem.p, mem.n);
} else { } else {
@ -666,6 +663,15 @@ void ThreadSafeArena::AddSerialArena(void* id, SerialArena* serial) {
head_.store(new_head, std::memory_order_release); head_.store(new_head, std::memory_order_release);
} }
void ThreadSafeArena::UnpoisonAllArenaBlocks() const {
VisitSerialArena([](const SerialArena* serial) {
for (const auto* b = serial->head(); b != nullptr && !b->IsSentry();
b = b->next) {
PROTOBUF_UNPOISON_MEMORY_REGION(b, b->size);
}
});
}
void ThreadSafeArena::Init() { void ThreadSafeArena::Init() {
tag_and_id_ = GetNextLifeCycleId(); tag_and_id_ = GetNextLifeCycleId();
arena_stats_ = Sample(); arena_stats_ = Sample();
@ -868,6 +874,10 @@ template void*
ThreadSafeArena::AllocateAlignedFallback<AllocationClient::kArray>(size_t); ThreadSafeArena::AllocateAlignedFallback<AllocationClient::kArray>(size_t);
void ThreadSafeArena::CleanupList() { void ThreadSafeArena::CleanupList() {
#ifdef PROTOBUF_ASAN
UnpoisonAllArenaBlocks();
#endif
WalkSerialArenaChunk([](SerialArenaChunk* chunk) { WalkSerialArenaChunk([](SerialArenaChunk* chunk) {
absl::Span<std::atomic<SerialArena*>> span = chunk->arenas(); absl::Span<std::atomic<SerialArena*>> span = chunk->arenas();
// Walks arenas backward to handle the first serial arena the last. // Walks arenas backward to handle the first serial arena the last.

@ -1506,6 +1506,45 @@ TEST(ArenaTest, MutableMessageReflection) {
#endif // PROTOBUF_RTTI #endif // PROTOBUF_RTTI
TEST(ArenaTest, ClearOneofMessageOnArena) {
if (!internal::DebugHardenClearOneofMessageOnArena()) {
GTEST_SKIP() << "arena allocated oneof message fields are not hardened.";
}
Arena arena;
auto* message = Arena::Create<unittest::TestOneof2>(&arena);
// Intentionally nested to force poisoning recursively to catch the access.
auto* child =
message->mutable_foo_message()->mutable_child()->mutable_child();
child->set_moo_int(100);
message->clear_foo_message();
#ifndef PROTOBUF_ASAN
EXPECT_NE(child->moo_int(), 100);
#else
#if GTEST_HAS_DEATH_TEST && defined(__cpp_if_constexpr)
EXPECT_DEATH(EXPECT_EQ(child->moo_int(), 0), "use-after-poison");
#endif
#endif
}
TEST(ArenaTest, CopyValuesWithinOneof) {
if (!internal::DebugHardenClearOneofMessageOnArena()) {
GTEST_SKIP() << "arena allocated oneof message fields are not hardened.";
}
Arena arena;
auto* message = Arena::Create<unittest::TestOneof>(&arena);
auto* foo = message->mutable_foogroup();
foo->set_a(100);
foo->set_b("hello world");
message->set_foo_string(message->foogroup().b());
// As a debug hardening measure, `set_foo_string` would clear `foo` in
// (!NDEBUG && !ASAN) and the copy wouldn't work.
EXPECT_TRUE(message->foo_string().empty()) << message->foo_string();
}
void FillArenaAwareFields(TestAllTypes* message) { void FillArenaAwareFields(TestAllTypes* message) {
std::string test_string = "hello world"; std::string test_string = "hello world";
message->set_optional_int32(42); message->set_optional_int32(42);
@ -1526,6 +1565,10 @@ void FillArenaAwareFields(TestAllTypes* message) {
// Test: no allocations occur on heap while touching all supported field types. // Test: no allocations occur on heap while touching all supported field types.
TEST(ArenaTest, NoHeapAllocationsTest) { TEST(ArenaTest, NoHeapAllocationsTest) {
if (internal::DebugHardenClearOneofMessageOnArena()) {
GTEST_SKIP() << "debug hardening may cause heap allocation.";
}
// Allocate a large initial block to avoid mallocs during hooked test. // Allocate a large initial block to avoid mallocs during hooked test.
std::vector<char> arena_block(128 * 1024); std::vector<char> arena_block(128 * 1024);
ArenaOptions options; ArenaOptions options;

@ -593,15 +593,27 @@ void OneofMessage::GenerateInlineAccessorDefinitions(io::Printer* p) const {
} }
void OneofMessage::GenerateClearingCode(io::Printer* p) const { void OneofMessage::GenerateClearingCode(io::Printer* p) const {
p->Emit(R"cc( p->Emit({{"poison_or_clear",
if (GetArena() == nullptr) { [&] {
delete $field_$; if (HasDescriptorMethods(field_->file(), options_)) {
} else if ($pbi$::DebugHardenClearOneofMessageOnArena()) { p->Emit(R"cc(
if ($field_$ != nullptr) { $pbi$::MaybePoisonAfterClear($field_$);
$field_$->Clear(); )cc");
} } else {
} p->Emit(R"cc(
)cc"); if ($field_$ != nullptr) {
$field_$->Clear();
}
)cc");
}
}}},
R"cc(
if (GetArena() == nullptr) {
delete $field_$;
} else if ($pbi$::DebugHardenClearOneofMessageOnArena()) {
$poison_or_clear$;
}
)cc");
} }
void OneofMessage::GenerateMessageClearingCode(io::Printer* p) const { void OneofMessage::GenerateMessageClearingCode(io::Printer* p) const {

@ -552,6 +552,7 @@ TEST(GENERATED_MESSAGE_TEST_NAME, CopyConstructor) {
} }
} }
#ifndef PROTOBUF_TEST_NO_DESCRIPTORS
TEST(GENERATED_MESSAGE_TEST_NAME, CopyConstructorWithArenas) { TEST(GENERATED_MESSAGE_TEST_NAME, CopyConstructorWithArenas) {
Arena arena; Arena arena;
UNITTEST::TestAllTypes* message1 = UNITTEST::TestAllTypes* message1 =
@ -572,7 +573,6 @@ TEST(GENERATED_MESSAGE_TEST_NAME, CopyConstructorWithArenas) {
TestUtil::ExpectAllFieldsSet(*message2_heap); TestUtil::ExpectAllFieldsSet(*message2_heap);
} }
#ifndef PROTOBUF_TEST_NO_DESCRIPTORS
TEST(GENERATED_MESSAGE_TEST_NAME, UpcastCopyFrom) { TEST(GENERATED_MESSAGE_TEST_NAME, UpcastCopyFrom) {
// Test the CopyFrom method that takes in the generic const Message& // Test the CopyFrom method that takes in the generic const Message&
// parameter. // parameter.

@ -16,6 +16,7 @@
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <new> // IWYU pragma: keep for operator delete #include <new> // IWYU pragma: keep for operator delete
#include <queue>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
@ -43,6 +44,7 @@
#include "google/protobuf/message_lite.h" #include "google/protobuf/message_lite.h"
#include "google/protobuf/port.h" #include "google/protobuf/port.h"
#include "google/protobuf/raw_ptr.h" #include "google/protobuf/raw_ptr.h"
#include "google/protobuf/reflection_visit_fields.h"
#include "google/protobuf/repeated_field.h" #include "google/protobuf/repeated_field.h"
#include "google/protobuf/repeated_ptr_field.h" #include "google/protobuf/repeated_ptr_field.h"
#include "google/protobuf/unknown_field_set.h" #include "google/protobuf/unknown_field_set.h"
@ -1307,6 +1309,10 @@ void Reflection::InternalSwap(Message* lhs, Message* rhs) const {
} }
} }
void Reflection::MaybePoisonAfterClear(Message& root) const {
root.Clear();
}
int Reflection::FieldSize(const Message& message, int Reflection::FieldSize(const Message& message,
const FieldDescriptor* field) const { const FieldDescriptor* field) const {
USAGE_CHECK_MESSAGE(FieldSize, &message); USAGE_CHECK_MESSAGE(FieldSize, &message);

@ -226,6 +226,8 @@ bool CreateUnknownEnumValues(const FieldDescriptor* field);
// Returns true if "message" is a descendant of "root". // Returns true if "message" is a descendant of "root".
PROTOBUF_EXPORT bool IsDescendant(Message& root, const Message& message); PROTOBUF_EXPORT bool IsDescendant(Message& root, const Message& message);
inline void MaybePoisonAfterClear(Message* root);
} // namespace internal } // namespace internal
// Abstract interface for protocol messages. // Abstract interface for protocol messages.
@ -1030,10 +1032,16 @@ class PROTOBUF_EXPORT Reflection final {
return schema_.IsSplit(field); return schema_.IsSplit(field);
} }
// Walks the message tree from "root" and poisons (under ASAN) the memory to
// force subsequent accesses to fail. Always calls Clear beforehand to clear
// strings, etc.
void MaybePoisonAfterClear(Message& root) const;
friend class FastReflectionBase; friend class FastReflectionBase;
friend class FastReflectionMessageMutator; friend class FastReflectionMessageMutator;
friend class internal::ReflectionVisit; friend class internal::ReflectionVisit;
friend bool internal::IsDescendant(Message& root, const Message& message); friend bool internal::IsDescendant(Message& root, const Message& message);
friend void internal::MaybePoisonAfterClear(Message* root);
const Descriptor* const descriptor_; const Descriptor* const descriptor_;
const internal::ReflectionSchema schema_; const internal::ReflectionSchema schema_;
@ -1626,6 +1634,16 @@ class RawMessageBase : public Message {
virtual size_t SpaceUsedLong() const = 0; virtual size_t SpaceUsedLong() const = 0;
}; };
inline void MaybePoisonAfterClear(Message* root) {
if (root == nullptr) return;
#ifndef PROTOBUF_ASAN
root->Clear();
#else
const Reflection* reflection = root->GetReflection();
reflection->MaybePoisonAfterClear(*root);
#endif
}
} // namespace internal } // namespace internal
template <typename Type> template <typename Type>

@ -239,7 +239,8 @@ inline constexpr bool DebugHardenStringValues() {
#endif #endif
} }
// Returns true if force clearing oneof message on arena is enabled. // Returns true if debug hardening for clearing oneof message on arenas is
// enabled.
inline constexpr bool DebugHardenClearOneofMessageOnArena() { inline constexpr bool DebugHardenClearOneofMessageOnArena() {
#ifdef NDEBUG #ifdef NDEBUG
return false; return false;

@ -273,7 +273,7 @@ TEST(Proto3ArenaTest, CheckMessageFieldIsCleared) {
TEST(Proto3ArenaTest, CheckOneofMessageFieldIsCleared) { TEST(Proto3ArenaTest, CheckOneofMessageFieldIsCleared) {
if (!internal::DebugHardenClearOneofMessageOnArena()) { if (!internal::DebugHardenClearOneofMessageOnArena()) {
GTEST_SKIP() << "arena allocated oneof message fields are not cleared."; GTEST_SKIP() << "arena allocated oneof message fields are not hardened.";
} }
Arena arena; Arena arena;
@ -286,7 +286,13 @@ TEST(Proto3ArenaTest, CheckOneofMessageFieldIsCleared) {
child->set_bb(100); child->set_bb(100);
msg->Clear(); msg->Clear();
#ifndef PROTOBUF_ASAN
EXPECT_EQ(child->bb(), 0); EXPECT_EQ(child->bb(), 0);
#else
#if GTEST_HAS_DEATH_TEST && defined(__cpp_if_constexpr)
EXPECT_DEATH(EXPECT_EQ(child->bb(), 100), "use-after-poison");
#endif
#endif
} }
TEST(Proto3OptionalTest, OptionalFieldDescriptor) { TEST(Proto3OptionalTest, OptionalFieldDescriptor) {

@ -302,7 +302,6 @@ void ReflectionVisit::VisitFields(MessageT& message, CallbackFn&& func,
auto& set = ExtensionSet(reflection, message); auto& set = ExtensionSet(reflection, message);
auto* extendee = reflection->descriptor_; auto* extendee = reflection->descriptor_;
auto* pool = reflection->descriptor_pool_; auto* pool = reflection->descriptor_pool_;
auto* arena = message.GetArena();
set.ForEach([&](int number, auto& ext) { set.ForEach([&](int number, auto& ext) {
ABSL_DCHECK_GT(ext.type, 0); ABSL_DCHECK_GT(ext.type, 0);

@ -129,6 +129,8 @@ class PROTOBUF_EXPORT ThreadSafeArena {
// Adds SerialArena to the chunked list. May create a new chunk. // Adds SerialArena to the chunked list. May create a new chunk.
void AddSerialArena(void* id, SerialArena* serial); void AddSerialArena(void* id, SerialArena* serial);
void UnpoisonAllArenaBlocks() const;
// Members are declared here to track sizeof(ThreadSafeArena) and hotness // Members are declared here to track sizeof(ThreadSafeArena) and hotness
// centrally. // centrally.

Loading…
Cancel
Save