#include "gwp_asan/guarded_pool_allocator.h"
+#include "gwp_asan/optional/segv_handler.h"
#include "gwp_asan/options.h"
+#include "gwp_asan/random.h"
#include "gwp_asan/utilities.h"
-#include "optional/segv_handler.h"
// RHEL creates the PRIu64 format macro (for printing uint64_t's) only when this
// macro is defined before including <inttypes.h>.
return nullptr;
uintptr_t Ptr = State.slotToAddr(Index);
- Ptr += allocationSlotOffset(Size);
+ // Should we right-align this allocation?
+ if (getRandomUnsigned32() % 2 == 0) {
+ AlignmentStrategy Align = AlignmentStrategy::DEFAULT;
+ if (PerfectlyRightAlign)
+ Align = AlignmentStrategy::PERFECT;
+ Ptr +=
+ State.maximumAllocationSize() - rightAlignedAllocationSize(Size, Align);
+ }
AllocationMetadata *Meta = addrToMetadata(Ptr);
// If a slot is multiple pages in size, and the allocation takes up a single
FreeSlots[FreeSlotsLength++] = SlotIndex;
}
-uintptr_t GuardedPoolAllocator::allocationSlotOffset(size_t Size) const {
- assert(Size > 0);
-
- bool ShouldRightAlign = getRandomUnsigned32() % 2 == 0;
- if (!ShouldRightAlign)
- return 0;
-
- uintptr_t Offset = State.maximumAllocationSize();
- if (!PerfectlyRightAlign) {
- if (Size == 3)
- Size = 4;
- else if (Size > 4 && Size <= 8)
- Size = 8;
- else if (Size > 8 && (Size % 16) != 0)
- Size += 16 - (Size % 16);
- }
- Offset -= Size;
- return Offset;
-}
-
GWP_ASAN_TLS_INITIAL_EXEC
GuardedPoolAllocator::ThreadLocalPackedVariables
GuardedPoolAllocator::ThreadLocals;
// Unreserve the guarded slot.
void freeSlot(size_t SlotIndex);
- // Returns the offset (in bytes) between the start of a guarded slot and where
- // the start of the allocation should take place. Determined using the size of
- // the allocation and the options provided at init-time.
- uintptr_t allocationSlotOffset(size_t AllocationSize) const;
-
// Raise a SEGV and set the corresponding fields in the Allocator's State in
// order to tell the crash handler what happened. Used when errors are
// detected internally (Double Free, Invalid Free).
"When allocations are right-aligned, should we perfectly align them up to "
"the page boundary? By default (false), we round up allocation size to the "
"nearest power of two (1, 2, 4, 8, 16) up to a maximum of 16-byte "
- "alignment for performance reasons. Setting this to true can find single "
- "byte buffer-overflows for multibyte allocations at the cost of "
- "performance, and may be incompatible with some architectures.")
+ "alignment for performance reasons. For Bionic, we use 8-byte alignment by "
+ "default. Setting this to true can find single byte buffer-overflows for "
+ "multibyte allocations at the cost of performance, and may be incompatible "
+ "with some architectures.")
GWP_ASAN_OPTION(int, MaxSimultaneousAllocations, 16,
"Number of simultaneously-guarded allocations available in the "
#include "gwp_asan/definitions.h"
#include "gwp_asan/utilities.h"
-#ifdef ANDROID
+#include <assert.h>
+
+#ifdef __BIONIC__
#include <stdlib.h>
extern "C" GWP_ASAN_WEAK void android_set_abort_message(const char *);
-#else // ANDROID
+#else // __BIONIC__
#include <stdio.h>
#endif
namespace gwp_asan {
-#ifdef ANDROID
+#ifdef __BIONIC__
void Check(bool Condition, const char *Message) {
if (Condition)
return;
android_set_abort_message(Message);
abort();
}
-#else // ANDROID
+#else // __BIONIC__
void Check(bool Condition, const char *Message) {
if (Condition)
return;
fprintf(stderr, "%s", Message);
__builtin_trap();
}
-#endif // ANDROID
+#endif // __BIONIC__
+
+// See `bionic/tests/malloc_test.cpp` in the Android source for documentation
+// regarding their alignment guarantees. We always round up to the closest
+// 8-byte window. As GWP-ASan's malloc(X) can always get exactly an X-sized
+// allocation, an allocation that rounds up to 16-bytes will always be given a
+// 16-byte aligned allocation.
+static size_t alignBionic(size_t RealAllocationSize) {
+ if (RealAllocationSize % 8 == 0)
+ return RealAllocationSize;
+ return RealAllocationSize + 8 - (RealAllocationSize % 8);
+}
+
+static size_t alignPowerOfTwo(size_t RealAllocationSize) {
+ if (RealAllocationSize <= 2)
+ return RealAllocationSize;
+ if (RealAllocationSize <= 4)
+ return 4;
+ if (RealAllocationSize <= 8)
+ return 8;
+ if (RealAllocationSize % 16 == 0)
+ return RealAllocationSize;
+ return RealAllocationSize + 16 - (RealAllocationSize % 16);
+}
+
+#ifdef __BIONIC__
+static constexpr AlignmentStrategy PlatformDefaultAlignment =
+ AlignmentStrategy::ANDROID;
+#else // __BIONIC__
+static constexpr AlignmentStrategy PlatformDefaultAlignment =
+ AlignmentStrategy::POWER_OF_TWO;
+#endif // __BIONIC__
+
+size_t rightAlignedAllocationSize(size_t RealAllocationSize,
+ AlignmentStrategy Align) {
+ assert(RealAllocationSize > 0);
+ if (Align == AlignmentStrategy::DEFAULT)
+ Align = PlatformDefaultAlignment;
+
+ switch (Align) {
+ case AlignmentStrategy::BIONIC:
+ return alignBionic(RealAllocationSize);
+ case AlignmentStrategy::POWER_OF_TWO:
+ return alignPowerOfTwo(RealAllocationSize);
+ case AlignmentStrategy::PERFECT:
+ return RealAllocationSize;
+ case AlignmentStrategy::DEFAULT:
+ __builtin_unreachable();
+ }
+}
} // namespace gwp_asan
//===----------------------------------------------------------------------===//
#include "gwp_asan/tests/harness.h"
+#include "gwp_asan/utilities.h"
-TEST_F(DefaultGuardedPoolAllocator, BasicAllocation) {
- std::vector<std::pair<int, int>> AllocSizeToAlignment = {
+TEST(AlignmentTest, PowerOfTwo) {
+ std::vector<std::pair<size_t, size_t>> AskedSizeToAlignedSize = {
{1, 1}, {2, 2}, {3, 4}, {4, 4}, {5, 8}, {7, 8},
- {8, 8}, {9, 16}, {15, 16}, {16, 16}, {17, 16}, {31, 16},
- {32, 16}, {33, 16}, {4095, 4096}, {4096, 4096},
+ {8, 8}, {9, 16}, {15, 16}, {16, 16}, {17, 32}, {31, 32},
+ {32, 32}, {33, 48}, {4095, 4096}, {4096, 4096},
};
- for (const auto &KV : AllocSizeToAlignment) {
- void *Ptr = GPA.allocate(KV.first);
- EXPECT_NE(nullptr, Ptr);
+ for (const auto &KV : AskedSizeToAlignedSize) {
+ EXPECT_EQ(KV.second,
+ gwp_asan::rightAlignedAllocationSize(
+ KV.first, gwp_asan::AlignmentStrategy::POWER_OF_TWO));
+ }
+}
+
+TEST(AlignmentTest, AlignBionic) {
+ std::vector<std::pair<size_t, size_t>> AskedSizeToAlignedSize = {
+ {1, 8}, {2, 8}, {3, 8}, {4, 8}, {5, 8}, {7, 8},
+ {8, 8}, {9, 16}, {15, 16}, {16, 16}, {17, 24}, {31, 32},
+ {32, 32}, {33, 40}, {4095, 4096}, {4096, 4096},
+ };
- // Check the alignment of the pointer is as expected.
- EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(Ptr) % KV.second);
+ for (const auto &KV : AskedSizeToAlignedSize) {
+ EXPECT_EQ(KV.second, gwp_asan::rightAlignedAllocationSize(
+ KV.first, gwp_asan::AlignmentStrategy::BIONIC));
+ }
+}
- GPA.deallocate(Ptr);
+TEST(AlignmentTest, PerfectAlignment) {
+ for (size_t i = 1; i <= 4096; ++i) {
+ EXPECT_EQ(i, gwp_asan::rightAlignedAllocationSize(
+ i, gwp_asan::AlignmentStrategy::PERFECT));
}
}
#include "gwp_asan/definitions.h"
+#include <stddef.h>
+#include <stdint.h>
+
namespace gwp_asan {
// Checks that `Condition` is true, otherwise fails in a platform-specific way
// with `Message`.
void Check(bool Condition, const char *Message);
+
+enum class AlignmentStrategy {
+ // Default => POWER_OF_TWO on most platforms, BIONIC for Android Bionic.
+ DEFAULT,
+ POWER_OF_TWO,
+ BIONIC,
+ PERFECT,
+};
+
+// Returns the real size of a right-aligned allocation.
+size_t rightAlignedAllocationSize(
+ size_t RealAllocationSize,
+ AlignmentStrategy Align = AlignmentStrategy::DEFAULT);
} // namespace gwp_asan