Skip to content

[scudo] Mitigate commitbase exploit #75295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions compiler-rt/lib/scudo/standalone/allocator_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ struct DefaultConfig {
static const s32 MinReleaseToOsIntervalMs = INT32_MIN;
static const s32 MaxReleaseToOsIntervalMs = INT32_MAX;
};
static const bool VerifyInUseAddresses = true;
static const u32 InUseBlocksSize = 1000U;
template <typename Config> using CacheT = MapAllocatorCache<Config>;
};

Expand Down Expand Up @@ -198,6 +200,8 @@ struct AndroidConfig {
static const s32 MinReleaseToOsIntervalMs = 0;
static const s32 MaxReleaseToOsIntervalMs = 1000;
};
static const bool VerifyInUseAddresses = true;
static const u32 InUseBlocksSize = 1000U;
template <typename Config> using CacheT = MapAllocatorCache<Config>;
};

Expand Down Expand Up @@ -230,6 +234,8 @@ struct FuchsiaConfig {
template <typename Config> using PrimaryT = SizeClassAllocator64<Config>;

struct Secondary {
static const bool VerifyInUseAddresses = true;
static const u32 InUseBlocksSize = 1000U;
template <typename Config> using CacheT = MapAllocatorNoCache<Config>;
};
template <typename Config> using SecondaryT = MapAllocator<Config>;
Expand All @@ -254,6 +260,8 @@ struct TrustyConfig {
template <typename Config> using PrimaryT = SizeClassAllocator64<Config>;

struct Secondary {
static const bool VerifyInUseAddresses = true;
static const u32 InUseBlocksSize = 1000U;
template <typename Config> using CacheT = MapAllocatorNoCache<Config>;
};

Expand Down
147 changes: 147 additions & 0 deletions compiler-rt/lib/scudo/standalone/secondary.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include "chunk.h"
#include "common.h"
#include "internal_defs.h"
#include "list.h"
#include "mem_map.h"
#include "memtag.h"
Expand Down Expand Up @@ -476,6 +477,23 @@ template <typename Config> class MapAllocator {
DCHECK_EQ(FreedBytes, 0U);
Cache.init(ReleaseToOsInterval);
Stats.init();

if (Config::Secondary::VerifyInUseAddresses) {
ReservedMemoryT InUseReserved;
InUseReserved.create(
0U, sizeof(void *) * (Config::Secondary::InUseBlocksSize + 1),
"scudo:secondary_integrity");
DCHECK_NE(InUseReserved.getBase(), 0U);

InUseAddresses = InUseReserved.dispatch(
InUseReserved.getBase(),
sizeof(void *) * (Config::Secondary::InUseBlocksSize + 1));
CHECK(InUseAddresses.isAllocated());
InUseAddresses.setMemoryPermission(
InUseAddresses.getBase(),
sizeof(void *) * (Config::Secondary::InUseBlocksSize + 1), 0);
}

if (LIKELY(S))
S->link(&Stats);
}
Expand Down Expand Up @@ -544,6 +562,8 @@ template <typename Config> class MapAllocator {
u32 NumberOfAllocs GUARDED_BY(Mutex) = 0;
u32 NumberOfFrees GUARDED_BY(Mutex) = 0;
LocalStats Stats GUARDED_BY(Mutex);

MemMapT InUseAddresses GUARDED_BY(Mutex) = {};
};

// As with the Primary, the size passed to this function includes any desired
Expand Down Expand Up @@ -588,6 +608,54 @@ void *MapAllocator<Config>::allocate(const Options &Options, uptr Size,
BlockEnd - PtrInt);
{
ScopedLock L(Mutex);

if (Config::Secondary::VerifyInUseAddresses) {
void **IntegrityList =
reinterpret_cast<void **>(InUseAddresses.getBase());
bool isFull = true;
bool reachedEnd = false;

while (!reachedEnd) {
for (u32 I = 0; I < Config::Secondary::InUseBlocksSize; I++) {
if (IntegrityList[I] == nullptr) {
isFull = false;
IntegrityList[I] = Ptr;
break;
}
}
if (isFull &&
IntegrityList[Config::Secondary::InUseBlocksSize] != nullptr) {
IntegrityList = static_cast<void **>(
IntegrityList[Config::Secondary::InUseBlocksSize]);
} else {
reachedEnd = true;
}
}

if (isFull) {
ReservedMemoryT InUseReserved;
InUseReserved.create(
0U, sizeof(void *) * (Config::Secondary::InUseBlocksSize + 1),
"scudo:secondary_integrity");
DCHECK_NE(InUseReserved.getBase(), 0U);

MemMapT NewAddresses = InUseReserved.dispatch(
InUseReserved.getBase(),
sizeof(void *) * (Config::Secondary::InUseBlocksSize + 1));
CHECK(NewAddresses.isAllocated());
NewAddresses.setMemoryPermission(
NewAddresses.getBase(),
sizeof(void *) * (Config::Secondary::InUseBlocksSize + 1), 0);

IntegrityList[Config::Secondary::InUseBlocksSize] =
reinterpret_cast<void *>(NewAddresses.getBase());

IntegrityList = static_cast<void **>(
IntegrityList[Config::Secondary::InUseBlocksSize]);
IntegrityList[0] = Ptr;
}
}

InUseBlocks.push_back(H);
AllocatedBytes += H->CommitSize;
FragmentedBytes += H->MemMap.getCapacity() - H->CommitSize;
Expand Down Expand Up @@ -662,6 +730,56 @@ void *MapAllocator<Config>::allocate(const Options &Options, uptr Size,
*BlockEndPtr = CommitBase + CommitSize;
{
ScopedLock L(Mutex);

if (Config::Secondary::VerifyInUseAddresses) {
void **IntegrityList =
reinterpret_cast<void **>(InUseAddresses.getBase());
bool isFull = true;
bool reachedEnd = false;

while (!reachedEnd) {
for (u32 I = 0; I < Config::Secondary::InUseBlocksSize; I++) {
if (IntegrityList[I] == nullptr) {
isFull = false;
IntegrityList[I] = reinterpret_cast<void *>(
HeaderPos + LargeBlock::getHeaderSize());
break;
}
}
if (isFull &&
IntegrityList[Config::Secondary::InUseBlocksSize] != nullptr) {
IntegrityList = static_cast<void **>(
IntegrityList[Config::Secondary::InUseBlocksSize]);
} else {
reachedEnd = true;
}
}

if (isFull) {
ReservedMemoryT InUseReserved;
InUseReserved.create(
0U, sizeof(void *) * (Config::Secondary::InUseBlocksSize + 1),
"scudo:secondary_integrity");
DCHECK_NE(InUseReserved.getBase(), 0U);

MemMapT NewAddresses = InUseReserved.dispatch(
InUseReserved.getBase(),
sizeof(void *) * (Config::Secondary::InUseBlocksSize + 1));
CHECK(NewAddresses.isAllocated());
NewAddresses.setMemoryPermission(
NewAddresses.getBase(),
sizeof(void *) * (Config::Secondary::InUseBlocksSize + 1), 0);

IntegrityList[Config::Secondary::InUseBlocksSize] =
reinterpret_cast<void *>(NewAddresses.getBase());

IntegrityList = static_cast<void **>(
IntegrityList[Config::Secondary::InUseBlocksSize]);
IntegrityList[0] =
reinterpret_cast<void *>(HeaderPos + LargeBlock::getHeaderSize());
}
}

InUseBlocks.push_back(H);
AllocatedBytes += CommitSize;
FragmentedBytes += H->MemMap.getCapacity() - CommitSize;
Expand All @@ -681,6 +799,35 @@ void MapAllocator<Config>::deallocate(const Options &Options, void *Ptr)
const uptr CommitSize = H->CommitSize;
{
ScopedLock L(Mutex);

if (Config::Secondary::VerifyInUseAddresses) {
void **IntegrityList =
reinterpret_cast<void **>(InUseAddresses.getBase());
bool isValid = false;
bool reachedEnd = false;

while (!reachedEnd) {
for (u32 I = 0; I < Config::Secondary::InUseBlocksSize; I++) {
if (IntegrityList[I] == Ptr) {
isValid = true;
IntegrityList[I] = nullptr;
break;
}
}
if (!isValid &&
IntegrityList[Config::Secondary::InUseBlocksSize] != nullptr) {
IntegrityList = static_cast<void **>(
IntegrityList[Config::Secondary::InUseBlocksSize]);
} else {
reachedEnd = true;
}
}

if (!isValid) {
return;
}
}

InUseBlocks.remove(H);
FreedBytes += CommitSize;
FragmentedBytes -= H->MemMap.getCapacity() - CommitSize;
Expand Down
4 changes: 4 additions & 0 deletions compiler-rt/lib/scudo/standalone/tests/combined_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ struct TestConditionVariableConfig {
#endif

struct Secondary {
static const bool VerifyInUseAddresses = true;
static const scudo::u32 InUseBlocksSize = 1000U;
template <typename Config>
using CacheT = scudo::MapAllocatorNoCache<Config>;
};
Expand Down Expand Up @@ -678,6 +680,8 @@ struct DeathConfig {
using PrimaryT = scudo::SizeClassAllocator64<Config>;

struct Secondary {
static const bool VerifyInUseAddresses = true;
static const scudo::u32 InUseBlocksSize = 1000U;
template <typename Config>
using CacheT = scudo::MapAllocatorNoCache<Config>;
};
Expand Down
4 changes: 4 additions & 0 deletions compiler-rt/lib/scudo/standalone/tests/secondary_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ template <typename Config> static void testSecondaryBasic(void) {
struct NoCacheConfig {
static const bool MaySupportMemoryTagging = false;
struct Secondary {
static const bool VerifyInUseAddresses = true;
static const scudo::u32 InUseBlocksSize = 1000U;
template <typename Config>
using CacheT = scudo::MapAllocatorNoCache<Config>;
};
Expand All @@ -102,6 +104,8 @@ struct TestConfig {
static const scudo::s32 MaxReleaseToOsIntervalMs = INT32_MAX;
};

static const bool VerifyInUseAddresses = true;
static const scudo::u32 InUseBlocksSize = 1000U;
template <typename Config> using CacheT = scudo::MapAllocatorCache<Config>;
};
};
Expand Down