diff --git a/compiler-rt/lib/scudo/standalone/CMakeLists.txt b/compiler-rt/lib/scudo/standalone/CMakeLists.txt --- a/compiler-rt/lib/scudo/standalone/CMakeLists.txt +++ b/compiler-rt/lib/scudo/standalone/CMakeLists.txt @@ -1,4 +1,5 @@ add_compiler_rt_component(scudo_standalone) +add_dependencies(scudo_standalone gwp_asan) include_directories(../..) @@ -101,6 +102,13 @@ wrappers_cpp.cpp ) +set(SCUDO_OBJECT_LIBS) + +if (COMPILER_RT_HAS_GWP_ASAN) + list(APPEND SCUDO_OBJECT_LIBS RTGwpAsan) + list(APPEND SCUDO_CFLAGS -DGWP_ASAN_HOOKS) +endif() + if(COMPILER_RT_HAS_SCUDO_STANDALONE) add_compiler_rt_object_libraries(RTScudoStandalone ARCHS ${SCUDO_STANDALONE_SUPPORTED_ARCH} @@ -124,6 +132,7 @@ SOURCES ${SCUDO_SOURCES} ${SCUDO_SOURCES_C_WRAPPERS} ADDITIONAL_HEADERS ${SCUDO_HEADERS} CFLAGS ${SCUDO_CFLAGS} + OBJECT_LIBS ${SCUDO_OBJECT_LIBS} PARENT_TARGET scudo_standalone) add_compiler_rt_runtime(clang_rt.scudo_standalone_cxx STATIC @@ -131,6 +140,7 @@ SOURCES ${SCUDO_SOURCES_CXX_WRAPPERS} ADDITIONAL_HEADERS ${SCUDO_HEADERS} CFLAGS ${SCUDO_CFLAGS} + OBJECT_LIBS ${SCUDO_OBJECT_LIBS} PARENT_TARGET scudo_standalone) add_subdirectory(benchmarks) diff --git a/compiler-rt/lib/scudo/standalone/combined.h b/compiler-rt/lib/scudo/standalone/combined.h --- a/compiler-rt/lib/scudo/standalone/combined.h +++ b/compiler-rt/lib/scudo/standalone/combined.h @@ -18,8 +18,19 @@ #include "quarantine.h" #include "report.h" #include "secondary.h" +#include "string_utils.h" #include "tsd.h" +#ifdef GWP_ASAN_HOOKS +# include "gwp_asan/guarded_pool_allocator.h" +// GWP-ASan is declared here in order to avoid indirect call overhead. It's also +// instantiated outside of the Allocator class, as the allocator is only +// zero-initialised. GWP-ASan requires constant initialisation, and the Scudo +// allocator doesn't have a constexpr constructor (see discussion here: +// https://reviews.llvm.org/D69265#inline-624315). +static gwp_asan::GuardedPoolAllocator GuardedAlloc; +#endif // GWP_ASAN_HOOKS + namespace scudo { template class Allocator { @@ -133,6 +144,18 @@ Quarantine.init( static_cast(getFlags()->quarantine_size_kb << 10), static_cast(getFlags()->thread_local_quarantine_size_kb << 10)); + +#ifdef GWP_ASAN_HOOKS + gwp_asan::options::Options Opt; + Opt.Enabled = getFlags()->GWP_ASAN_Enabled; + Opt.PerfectlyRightAlign = getFlags()->GWP_ASAN_PerfectlyRightAlign; + Opt.MaxSimultaneousAllocations = + getFlags()->GWP_ASAN_MaxSimultaneousAllocations; + Opt.SampleRate = getFlags()->GWP_ASAN_SampleRate; + Opt.InstallSignalHandlers = getFlags()->GWP_ASAN_InstallSignalHandlers; + Opt.Printf = Printf; + GuardedAlloc.init(Opt); +#endif // GWP_ASAN_HOOKS } void reset() { memset(this, 0, sizeof(*this)); } @@ -164,6 +187,14 @@ uptr Alignment = MinAlignment, bool ZeroContents = false) { initThreadMaybe(); + +#ifdef GWP_ASAN_HOOKS + if (UNLIKELY(GuardedAlloc.shouldSample())) { + if (void *Ptr = GuardedAlloc.allocate(Size)) + return Ptr; + } +#endif // GWP_ASAN_HOOKS + ZeroContents |= static_cast(Options.ZeroContents); if (UNLIKELY(Alignment > MaxAlignment)) { @@ -260,6 +291,13 @@ // being destroyed properly. Any other heap operation will do a full init. initThreadMaybe(/*MinimalInit=*/true); +#ifdef GWP_ASAN_HOOKS + if (UNLIKELY(GuardedAlloc.pointerIsMine(Ptr))) { + GuardedAlloc.deallocate(Ptr); + return; + } +#endif // GWP_ASAN_HOOKS + if (&__scudo_deallocate_hook) __scudo_deallocate_hook(Ptr); @@ -299,6 +337,17 @@ DCHECK_NE(OldPtr, nullptr); DCHECK_NE(NewSize, 0); +#ifdef GWP_ASAN_HOOKS + if (UNLIKELY(GuardedAlloc.pointerIsMine(OldPtr))) { + uptr OldSize = GuardedAlloc.getSize(OldPtr); + void *NewPtr = allocate(NewSize, Chunk::Origin::Malloc, Alignment); + if (NewPtr) + memcpy(NewPtr, OldPtr, (NewSize < OldSize) ? NewSize : OldSize); + GuardedAlloc.deallocate(OldPtr); + return NewPtr; + } +#endif // GWP_ASAN_HOOKS + if (UNLIKELY(!isAligned(reinterpret_cast(OldPtr), MinAlignment))) reportMisalignedPointer(AllocatorAction::Reallocating, OldPtr); @@ -444,6 +493,12 @@ initThreadMaybe(); if (UNLIKELY(!Ptr)) return 0; + +#ifdef GWP_ASAN_HOOKS + if (UNLIKELY(GuardedAlloc.pointerIsMine(Ptr))) + return GuardedAlloc.getSize(Ptr); +#endif // GWP_ASAN_HOOKS + Chunk::UnpackedHeader Header; Chunk::loadHeader(Cookie, Ptr, &Header); // Getting the usable size of a chunk only makes sense if it's allocated. @@ -462,6 +517,8 @@ // A corrupted chunk will not be reported as owned, which is WAI. bool isOwned(const void *Ptr) { initThreadMaybe(); + if (GuardedAlloc.pointerIsMine(Ptr)) + return true; if (!Ptr || !isAligned(reinterpret_cast(Ptr), MinAlignment)) return false; Chunk::UnpackedHeader Header; diff --git a/compiler-rt/lib/scudo/standalone/flags.h b/compiler-rt/lib/scudo/standalone/flags.h --- a/compiler-rt/lib/scudo/standalone/flags.h +++ b/compiler-rt/lib/scudo/standalone/flags.h @@ -17,6 +17,14 @@ #define SCUDO_FLAG(Type, Name, DefaultValue, Description) Type Name; #include "flags.inc" #undef SCUDO_FLAG + +#ifdef GWP_ASAN_HOOKS +#define GWP_ASAN_OPTION(Type, Name, DefaultValue, Description) \ + Type GWP_ASAN_##Name; +#include "gwp_asan/options.inc" +#undef GWP_ASAN_OPTION +#endif // GWP_ASAN_HOOKS + void setDefaults(); }; diff --git a/compiler-rt/lib/scudo/standalone/flags.cpp b/compiler-rt/lib/scudo/standalone/flags.cpp --- a/compiler-rt/lib/scudo/standalone/flags.cpp +++ b/compiler-rt/lib/scudo/standalone/flags.cpp @@ -22,6 +22,13 @@ #define SCUDO_FLAG(Type, Name, DefaultValue, Description) Name = DefaultValue; #include "flags.inc" #undef SCUDO_FLAG + +#ifdef GWP_ASAN_HOOKS +#define GWP_ASAN_OPTION(Type, Name, DefaultValue, Description) \ + GWP_ASAN_##Name = DefaultValue; +#include "gwp_asan/options.inc" +#undef GWP_ASAN_OPTION +#endif // GWP_ASAN_HOOKS } void registerFlags(FlagParser *Parser, Flags *F) { @@ -30,6 +37,14 @@ reinterpret_cast(&F->Name)); #include "flags.inc" #undef SCUDO_FLAG + +#ifdef GWP_ASAN_HOOKS +#define GWP_ASAN_OPTION(Type, Name, DefaultValue, Description) \ + Parser->registerFlag("GWP_ASAN_"#Name, Description, FlagType::FT_##Type, \ + reinterpret_cast(&F->GWP_ASAN_##Name)); +#include "gwp_asan/options.inc" +#undef GWP_ASAN_OPTION +#endif // GWP_ASAN_HOOKS } static const char *getCompileDefinitionScudoDefaultOptions() { diff --git a/compiler-rt/lib/scudo/standalone/flags_parser.h b/compiler-rt/lib/scudo/standalone/flags_parser.h --- a/compiler-rt/lib/scudo/standalone/flags_parser.h +++ b/compiler-rt/lib/scudo/standalone/flags_parser.h @@ -29,7 +29,7 @@ void printFlagDescriptions(); private: - static const u32 MaxFlags = 12; + static const u32 MaxFlags = 16; struct Flag { const char *Name; const char *Desc; diff --git a/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt b/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt --- a/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt +++ b/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt @@ -20,6 +20,10 @@ list(APPEND SCUDO_UNITTEST_CFLAGS -fno-emulated-tls) endif() +if (COMPILER_RT_HAS_GWP_ASAN) + list(APPEND SCUDO_UNITTEST_CFLAGS -DGWP_ASAN_HOOKS) +endif() + set(SCUDO_TEST_ARCH ${SCUDO_STANDALONE_SUPPORTED_ARCH}) # gtests requires c++ @@ -38,6 +42,10 @@ macro(add_scudo_unittest testname) cmake_parse_arguments(TEST "" "" "SOURCES;ADDITIONAL_RTOBJECTS" ${ARGN}) + if (COMPILER_RT_HAS_GWP_ASAN) + list(APPEND TEST_ADDITIONAL_RTOBJECTS RTGwpAsan) + endif() + if(COMPILER_RT_HAS_SCUDO_STANDALONE) foreach(arch ${SCUDO_TEST_ARCH}) # Additional runtime objects get added along RTScudoStandalone diff --git a/compiler-rt/lib/scudo/standalone/tests/flags_test.cpp b/compiler-rt/lib/scudo/standalone/tests/flags_test.cpp --- a/compiler-rt/lib/scudo/standalone/tests/flags_test.cpp +++ b/compiler-rt/lib/scudo/standalone/tests/flags_test.cpp @@ -117,3 +117,20 @@ EXPECT_TRUE(Flags.delete_size_mismatch); EXPECT_EQ(2048, Flags.quarantine_max_chunk_size); } + +TEST(ScudoFlagsTest, GWPASanFlags) { +#ifndef GWP_ASAN_HOOKS + GTEST_SKIP() << "GWP-ASan wasn't built as part of Scudo Standalone."; +#endif // GWP_ASAN_HOOKS + + scudo::FlagParser Parser; + scudo::Flags Flags; + scudo::registerFlags(&Parser, &Flags); + Flags.setDefaults(); + Flags.GWP_ASAN_Enabled = false; + Parser.parseString("GWP_ASAN_Enabled=true:GWP_ASAN_SampleRate=1:" + "GWP_ASAN_InstallSignalHandlers=false"); + EXPECT_TRUE(Flags.GWP_ASAN_Enabled); + EXPECT_FALSE(Flags.GWP_ASAN_InstallSignalHandlers); + EXPECT_EQ(1, Flags.GWP_ASAN_SampleRate); +} diff --git a/compiler-rt/test/scudo/standalone/unit/lit.site.cfg.py.in b/compiler-rt/test/scudo/standalone/unit/lit.site.cfg.py.in --- a/compiler-rt/test/scudo/standalone/unit/lit.site.cfg.py.in +++ b/compiler-rt/test/scudo/standalone/unit/lit.site.cfg.py.in @@ -10,3 +10,7 @@ # For unit tests, we define it as build directory with unit tests. config.test_exec_root = "@COMPILER_RT_BINARY_DIR@/lib/scudo/standalone/tests" config.test_source_root = config.test_exec_root + +# Disable GWP-ASan for scudo internal tests. +if config.gwp_asan: + config.environment['SCUDO_OPTIONS'] = 'GWP_ASAN_Enabled=0' diff --git a/compiler-rt/unittests/lit.common.unit.configured.in b/compiler-rt/unittests/lit.common.unit.configured.in --- a/compiler-rt/unittests/lit.common.unit.configured.in +++ b/compiler-rt/unittests/lit.common.unit.configured.in @@ -11,6 +11,7 @@ config.host_arch = "@HOST_ARCH@" config.host_os = "@HOST_OS@" config.llvm_lib_dir = "@LLVM_LIBRARY_DIR@" +config.gwp_asan = @COMPILER_RT_HAS_GWP_ASAN_PYBOOL@ # LLVM tools dir and build mode can be passed in lit parameters, # so try to apply substitution.