| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| //===-- scudo_allocator.h ---------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// Header for scudo_allocator.cpp. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef SCUDO_ALLOCATOR_H_ | ||
| #define SCUDO_ALLOCATOR_H_ | ||
|
|
||
| #ifndef __x86_64__ | ||
| # error "The Scudo hardened allocator currently only supports x86_64." | ||
| #endif | ||
|
|
||
| #include "scudo_flags.h" | ||
|
|
||
| #include "sanitizer_common/sanitizer_allocator.h" | ||
|
|
||
| namespace __scudo { | ||
|
|
||
| enum AllocType : u8 { | ||
| FromMalloc = 0, // Memory block came from malloc, realloc, calloc, etc. | ||
| FromNew = 1, // Memory block came from operator new. | ||
| FromNewArray = 2, // Memory block came from operator new []. | ||
| FromMemalign = 3, // Memory block came from memalign, posix_memalign, etc. | ||
| }; | ||
|
|
||
| struct AllocatorOptions { | ||
| u32 QuarantineSizeMb; | ||
| u32 ThreadLocalQuarantineSizeKb; | ||
| bool MayReturnNull; | ||
| bool DeallocationTypeMismatch; | ||
| bool DeleteSizeMismatch; | ||
| bool ZeroContents; | ||
|
|
||
| void setFrom(const Flags *f, const CommonFlags *cf); | ||
| void copyTo(Flags *f, CommonFlags *cf) const; | ||
| }; | ||
|
|
||
| void initAllocator(const AllocatorOptions &options); | ||
| void drainQuarantine(); | ||
|
|
||
| void *scudoMalloc(uptr Size, AllocType Type); | ||
| void scudoFree(void *Ptr, AllocType Type); | ||
| void scudoSizedFree(void *Ptr, uptr Size, AllocType Type); | ||
| void *scudoRealloc(void *Ptr, uptr Size); | ||
| void *scudoCalloc(uptr NMemB, uptr Size); | ||
| void *scudoMemalign(uptr Alignment, uptr Size); | ||
| void *scudoValloc(uptr Size); | ||
| void *scudoPvalloc(uptr Size); | ||
| int scudoPosixMemalign(void **MemPtr, uptr Alignment, uptr Size); | ||
| void *scudoAlignedAlloc(uptr Alignment, uptr Size); | ||
| uptr scudoMallocUsableSize(void *Ptr); | ||
|
|
||
| } // namespace __scudo | ||
|
|
||
| #endif // SCUDO_ALLOCATOR_H_ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| //===-- scudo_flags.cpp -----------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// Hardened Allocator flag parsing logic. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "scudo_flags.h" | ||
| #include "scudo_utils.h" | ||
|
|
||
| #include "sanitizer_common/sanitizer_flags.h" | ||
| #include "sanitizer_common/sanitizer_flag_parser.h" | ||
|
|
||
| namespace __scudo { | ||
|
|
||
| Flags scudo_flags_dont_use_directly; // use via flags(). | ||
|
|
||
| void Flags::setDefaults() { | ||
| #define SCUDO_FLAG(Type, Name, DefaultValue, Description) Name = DefaultValue; | ||
| #include "scudo_flags.inc" | ||
| #undef SCUDO_FLAG | ||
| } | ||
|
|
||
| static void RegisterScudoFlags(FlagParser *parser, Flags *f) { | ||
| #define SCUDO_FLAG(Type, Name, DefaultValue, Description) \ | ||
| RegisterFlag(parser, #Name, Description, &f->Name); | ||
| #include "scudo_flags.inc" | ||
| #undef SCUDO_FLAG | ||
| } | ||
|
|
||
| void initFlags() { | ||
| SetCommonFlagsDefaults(); | ||
| { | ||
| CommonFlags cf; | ||
| cf.CopyFrom(*common_flags()); | ||
| cf.exitcode = 1; | ||
| OverrideCommonFlags(cf); | ||
| } | ||
| Flags *f = getFlags(); | ||
| f->setDefaults(); | ||
|
|
||
| FlagParser scudo_parser; | ||
| RegisterScudoFlags(&scudo_parser, f); | ||
| RegisterCommonFlags(&scudo_parser); | ||
|
|
||
| scudo_parser.ParseString(GetEnv("SCUDO_OPTIONS")); | ||
|
|
||
| InitializeCommonFlags(); | ||
|
|
||
| // Sanity checks and default settings for the Quarantine parameters. | ||
|
|
||
| if (f->QuarantineSizeMb < 0) { | ||
| const int DefaultQuarantineSizeMb = 64; | ||
| f->QuarantineSizeMb = DefaultQuarantineSizeMb; | ||
| } | ||
| // We enforce an upper limit for the quarantine size of 4Gb. | ||
| if (f->QuarantineSizeMb > (4 * 1024)) { | ||
| dieWithMessage("ERROR: the quarantine size is too large\n"); | ||
| } | ||
| if (f->ThreadLocalQuarantineSizeKb < 0) { | ||
| const int DefaultThreadLocalQuarantineSizeKb = 1024; | ||
| f->ThreadLocalQuarantineSizeKb = DefaultThreadLocalQuarantineSizeKb; | ||
| } | ||
| // And an upper limit of 128Mb for the thread quarantine cache. | ||
| if (f->ThreadLocalQuarantineSizeKb > (128 * 1024)) { | ||
| dieWithMessage("ERROR: the per thread quarantine cache size is too " | ||
| "large\n"); | ||
| } | ||
| } | ||
|
|
||
| Flags *getFlags() { | ||
| return &scudo_flags_dont_use_directly; | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| //===-- scudo_flags.h -------------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// Header for scudo_flags.cpp. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef SCUDO_FLAGS_H_ | ||
| #define SCUDO_FLAGS_H_ | ||
|
|
||
| namespace __scudo { | ||
|
|
||
| struct Flags { | ||
| #define SCUDO_FLAG(Type, Name, DefaultValue, Description) Type Name; | ||
| #include "scudo_flags.inc" | ||
| #undef SCUDO_FLAG | ||
|
|
||
| void setDefaults(); | ||
| }; | ||
|
|
||
| Flags *getFlags(); | ||
|
|
||
| void initFlags(); | ||
|
|
||
| } // namespace __scudo | ||
|
|
||
| #endif // SCUDO_FLAGS_H_ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| //===-- scudo_flags.inc -----------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// Hardened Allocator runtime flags. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef SCUDO_FLAG | ||
| # error "Define SCUDO_FLAG prior to including this file!" | ||
| #endif | ||
|
|
||
| SCUDO_FLAG(int, QuarantineSizeMb, 64, | ||
| "Size (in Mb) of quarantine used to delay the actual deallocation " | ||
| "of chunks. Lower value may reduce memory usage but decrease the " | ||
| "effectiveness of the mitigation.") | ||
|
|
||
| SCUDO_FLAG(int, ThreadLocalQuarantineSizeKb, 1024, | ||
| "Size (in Kb) of per-thread cache used to offload the global " | ||
| "quarantine. Lower value may reduce memory usage but might increase " | ||
| "the contention on the global quarantine.") | ||
|
|
||
| SCUDO_FLAG(bool, DeallocationTypeMismatch, true, | ||
| "Report errors on malloc/delete, new/free, new/delete[], etc.") | ||
|
|
||
| SCUDO_FLAG(bool, DeleteSizeMismatch, true, | ||
| "Report errors on mismatch between size of new and delete.") | ||
|
|
||
| SCUDO_FLAG(bool, ZeroContents, false, | ||
| "Zero chunk contents on allocation and deallocation.") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| //===-- scudo_interceptors.cpp ----------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// Linux specific malloc interception functions. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "sanitizer_common/sanitizer_platform.h" | ||
| #if SANITIZER_LINUX | ||
|
|
||
| #include "scudo_allocator.h" | ||
|
|
||
| #include "interception/interception.h" | ||
|
|
||
| using namespace __scudo; | ||
|
|
||
| INTERCEPTOR(void, free, void *ptr) { | ||
| scudoFree(ptr, FromMalloc); | ||
| } | ||
|
|
||
| INTERCEPTOR(void, cfree, void *ptr) { | ||
| scudoFree(ptr, FromMalloc); | ||
| } | ||
|
|
||
| INTERCEPTOR(void*, malloc, uptr size) { | ||
| return scudoMalloc(size, FromMalloc); | ||
| } | ||
|
|
||
| INTERCEPTOR(void*, realloc, void *ptr, uptr size) { | ||
| return scudoRealloc(ptr, size); | ||
| } | ||
|
|
||
| INTERCEPTOR(void*, calloc, uptr nmemb, uptr size) { | ||
| return scudoCalloc(nmemb, size); | ||
| } | ||
|
|
||
| INTERCEPTOR(void*, valloc, uptr size) { | ||
| return scudoValloc(size); | ||
| } | ||
|
|
||
| INTERCEPTOR(void*, memalign, uptr alignment, uptr size) { | ||
| return scudoMemalign(alignment, size); | ||
| } | ||
|
|
||
| INTERCEPTOR(void*, __libc_memalign, uptr alignment, uptr size) { | ||
| return scudoMemalign(alignment, size); | ||
| } | ||
|
|
||
| INTERCEPTOR(void*, pvalloc, uptr size) { | ||
| return scudoPvalloc(size); | ||
| } | ||
|
|
||
| INTERCEPTOR(void*, aligned_alloc, uptr alignment, uptr size) { | ||
| return scudoAlignedAlloc(alignment, size); | ||
| } | ||
|
|
||
| INTERCEPTOR(int, posix_memalign, void **memptr, uptr alignment, uptr size) { | ||
| return scudoPosixMemalign(memptr, alignment, size); | ||
| } | ||
|
|
||
| INTERCEPTOR(uptr, malloc_usable_size, void *ptr) { | ||
| return scudoMallocUsableSize(ptr); | ||
| } | ||
|
|
||
| INTERCEPTOR(int, mallopt, int cmd, int value) { | ||
| return -1; | ||
| } | ||
|
|
||
| #endif // SANITIZER_LINUX |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| //===-- scudo_new_delete.cpp ------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// Interceptors for operators new and delete. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "scudo_allocator.h" | ||
|
|
||
| #include "interception/interception.h" | ||
|
|
||
| #include <cstddef> | ||
|
|
||
| using namespace __scudo; | ||
|
|
||
| #define CXX_OPERATOR_ATTRIBUTE INTERCEPTOR_ATTRIBUTE | ||
|
|
||
| // Fake std::nothrow_t to avoid including <new>. | ||
| namespace std { | ||
| struct nothrow_t {}; | ||
| } // namespace std | ||
|
|
||
| CXX_OPERATOR_ATTRIBUTE | ||
| void *operator new(size_t size) { | ||
| return scudoMalloc(size, FromNew); | ||
| } | ||
| CXX_OPERATOR_ATTRIBUTE | ||
| void *operator new[](size_t size) { | ||
| return scudoMalloc(size, FromNewArray); | ||
| } | ||
| CXX_OPERATOR_ATTRIBUTE | ||
| void *operator new(size_t size, std::nothrow_t const&) { | ||
| return scudoMalloc(size, FromNew); | ||
| } | ||
| CXX_OPERATOR_ATTRIBUTE | ||
| void *operator new[](size_t size, std::nothrow_t const&) { | ||
| return scudoMalloc(size, FromNewArray); | ||
| } | ||
|
|
||
| CXX_OPERATOR_ATTRIBUTE | ||
| void operator delete(void *ptr) NOEXCEPT { | ||
| return scudoFree(ptr, FromNew); | ||
| } | ||
| CXX_OPERATOR_ATTRIBUTE | ||
| void operator delete[](void *ptr) NOEXCEPT { | ||
| return scudoFree(ptr, FromNewArray); | ||
| } | ||
| CXX_OPERATOR_ATTRIBUTE | ||
| void operator delete(void *ptr, std::nothrow_t const&) NOEXCEPT { | ||
| return scudoFree(ptr, FromNew); | ||
| } | ||
| CXX_OPERATOR_ATTRIBUTE | ||
| void operator delete[](void *ptr, std::nothrow_t const&) NOEXCEPT { | ||
| return scudoFree(ptr, FromNewArray); | ||
| } | ||
| CXX_OPERATOR_ATTRIBUTE | ||
| void operator delete(void *ptr, size_t size) NOEXCEPT { | ||
| scudoSizedFree(ptr, size, FromNew); | ||
| } | ||
| CXX_OPERATOR_ATTRIBUTE | ||
| void operator delete[](void *ptr, size_t size) NOEXCEPT { | ||
| scudoSizedFree(ptr, size, FromNewArray); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| //===-- scudo_termination.cpp -----------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// This file contains bare-bones termination functions to replace the | ||
| /// __sanitizer ones, in order to avoid any potential abuse of the callbacks | ||
| /// functionality. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "sanitizer_common/sanitizer_common.h" | ||
|
|
||
| namespace __sanitizer { | ||
|
|
||
| bool AddDieCallback(DieCallbackType callback) { return true; } | ||
|
|
||
| bool RemoveDieCallback(DieCallbackType callback) { return true; } | ||
|
|
||
| void SetUserDieCallback(DieCallbackType callback) {} | ||
|
|
||
| void NORETURN Die() { | ||
| if (common_flags()->abort_on_error) | ||
| Abort(); | ||
| internal__exit(common_flags()->exitcode); | ||
| } | ||
|
|
||
| void SetCheckFailedCallback(CheckFailedCallbackType callback) {} | ||
|
|
||
| void NORETURN CheckFailed(const char *file, int line, const char *cond, | ||
| u64 v1, u64 v2) { | ||
| Report("Sanitizer CHECK failed: %s:%d %s (%lld, %lld)\n", file, line, cond, | ||
| v1, v2); | ||
| Die(); | ||
| } | ||
|
|
||
| } // namespace __sanitizer |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| //===-- scudo_utils.cpp -----------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// Platform specific utility functions. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "scudo_utils.h" | ||
|
|
||
| #include <errno.h> | ||
| #include <fcntl.h> | ||
| #include <stdarg.h> | ||
| #include <unistd.h> | ||
|
|
||
| #include <cstring> | ||
|
|
||
| // TODO(kostyak): remove __sanitizer *Printf uses in favor for our own less | ||
| // complicated string formatting code. The following is a | ||
| // temporary workaround to be able to use __sanitizer::VSNPrintf. | ||
| namespace __sanitizer { | ||
|
|
||
| extern int VSNPrintf(char *buff, int buff_length, const char *format, | ||
| va_list args); | ||
|
|
||
| } // namespace __sanitizer | ||
|
|
||
| namespace __scudo { | ||
|
|
||
| FORMAT(1, 2) | ||
| void dieWithMessage(const char *Format, ...) { | ||
| // Our messages are tiny, 128 characters is more than enough. | ||
| char Message[128]; | ||
| va_list Args; | ||
| va_start(Args, Format); | ||
| __sanitizer::VSNPrintf(Message, sizeof(Message), Format, Args); | ||
| va_end(Args); | ||
| RawWrite(Message); | ||
| Die(); | ||
| } | ||
|
|
||
| typedef struct { | ||
| u32 Eax; | ||
| u32 Ebx; | ||
| u32 Ecx; | ||
| u32 Edx; | ||
| } CPUIDInfo; | ||
|
|
||
| static void getCPUID(CPUIDInfo *info, u32 leaf, u32 subleaf) | ||
| { | ||
| asm volatile("cpuid" | ||
| : "=a" (info->Eax), "=b" (info->Ebx), "=c" (info->Ecx), "=d" (info->Edx) | ||
| : "a" (leaf), "c" (subleaf) | ||
| ); | ||
| } | ||
|
|
||
| // Returns true is the CPU is a "GenuineIntel" or "AuthenticAMD" | ||
| static bool isSupportedCPU() | ||
| { | ||
| CPUIDInfo Info; | ||
|
|
||
| getCPUID(&Info, 0, 0); | ||
| if (memcmp(reinterpret_cast<char *>(&Info.Ebx), "Genu", 4) == 0 && | ||
| memcmp(reinterpret_cast<char *>(&Info.Edx), "ineI", 4) == 0 && | ||
| memcmp(reinterpret_cast<char *>(&Info.Ecx), "ntel", 4) == 0) { | ||
| return true; | ||
| } | ||
| if (memcmp(reinterpret_cast<char *>(&Info.Ebx), "Auth", 4) == 0 && | ||
| memcmp(reinterpret_cast<char *>(&Info.Edx), "enti", 4) == 0 && | ||
| memcmp(reinterpret_cast<char *>(&Info.Ecx), "cAMD", 4) == 0) { | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| bool testCPUFeature(CPUFeature feature) | ||
| { | ||
| static bool InfoInitialized = false; | ||
| static CPUIDInfo CPUInfo = {}; | ||
|
|
||
| if (InfoInitialized == false) { | ||
| if (isSupportedCPU() == true) | ||
| getCPUID(&CPUInfo, 1, 0); | ||
| else | ||
| UNIMPLEMENTED(); | ||
| InfoInitialized = true; | ||
| } | ||
| switch (feature) { | ||
| case SSE4_2: | ||
| return ((CPUInfo.Ecx >> 20) & 0x1) != 0; | ||
| default: | ||
| break; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| // readRetry will attempt to read Count bytes from the Fd specified, and if | ||
| // interrupted will retry to read additional bytes to reach Count. | ||
| static ssize_t readRetry(int Fd, u8 *Buffer, size_t Count) { | ||
| ssize_t AmountRead = 0; | ||
| while (static_cast<size_t>(AmountRead) < Count) { | ||
| ssize_t Result = read(Fd, Buffer + AmountRead, Count - AmountRead); | ||
| if (Result > 0) | ||
| AmountRead += Result; | ||
| else if (!Result) | ||
| break; | ||
| else if (errno != EINTR) { | ||
| AmountRead = -1; | ||
| break; | ||
| } | ||
| } | ||
| return AmountRead; | ||
| } | ||
|
|
||
| // Default constructor for Xorshift128Plus seeds the state with /dev/urandom | ||
| Xorshift128Plus::Xorshift128Plus() { | ||
| int Fd = open("/dev/urandom", O_RDONLY); | ||
| bool Success = readRetry(Fd, reinterpret_cast<u8 *>(&State_0_), | ||
| sizeof(State_0_)) == sizeof(State_0_); | ||
| Success &= readRetry(Fd, reinterpret_cast<u8 *>(&State_1_), | ||
| sizeof(State_1_)) == sizeof(State_1_); | ||
| close(Fd); | ||
| if (!Success) { | ||
| dieWithMessage("ERROR: failed to read enough data from /dev/urandom.\n"); | ||
| } | ||
| } | ||
|
|
||
| } // namespace __scudo |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| //===-- scudo_utils.h -------------------------------------------*- C++ -*-===// | ||
| // | ||
| // The LLVM Compiler Infrastructure | ||
| // | ||
| // This file is distributed under the University of Illinois Open Source | ||
| // License. See LICENSE.TXT for details. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// Header for scudo_utils.cpp. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef SCUDO_UTILS_H_ | ||
| #define SCUDO_UTILS_H_ | ||
|
|
||
| #include <string.h> | ||
|
|
||
| #include "sanitizer_common/sanitizer_common.h" | ||
|
|
||
| namespace __scudo { | ||
|
|
||
| template <class Dest, class Source> | ||
| inline Dest bit_cast(const Source& source) { | ||
| static_assert(sizeof(Dest) == sizeof(Source), "Sizes are not equal!"); | ||
| Dest dest; | ||
| memcpy(&dest, &source, sizeof(dest)); | ||
| return dest; | ||
| } | ||
|
|
||
| void dieWithMessage(const char *Format, ...); | ||
|
|
||
| enum CPUFeature { | ||
| SSE4_2 = 0, | ||
| ENUM_CPUFEATURE_MAX | ||
| }; | ||
| bool testCPUFeature(CPUFeature feature); | ||
|
|
||
| // Tiny PRNG based on https://en.wikipedia.org/wiki/Xorshift#xorshift.2B | ||
| // The state (128 bits) will be stored in thread local storage. | ||
| struct Xorshift128Plus { | ||
| public: | ||
| Xorshift128Plus(); | ||
| u64 Next() { | ||
| u64 x = State_0_; | ||
| const u64 y = State_1_; | ||
| State_0_ = y; | ||
| x ^= x << 23; | ||
| State_1_ = x ^ y ^ (x >> 17) ^ (y >> 26); | ||
| return State_1_ + y; | ||
| } | ||
| private: | ||
| u64 State_0_; | ||
| u64 State_1_; | ||
| }; | ||
|
|
||
| } // namespace __scudo | ||
|
|
||
| #endif // SCUDO_UTILS_H_ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| set(SCUDO_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) | ||
| set(SCUDO_LIT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) | ||
|
|
||
|
|
||
| set(SCUDO_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS}) | ||
| if(NOT COMPILER_RT_STANDALONE_BUILD) | ||
| list(APPEND SCUDO_TEST_DEPS scudo) | ||
| endif() | ||
|
|
||
| configure_lit_site_cfg( | ||
| ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in | ||
| ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg | ||
| ) | ||
|
|
||
| if(CMAKE_SYSTEM_NAME MATCHES "Linux") | ||
| EXEC_PROGRAM(cat ARGS "/proc/cpuinfo" OUTPUT_VARIABLE CPUINFO) | ||
| STRING(REGEX REPLACE "^.*(sse4_2).*$" "\\1" SSE_THERE ${CPUINFO}) | ||
| STRING(COMPARE EQUAL "sse4_2" "${SSE_THERE}" SSE42_TRUE) | ||
| endif(CMAKE_SYSTEM_NAME MATCHES "Linux") | ||
|
|
||
| if (SSE42_TRUE AND CMAKE_SIZEOF_VOID_P EQUAL 8) | ||
| add_lit_testsuite(check-scudo | ||
| "Running the Scudo Hardened Allocator tests" | ||
| ${CMAKE_CURRENT_BINARY_DIR} | ||
| DEPENDS ${SCUDO_TEST_DEPS}) | ||
| set_target_properties(check-scudo PROPERTIES FOLDER | ||
| "Scudo Hardened Allocator tests") | ||
| endif(SSE42_TRUE AND CMAKE_SIZEOF_VOID_P EQUAL 8) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| // RUN: %clang_scudo %s -o %t | ||
| // RUN: not %run %t pointers 2>&1 | FileCheck %s | ||
|
|
||
| // Tests that a non-16-byte aligned pointer will trigger the associated error | ||
| // on deallocation. | ||
|
|
||
| #include <assert.h> | ||
| #include <malloc.h> | ||
| #include <stdint.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
|
|
||
| int main(int argc, char **argv) | ||
| { | ||
| assert(argc == 2); | ||
| if (!strcmp(argv[1], "pointers")) { | ||
| void *p = malloc(1U << 16); | ||
| if (!p) | ||
| return 1; | ||
| free(reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(p) | 8)); | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| // CHECK: ERROR: attempted to deallocate a chunk not properly aligned |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| // RUN: %clang_scudo %s -o %t | ||
| // RUN: not %run %t malloc 2>&1 | FileCheck %s | ||
| // RUN: not %run %t new 2>&1 | FileCheck %s | ||
| // RUN: not %run %t newarray 2>&1 | FileCheck %s | ||
| // RUN: not %run %t memalign 2>&1 | FileCheck %s | ||
|
|
||
| // Tests double-free error on pointers allocated with different allocation | ||
| // functions. | ||
|
|
||
| #include <assert.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
|
|
||
| int main(int argc, char **argv) | ||
| { | ||
| assert(argc == 2); | ||
| if (!strcmp(argv[1], "malloc")) { | ||
| void *p = malloc(sizeof(int)); | ||
| if (!p) | ||
| return 1; | ||
| free(p); | ||
| free(p); | ||
| } | ||
| if (!strcmp(argv[1], "new")) { | ||
| int *p = new int; | ||
| if (!p) | ||
| return 1; | ||
| delete p; | ||
| delete p; | ||
| } | ||
| if (!strcmp(argv[1], "newarray")) { | ||
| int *p = new int[8]; | ||
| if (!p) | ||
| return 1; | ||
| delete[] p; | ||
| delete[] p; | ||
| } | ||
| if (!strcmp(argv[1], "memalign")) { | ||
| void *p = nullptr; | ||
| posix_memalign(&p, 0x100, sizeof(int)); | ||
| if (!p) | ||
| return 1; | ||
| free(p); | ||
| free(p); | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| // CHECK: ERROR: invalid chunk state when deallocating address |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # -*- Python -*- | ||
|
|
||
| import os | ||
|
|
||
| # Setup config name. | ||
| config.name = 'Scudo' | ||
|
|
||
| # Setup source root. | ||
| config.test_source_root = os.path.dirname(__file__) | ||
|
|
||
| # Path to the static library | ||
| base_lib = os.path.join(config.compiler_rt_libdir, | ||
| "libclang_rt.scudo-%s.a" % config.target_arch) | ||
| whole_archive = "-Wl,-whole-archive %s -Wl,-no-whole-archive " % base_lib | ||
|
|
||
| # Test suffixes. | ||
| config.suffixes = ['.c', '.cc', '.cpp', '.m', '.mm', '.ll', '.test'] | ||
|
|
||
| # C flags. | ||
| c_flags = ["-std=c++11", | ||
| "-lstdc++", | ||
| "-ldl", | ||
| "-lrt", | ||
| "-pthread", | ||
| "-latomic", | ||
| "-fPIE", | ||
| "-pie", | ||
| "-O0"] | ||
|
|
||
| def build_invocation(compile_flags): | ||
| return " " + " ".join([config.clang] + compile_flags) + " " | ||
|
|
||
| # Add clang substitutions. | ||
| config.substitutions.append( ("%clang_scudo ", | ||
| build_invocation(c_flags) + whole_archive) ) | ||
|
|
||
| # Hardened Allocator tests are currently supported on Linux only. | ||
| if config.host_os not in ['Linux']: | ||
| config.unsupported = True |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| @LIT_SITE_CFG_IN_HEADER@ | ||
|
|
||
| # Load common config for all compiler-rt lit tests. | ||
| lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured") | ||
|
|
||
| # Load tool-specific config that would do the real work. | ||
| lit_config.load_config(config, "@SCUDO_LIT_SOURCE_DIR@/lit.cfg") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // RUN: %clang_scudo %s -o %t | ||
| // RUN: %run %t 2>&1 | ||
|
|
||
| // Tests that a regular workflow of allocation, memory fill and free works as | ||
| // intended. Also tests that a zero-sized allocation succeeds. | ||
|
|
||
| #include <malloc.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
|
|
||
| int main(int argc, char **argv) | ||
| { | ||
| void *p; | ||
| size_t size = 1U << 8; | ||
|
|
||
| p = malloc(size); | ||
| if (!p) | ||
| return 1; | ||
| memset(p, 'A', size); | ||
| free(p); | ||
| p = malloc(0); | ||
| if (!p) | ||
| return 1; | ||
| free(p); | ||
|
|
||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| // RUN: %clang_scudo %s -o %t | ||
| // RUN: %run %t valid 2>&1 | ||
| // RUN: not %run %t invalid 2>&1 | FileCheck %s | ||
|
|
||
| // Tests that the various aligned allocation functions work as intended. Also | ||
| // tests for the condition where the alignment is not a power of 2. | ||
|
|
||
| #include <assert.h> | ||
| #include <malloc.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
|
|
||
| int main(int argc, char **argv) | ||
| { | ||
| void *p; | ||
| size_t alignment = 1U << 12; | ||
| size_t size = alignment; | ||
|
|
||
| assert(argc == 2); | ||
| if (!strcmp(argv[1], "valid")) { | ||
| p = memalign(alignment, size); | ||
| if (!p) | ||
| return 1; | ||
| free(p); | ||
| p = nullptr; | ||
| posix_memalign(&p, alignment, size); | ||
| if (!p) | ||
| return 1; | ||
| free(p); | ||
| p = aligned_alloc(alignment, size); | ||
| if (!p) | ||
| return 1; | ||
| free(p); | ||
| } | ||
| if (!strcmp(argv[1], "invalid")) { | ||
| p = memalign(alignment - 1, size); | ||
| free(p); | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| // CHECK: ERROR: malloc alignment is not a power of 2 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| // RUN: %clang_scudo %s -o %t | ||
| // RUN: SCUDO_OPTIONS=DeallocationTypeMismatch=1 not %run %t mallocdel 2>&1 | FileCheck %s | ||
| // RUN: SCUDO_OPTIONS=DeallocationTypeMismatch=0 %run %t mallocdel 2>&1 | ||
| // RUN: SCUDO_OPTIONS=DeallocationTypeMismatch=1 not %run %t newfree 2>&1 | FileCheck %s | ||
| // RUN: SCUDO_OPTIONS=DeallocationTypeMismatch=0 %run %t newfree 2>&1 | ||
| // RUN: SCUDO_OPTIONS=DeallocationTypeMismatch=1 not %run %t memaligndel 2>&1 | FileCheck %s | ||
| // RUN: SCUDO_OPTIONS=DeallocationTypeMismatch=0 %run %t memaligndel 2>&1 | ||
|
|
||
| // Tests that type mismatches between allocation and deallocation functions are | ||
| // caught when the related option is set. | ||
|
|
||
| #include <assert.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
| #include <malloc.h> | ||
|
|
||
| int main(int argc, char **argv) | ||
| { | ||
| assert(argc == 2); | ||
| if (!strcmp(argv[1], "mallocdel")) { | ||
| int *p = (int *)malloc(16); | ||
| if (!p) | ||
| return 1; | ||
| delete p; | ||
| } | ||
| if (!strcmp(argv[1], "newfree")) { | ||
| int *p = new int; | ||
| if (!p) | ||
| return 1; | ||
| free((void *)p); | ||
| } | ||
| if (!strcmp(argv[1], "memaligndel")) { | ||
| int *p = (int *)memalign(0x10, 0x10); | ||
| if (!p) | ||
| return 1; | ||
| delete p; | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| // CHECK: ERROR: allocation type mismatch on address |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| // RUN: %clang_scudo %s -o %t | ||
| // RUN: not %run %t malloc 2>&1 | FileCheck %s | ||
| // RUN: SCUDO_OPTIONS=QuarantineSizeMb=1 not %run %t quarantine 2>&1 | FileCheck %s | ||
|
|
||
| // Tests that header corruption of an allocated or quarantined chunk is caught. | ||
|
|
||
| #include <assert.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
|
|
||
| int main(int argc, char **argv) | ||
| { | ||
| assert(argc == 2); | ||
| if (!strcmp(argv[1], "malloc")) { | ||
| // Simulate a header corruption of an allocated chunk (1-bit) | ||
| void *p = malloc(1U << 4); | ||
| if (!p) | ||
| return 1; | ||
| ((char *)p)[-1] ^= 1; | ||
| free(p); | ||
| } | ||
| if (!strcmp(argv[1], "quarantine")) { | ||
| void *p = malloc(1U << 4); | ||
| if (!p) | ||
| return 1; | ||
| free(p); | ||
| // Simulate a header corruption of a quarantined chunk | ||
| ((char *)p)[-2] ^= 1; | ||
| // Trigger the quarantine recycle | ||
| for (int i = 0; i < 0x100; i++) { | ||
| p = malloc(1U << 16); | ||
| free(p); | ||
| } | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| // CHECK: ERROR: corrupted chunk header at address |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| // RUN: %clang_scudo %s -o %t | ||
| // RUN: %run %t 2>&1 | ||
|
|
||
| // Verifies that calling malloc in a preinit_array function succeeds, and that | ||
| // the resulting pointer can be freed at program termination. | ||
|
|
||
| #include <malloc.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
|
|
||
| static void *global_p = nullptr; | ||
|
|
||
| void __init(void) { | ||
| global_p = malloc(1); | ||
| if (!global_p) | ||
| exit(1); | ||
| } | ||
|
|
||
| void __fini(void) { | ||
| if (global_p) | ||
| free(global_p); | ||
| } | ||
|
|
||
| int main(int argc, char **argv) | ||
| { | ||
| void *p = malloc(1); | ||
| if (!p) | ||
| return 1; | ||
| free(p); | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| __attribute__((section(".preinit_array"), used)) | ||
| void (*__local_preinit)(void) = __init; | ||
| __attribute__((section(".fini_array"), used)) | ||
| void (*__local_fini)(void) = __fini; | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| // RUN: %clang_scudo %s -o %t | ||
| // RUN: SCUDO_OPTIONS=QuarantineSizeMb=1 %run %t 2>&1 | ||
|
|
||
| // Tests that the quarantine prevents a chunk from being reused right away. | ||
| // Also tests that a chunk will eventually become available again for | ||
| // allocation when the recycling criteria has been met. | ||
|
|
||
| #include <malloc.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
|
|
||
| int main(int argc, char **argv) | ||
| { | ||
| void *p, *old_p; | ||
| size_t size = 1U << 16; | ||
|
|
||
| // The delayed freelist will prevent a chunk from being available right away | ||
| p = malloc(size); | ||
| if (!p) | ||
| return 1; | ||
| old_p = p; | ||
| free(p); | ||
| p = malloc(size); | ||
| if (!p) | ||
| return 1; | ||
| if (old_p == p) | ||
| return 1; | ||
| free(p); | ||
|
|
||
| // Eventually the chunk should become available again | ||
| bool found = false; | ||
| for (int i = 0; i < 0x100 && found == false; i++) { | ||
| p = malloc(size); | ||
| if (!p) | ||
| return 1; | ||
| found = (p == old_p); | ||
| free(p); | ||
| } | ||
| if (found == false) | ||
| return 1; | ||
|
|
||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| // RUN: %clang_scudo %s -o %t | ||
| // RUN: %run %t pointers 2>&1 | ||
| // RUN: %run %t contents 2>&1 | ||
| // RUN: not %run %t memalign 2>&1 | FileCheck %s | ||
|
|
||
| // Tests that our reallocation function returns the same pointer when the | ||
| // requested size can fit into the previously allocated chunk. Also tests that | ||
| // a new chunk is returned if the size is greater, and that the contents of the | ||
| // chunk are left unchanged. | ||
| // As a final test, make sure that a chunk allocated by memalign cannot be | ||
| // reallocated. | ||
|
|
||
| #include <assert.h> | ||
| #include <malloc.h> | ||
| #include <string.h> | ||
|
|
||
| int main(int argc, char **argv) | ||
| { | ||
| void *p, *old_p; | ||
| size_t size = 32; | ||
|
|
||
| assert(argc == 2); | ||
| if (!strcmp(argv[1], "pointers")) { | ||
| old_p = p = realloc(nullptr, size); | ||
| if (!p) | ||
| return 1; | ||
| size = malloc_usable_size(p); | ||
| // Our realloc implementation will return the same pointer if the size | ||
| // requested is lower or equal to the usable size of the associated chunk. | ||
| p = realloc(p, size - 1); | ||
| if (p != old_p) | ||
| return 1; | ||
| p = realloc(p, size); | ||
| if (p != old_p) | ||
| return 1; | ||
| // And a new one if the size is greater. | ||
| p = realloc(p, size + 1); | ||
| if (p == old_p) | ||
| return 1; | ||
| // A size of 0 will free the chunk and return nullptr. | ||
| p = realloc(p, 0); | ||
| if (p) | ||
| return 1; | ||
| old_p = nullptr; | ||
| } | ||
| if (!strcmp(argv[1], "contents")) { | ||
| p = realloc(nullptr, size); | ||
| if (!p) | ||
| return 1; | ||
| for (int i = 0; i < size; i++) | ||
| reinterpret_cast<char *>(p)[i] = 'A'; | ||
| p = realloc(p, size + 1); | ||
| // The contents of the reallocated chunk must match the original one. | ||
| for (int i = 0; i < size; i++) | ||
| if (reinterpret_cast<char *>(p)[i] != 'A') | ||
| return 1; | ||
| } | ||
| if (!strcmp(argv[1], "memalign")) { | ||
| // A chunk coming from memalign cannot be reallocated. | ||
| p = memalign(16, size); | ||
| if (!p) | ||
| return 1; | ||
| p = realloc(p, size); | ||
| free(p); | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| // CHECK: ERROR: invalid chunk type when reallocating address |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| // RUN: %clang_scudo -fsized-deallocation %s -o %t | ||
| // RUN: SCUDO_OPTIONS=DeleteSizeMismatch=1 %run %t gooddel 2>&1 | ||
| // RUN: SCUDO_OPTIONS=DeleteSizeMismatch=1 not %run %t baddel 2>&1 | FileCheck %s | ||
| // RUN: SCUDO_OPTIONS=DeleteSizeMismatch=0 %run %t baddel 2>&1 | ||
| // RUN: SCUDO_OPTIONS=DeleteSizeMismatch=1 %run %t gooddelarr 2>&1 | ||
| // RUN: SCUDO_OPTIONS=DeleteSizeMismatch=1 not %run %t baddelarr 2>&1 | FileCheck %s | ||
| // RUN: SCUDO_OPTIONS=DeleteSizeMismatch=0 %run %t baddelarr 2>&1 | ||
|
|
||
| // Ensures that the sized delete operator errors out when the appropriate | ||
| // option is passed and the sizes do not match between allocation and | ||
| // deallocation functions. | ||
|
|
||
| #include <new> | ||
| #include <assert.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
|
|
||
| int main(int argc, char **argv) | ||
| { | ||
| assert(argc == 2); | ||
| if (!strcmp(argv[1], "gooddel")) { | ||
| long long *p = new long long; | ||
| operator delete(p, sizeof(long long)); | ||
| } | ||
| if (!strcmp(argv[1], "baddel")) { | ||
| long long *p = new long long; | ||
| operator delete(p, 2); | ||
| } | ||
| if (!strcmp(argv[1], "gooddelarr")) { | ||
| char *p = new char[64]; | ||
| operator delete[](p, 64); | ||
| } | ||
| if (!strcmp(argv[1], "baddelarr")) { | ||
| char *p = new char[63]; | ||
| operator delete[](p, 64); | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| // CHECK: ERROR: invalid sized delete on chunk at address |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| // RUN: %clang_scudo %s -o %t | ||
| // RUN: SCUDO_OPTIONS=allocator_may_return_null=0 not %run %t malloc 2>&1 | FileCheck %s | ||
| // RUN: SCUDO_OPTIONS=allocator_may_return_null=1 %run %t malloc 2>&1 | ||
| // RUN: SCUDO_OPTIONS=allocator_may_return_null=0 not %run %t calloc 2>&1 | FileCheck %s | ||
| // RUN: SCUDO_OPTIONS=allocator_may_return_null=1 %run %t calloc 2>&1 | ||
| // RUN: %run %t usable 2>&1 | ||
|
|
||
| // Tests for various edge cases related to sizes, notably the maximum size the | ||
| // allocator can allocate. Tests that an integer overflow in the parameters of | ||
| // calloc is caught. | ||
|
|
||
| #include <assert.h> | ||
| #include <malloc.h> | ||
| #include <stdlib.h> | ||
| #include <string.h> | ||
|
|
||
| #include <limits> | ||
|
|
||
| int main(int argc, char **argv) | ||
| { | ||
| assert(argc == 2); | ||
| if (!strcmp(argv[1], "malloc")) { | ||
| // Currently the maximum size the allocator can allocate is 1ULL<<40 bytes. | ||
| size_t size = std::numeric_limits<size_t>::max(); | ||
| void *p = malloc(size); | ||
| if (p) | ||
| return 1; | ||
| size = (1ULL << 40) - 16; | ||
| p = malloc(size); | ||
| if (p) | ||
| return 1; | ||
| } | ||
| if (!strcmp(argv[1], "calloc")) { | ||
| // Trigger an overflow in calloc. | ||
| size_t size = std::numeric_limits<size_t>::max(); | ||
| void *p = calloc((size / 0x1000) + 1, 0x1000); | ||
| if (p) | ||
| return 1; | ||
| } | ||
| if (!strcmp(argv[1], "usable")) { | ||
| // Playing with the actual usable size of a chunk. | ||
| void *p = malloc(1007); | ||
| if (!p) | ||
| return 1; | ||
| size_t size = malloc_usable_size(p); | ||
| if (size < 1007) | ||
| return 1; | ||
| memset(p, 'A', size); | ||
| p = realloc(p, 2014); | ||
| if (!p) | ||
| return 1; | ||
| size = malloc_usable_size(p); | ||
| if (size < 2014) | ||
| return 1; | ||
| memset(p, 'B', size); | ||
| free(p); | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| // CHECK: allocator is terminating the process |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| ======================== | ||
| Scudo Hardened Allocator | ||
| ======================== | ||
|
|
||
| .. contents:: | ||
| :local: | ||
| :depth: 1 | ||
|
|
||
| Introduction | ||
| ============ | ||
| The Scudo Hardened Allocator is a user-mode allocator based on LLVM Sanitizer's | ||
| CombinedAllocator, which aims at providing additional mitigations against heap | ||
| based vulnerabilities, while maintaining good performance. | ||
|
|
||
| The name "Scudo" has been retained from the initial implementation (Escudo | ||
| meaning Shield in Spanish and Portuguese). | ||
|
|
||
| Design | ||
| ====== | ||
| Chunk Header | ||
| ------------ | ||
| Every chunk of heap memory will be preceded by a chunk header. This has two | ||
| purposes, the first one being to store various information about the chunk, | ||
| the second one being to detect potential heap overflows. In order to achieve | ||
| this, the header will be checksumed, involving the pointer to the chunk itself | ||
| and a global secret. Any corruption of the header will be detected when said | ||
| header is accessed, and the process terminated. | ||
|
|
||
| The following information is stored in the header: | ||
|
|
||
| - the 16-bit checksum; | ||
| - the user requested size for that chunk, which is necessary for reallocation | ||
| purposes; | ||
| - the state of the chunk (available, allocated or quarantined); | ||
| - the allocation type (malloc, new, new[] or memalign), to detect potential | ||
| mismatches in the allocation APIs used; | ||
| - whether or not the chunk is offseted (ie: if the chunk beginning is different | ||
| than the backend allocation beginning, which is most often the case with some | ||
| aligned allocations); | ||
| - the associated offset; | ||
| - a 16-bit salt. | ||
|
|
||
| On x64, which is currently the only architecture supported, the header fits | ||
| within 16-bytes, which works nicely with the minimum alignment requirements. | ||
|
|
||
| The checksum is computed as a CRC32 (requiring the SSE 4.2 instruction set) | ||
| of the global secret, the chunk pointer itself, and the 16 bytes of header with | ||
| the checksum field zeroed out. | ||
|
|
||
| The header is atomically loaded and stored to prevent races (this requires | ||
| platform support such as the cmpxchg16b instruction). This is important as two | ||
| consecutive chunks could belong to different threads. We also want to avoid | ||
| any type of double fetches of information located in the header, and use local | ||
| copies of the header for this purpose. | ||
|
|
||
| Delayed Freelist | ||
| ----------------- | ||
| A delayed freelist allows us to not return a chunk directly to the backend, but | ||
| to keep it aside for a while. Once a criterion is met, the delayed freelist is | ||
| emptied, and the quarantined chunks are returned to the backend. This helps | ||
| mitigate use-after-free vulnerabilities by reducing the determinism of the | ||
| allocation and deallocation patterns. | ||
|
|
||
| This feature is using the Sanitizer's Quarantine as its base, and the amount of | ||
| memory that it can hold is configurable by the user (see the Options section | ||
| below). | ||
|
|
||
| Randomness | ||
| ---------- | ||
| It is important for the allocator to not make use of fixed addresses. We use | ||
| the dynamic base option for the SizeClassAllocator, allowing us to benefit | ||
| from the randomness of mmap. | ||
|
|
||
| Usage | ||
| ===== | ||
|
|
||
| Library | ||
| ------- | ||
| The allocator static library can be built from the LLVM build tree thanks to | ||
| the "scudo" CMake rule. The associated tests can be exercised thanks to the | ||
| "check-scudo" CMake rule. | ||
|
|
||
| Linking the static library to your project can require the use of the | ||
| "whole-archive" linker flag (or equivalent), depending on your linker. | ||
| Additional flags might also be necessary. | ||
|
|
||
| Your linked binary should now make use of the Scudo allocation and deallocation | ||
| functions. | ||
|
|
||
| Options | ||
| ------- | ||
| Several aspects of the allocator can be configured through environment options, | ||
| following the usual ASan options syntax, through the variable SCUDO_OPTIONS. | ||
|
|
||
| For example: SCUDO_OPTIONS="DeleteSizeMismatch=1:QuarantineSizeMb=16". | ||
|
|
||
| The following options are available: | ||
|
|
||
| - QuarantineSizeMb (integer, defaults to 64): the size (in Mb) of quarantine | ||
| used to delay the actual deallocation of chunks. Lower value may reduce | ||
| memory usage but decrease the effectiveness of the mitigation; a negative | ||
| value will fallback to a default of 64Mb; | ||
|
|
||
| - ThreadLocalQuarantineSizeKb (integer, default to 1024): the size (in Kb) of | ||
| per-thread cache used to offload the global quarantine. Lower value may | ||
| reduce memory usage but might increase the contention on the global | ||
| quarantine. | ||
|
|
||
| - DeallocationTypeMismatch (boolean, defaults to true): whether or not we report | ||
| errors on malloc/delete, new/free, new/delete[], etc; | ||
|
|
||
| - DeleteSizeMismatch (boolean, defaults to true): whether or not we report | ||
| errors on mismatch between size of new and delete; | ||
|
|
||
| - ZeroContents (boolean, defaults to false): whether or not we zero chunk | ||
| contents on allocation and deallocation. | ||
|
|