Commit 5f2a74c8 authored by Mitch Phillips's avatar Mitch Phillips
Browse files

[GWP-ASan] Update alignment on Android.

Summary:
Android has different alignment requirements. You can read more about
them here
(https://cs.android.com/android/platform/superproject/+/master:bionic/tests/malloc_test.cpp;l=808),
but the general gist is that for malloc(x <= 8), we do malloc(8), and
for everything else, we do 16-byte alignment.

Reviewers: eugenis, morehouse, cferris

Reviewed By: eugenis, morehouse

Subscribers: #sanitizers, llvm-commits, pcc

Tags: #sanitizers, #llvm

Differential Revision: https://reviews.llvm.org/D74364
parent 369d086d
Loading
Loading
Loading
Loading
+10 −22
Original line number Diff line number Diff line
@@ -8,9 +8,10 @@

#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>.
@@ -175,7 +176,14 @@ void *GuardedPoolAllocator::allocate(size_t Size) {
    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
@@ -278,26 +286,6 @@ void GuardedPoolAllocator::freeSlot(size_t SlotIndex) {
  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;
+0 −5
Original line number Diff line number Diff line
@@ -149,11 +149,6 @@ private:
  // 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).
+4 −3
Original line number Diff line number Diff line
@@ -17,9 +17,10 @@ GWP_ASAN_OPTION(
    "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 "
+56 −5
Original line number Diff line number Diff line
@@ -9,16 +9,18 @@
#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;
@@ -26,13 +28,62 @@ void Check(bool Condition, const char *Message) {
    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
+27 −10
Original line number Diff line number Diff line
@@ -7,21 +7,38 @@
//===----------------------------------------------------------------------===//

#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));
  }
}
Loading