Index: lib/scudo/standalone/CMakeLists.txt =================================================================== --- lib/scudo/standalone/CMakeLists.txt +++ lib/scudo/standalone/CMakeLists.txt @@ -37,6 +37,8 @@ checksum.cc crc32_hw.cc common.cc + flags.cc + flags_parser.cc fuchsia.cc linux.cc report.cc @@ -57,6 +59,7 @@ atomic_helpers.h bytemap.h checksum.h + interface.h internal_defs.h linux.h list.h Index: lib/scudo/standalone/flags.h =================================================================== --- /dev/null +++ lib/scudo/standalone/flags.h @@ -0,0 +1,30 @@ +//===-- flags.h -------------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef SCUDO_FLAGS_H_ +#define SCUDO_FLAGS_H_ + +#include "internal_defs.h" + +namespace scudo { + +struct Flags { +#define SCUDO_FLAG(Type, Name, DefaultValue, Description) Type Name; +#include "flags.inc" +#undef SCUDO_FLAG + void setDefaults(); +}; + +Flags *getFlags(); +void initFlags(); +class FlagParser; +void registerFlags(FlagParser *Parser, Flags *F); + +} // namespace scudo + +#endif // SCUDO_FLAGS_H_ Index: lib/scudo/standalone/flags.cc =================================================================== --- /dev/null +++ lib/scudo/standalone/flags.cc @@ -0,0 +1,55 @@ +//===-- flags.cc ------------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "flags.h" + +#include "common.h" +#include "flags_parser.h" +#include "interface.h" + +namespace scudo { +static Flags FlagsDoNotUse; // Use via getFlags(). + +void Flags::setDefaults() { +#define SCUDO_FLAG(Type, Name, DefaultValue, Description) Name = DefaultValue; +#include "flags.inc" +#undef SCUDO_FLAG +} + +void registerFlags(FlagParser *Parser, Flags *F) { +#define SCUDO_FLAG(Type, Name, DefaultValue, Description) \ + registerFlag(Parser, #Name, Description, &F->Name); +#include "flags.inc" +#undef SCUDO_FLAG +} + +static const char *getCompileDefinitionScudoDefaultOptions() { +#ifdef SCUDO_DEFAULT_OPTIONS + return STRINGIFY(SCUDO_DEFAULT_OPTIONS); +#else + return ""; +#endif +} + +static const char *getScudoDefaultOptions() { + return (&__scudo_default_options) ? __scudo_default_options() : ""; +} + +void initFlags() { + Flags *F = getFlags(); + F->setDefaults(); + FlagParser Parser; + registerFlags(&Parser, F); + Parser.parseString(getCompileDefinitionScudoDefaultOptions()); + Parser.parseString(getScudoDefaultOptions()); + Parser.parseString(getEnv("SCUDO_OPTIONS")); +} + +Flags *getFlags() { return &FlagsDoNotUse; } + +} // namespace scudo Index: lib/scudo/standalone/flags.inc =================================================================== --- /dev/null +++ lib/scudo/standalone/flags.inc @@ -0,0 +1,50 @@ +//===-- flags.inc -----------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef SCUDO_FLAG +#error "Define SCUDO_FLAG prior to including this file!" +#endif + +SCUDO_FLAG(int, quarantine_size_kb, 0, + "Size (in kilobytes) 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, thread_local_quarantine_size_kb, 0, + "Size (in kilobytes) 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(int, quarantine_max_chunk_size, 0, + "Size (in bytes) up to which chunks will be quarantined (if lower " + "than or equal to).") + +SCUDO_FLAG(bool, dealloc_type_mismatch, false, + "Terminate on a type mismatch in allocation-deallocation functions, " + "eg: malloc/delete, new/free, new/delete[], etc.") + +SCUDO_FLAG(bool, delete_size_mismatch, true, + "Terminate on a size mismatch between a sized-delete and the actual " + "size of a chunk (as provided to new/new[]).") + +SCUDO_FLAG(bool, zero_contents, false, "Zero chunk contents on allocation.") + +SCUDO_FLAG(int, rss_limit_mb, -1, + "Enforce an upper limit (in megabytes) to the process RSS. The " + "allocator will terminate or return NULL when allocations are " + "attempted past that limit (depending on may_return_null). Negative " + "values disable the feature.") + +SCUDO_FLAG(bool, may_return_null, true, + "Indicate whether the allocator should terminate instead of " + "returning NULL in otherwise non-fatal error scenarios, eg: OOM, " + "invalid allocation alignments, etc.") + +SCUDO_FLAG(int, release_to_os_interval_ms, 5000, + "Interval (in milliseconds) at which to attempt release of unused " + "memory to the OS. Negative values disable the feature.") Index: lib/scudo/standalone/flags_parser.h =================================================================== --- /dev/null +++ lib/scudo/standalone/flags_parser.h @@ -0,0 +1,141 @@ +//===-- flags_parser.h ------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef SCUDO_FLAGS_PARSER_H_ +#define SCUDO_FLAGS_PARSER_H_ + +#include "report.h" +#include "string_utils.h" + +#include +#include + +namespace scudo { + +class FlagHandlerBase { +public: + virtual bool parse(UNUSED const char *Value) { return false; } + virtual ~FlagHandlerBase() = default; + void operator delete(void *) {} + void operator delete(void *, size_t) {} +}; + +template class FlagHandler : public FlagHandlerBase { + T *FlagValue; + +public: + explicit FlagHandler(T *Value) : FlagValue(Value) {} + bool parse(const char *Value) final; + // Even though we are using a placement new, delete still needs to be defined. + void operator delete(void *) {} + void operator delete(void *, size_t) {} + ~FlagHandler() final = default; +}; + +INLINE bool parseBool(const char *Value, bool *b) { + if (strcmp(Value, "0") == 0 || strcmp(Value, "no") == 0 || + strcmp(Value, "false") == 0) { + *b = false; + return true; + } + if (strcmp(Value, "1") == 0 || strcmp(Value, "yes") == 0 || + strcmp(Value, "true") == 0) { + *b = true; + return true; + } + return false; +} + +template <> INLINE bool FlagHandler::parse(const char *Value) { + if (parseBool(Value, FlagValue)) + return true; + reportInvalidFlag("bool", Value); + return false; +} + +template <> INLINE bool FlagHandler::parse(const char *Value) { + *FlagValue = Value; + return true; +} + +template <> INLINE bool FlagHandler::parse(const char *Value) { + char *ValueEnd; + *FlagValue = static_cast(strtol(Value, &ValueEnd, 10)); + const bool Ok = *ValueEnd == 0; + if (!Ok) + reportInvalidFlag("int", Value); + return Ok; +} + +template <> INLINE bool FlagHandler::parse(const char *Value) { + char *ValueEnd; + *FlagValue = static_cast(strtoull(Value, &ValueEnd, 10)); + const bool Ok = *ValueEnd == 0; + if (!Ok) + reportInvalidFlag("uptr", Value); + return Ok; +} + +// Minimal map based allocator. Memory is never unmapped. +class FlagsAllocator { +public: + // Requires an external lock. + void *allocate(uptr Size); + +private: + uptr AllocatedEnd; + uptr AllocatedCurrent; +}; + +class FlagParser { + static const int MaxFlags = 32; + struct Flag { + const char *Name; + const char *Desc; + FlagHandlerBase *Handler; + } * Flags; + u32 NumberOfFlags; + + const char *Buffer; + uptr Pos; + +public: + FlagParser(); + void registerHandler(const char *Name, FlagHandlerBase *Handler, + const char *Desc); + void parseString(const char *S); + void printFlagDescriptions(); + + static FlagsAllocator Alloc; + +private: + void reportFatalError(const char *Error); + bool isSpace(char C); + void skipWhitespace(); + void parseFlags(); + void parseFlag(); + bool runHandler(const char *Name, const char *Value); + char *duplicateString(const char *S, uptr N); +}; + +template +static void registerFlag(FlagParser *Parser, const char *Name, const char *Desc, + T *Var) { + FlagHandler *Handler = new (FlagParser::Alloc) FlagHandler(Var); + Parser->registerHandler(Name, Handler, Desc); +} + +void reportUnrecognizedFlags(); + +} // namespace scudo + +INLINE void *operator new(size_t Size, scudo::FlagsAllocator &Alloc) { + return Alloc.allocate(Size); +} + +#endif // SCUDO_FLAGS_PARSER_H_ Index: lib/scudo/standalone/flags_parser.cc =================================================================== --- /dev/null +++ lib/scudo/standalone/flags_parser.cc @@ -0,0 +1,162 @@ +//===-- flags_parser.cc -----------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "flags_parser.h" + +#include "common.h" +#include "report.h" + +#include + +namespace scudo { + +class UnknownFlagsRegistry { + static const uptr MaxUnknownFlags = 16; + const char *UnknownFlagsNames[MaxUnknownFlags]; + uptr NumberOfUnknownFlags; + +public: + void add(const char *Name) { + CHECK_LT(NumberOfUnknownFlags, MaxUnknownFlags); + UnknownFlagsNames[NumberOfUnknownFlags++] = Name; + } + + void report() { + if (!NumberOfUnknownFlags) + return; + Printf("Scudo WARNING: found %d unrecognized flag(s):\n", + NumberOfUnknownFlags); + for (uptr I = 0; I < NumberOfUnknownFlags; ++I) + Printf(" %s\n", UnknownFlagsNames[I]); + NumberOfUnknownFlags = 0; + } +}; +static UnknownFlagsRegistry UnknownFlags; + +void reportUnrecognizedFlags() { UnknownFlags.report(); } + +FlagsAllocator FlagParser::Alloc; + +void *FlagsAllocator::allocate(uptr Size) { + Size = roundUpTo(Size, 8); + if (AllocatedEnd - AllocatedCurrent < Size) { + const uptr SizeToAllocate = Max(Size, getPageSizeCached()); + AllocatedCurrent = + reinterpret_cast(map(nullptr, SizeToAllocate, "scudo:flags")); + AllocatedEnd = AllocatedCurrent + SizeToAllocate; + } + DCHECK(AllocatedEnd - AllocatedCurrent >= Size); + void *P = reinterpret_cast(AllocatedCurrent); + AllocatedCurrent += Size; + return P; +} + +char *FlagParser::duplicateString(const char *S, uptr N) { + const uptr Length = strnlen(S, N); + char *NewS = reinterpret_cast(Alloc.allocate(Length + 1)); + memcpy(NewS, S, Length); + NewS[Length] = 0; + return NewS; +} + +void FlagParser::printFlagDescriptions() { + Printf("Available flags for Scudo:\n"); + for (u32 I = 0; I < NumberOfFlags; ++I) + Printf("\t%s\n\t\t- %s\n", Flags[I].Name, Flags[I].Desc); +} + +void FlagParser::reportFatalError(const char *Error) { reportError(Error); } + +bool FlagParser::isSpace(char C) { + return C == ' ' || C == ',' || C == ':' || C == '\n' || C == '\t' || + C == '\r'; +} + +void FlagParser::skipWhitespace() { + while (isSpace(Buffer[Pos])) + ++Pos; +} + +void FlagParser::parseFlag() { + const uptr NameStart = Pos; + while (Buffer[Pos] != 0 && Buffer[Pos] != '=' && !isSpace(Buffer[Pos])) + ++Pos; + if (Buffer[Pos] != '=') + reportFatalError("expected '='"); + char *Name = duplicateString(Buffer + NameStart, Pos - NameStart); + + const uptr ValueStart = ++Pos; + char *Value; + if (Buffer[Pos] == '\'' || Buffer[Pos] == '"') { + char quote = Buffer[Pos++]; + while (Buffer[Pos] != 0 && Buffer[Pos] != quote) + ++Pos; + if (Buffer[Pos] == 0) + reportFatalError("unterminated string"); + Value = duplicateString(Buffer + ValueStart + 1, Pos - ValueStart - 1); + ++Pos; // consume the closing quote + } else { + while (Buffer[Pos] != 0 && !isSpace(Buffer[Pos])) + ++Pos; + if (Buffer[Pos] != 0 && !isSpace(Buffer[Pos])) + reportFatalError("expected separator or eol"); + Value = duplicateString(Buffer + ValueStart, Pos - ValueStart); + } + + if (!runHandler(Name, Value)) + reportFatalError("Flag parsing failed."); +} + +void FlagParser::parseFlags() { + while (true) { + skipWhitespace(); + if (Buffer[Pos] == 0) + break; + parseFlag(); + } +} + +void FlagParser::parseString(const char *S) { + if (!S) + return; + // Backup current parser state to allow nested parseString() calls. + const char *OldBuffer = Buffer; + const uptr OldPos = Pos; + Buffer = S; + Pos = 0; + + parseFlags(); + + Buffer = OldBuffer; + Pos = OldPos; +} + +bool FlagParser::runHandler(const char *Name, const char *Value) { + for (u32 I = 0; I < NumberOfFlags; ++I) { + if (strcmp(Name, Flags[I].Name) == 0) + return Flags[I].Handler->parse(Value); + } + // Unrecognized flag. This is not a fatal error, we may print a warning later. + UnknownFlags.add(Name); + return true; +} + +void FlagParser::registerHandler(const char *Name, FlagHandlerBase *Handler, + const char *Desc) { + CHECK_LT(NumberOfFlags, MaxFlags); + Flags[NumberOfFlags].Name = Name; + Flags[NumberOfFlags].Desc = Desc; + Flags[NumberOfFlags].Handler = Handler; + ++NumberOfFlags; +} + +FlagParser::FlagParser() : NumberOfFlags(0), Buffer(nullptr), Pos(0) { + Flags = reinterpret_cast(Alloc.allocate(sizeof(Flag) * MaxFlags)); +} + +} // namespace scudo Index: lib/scudo/standalone/interface.h =================================================================== --- /dev/null +++ lib/scudo/standalone/interface.h @@ -0,0 +1,29 @@ +//===-- interface.h ---------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef SCUDO_INTERFACE_H_ +#define SCUDO_INTERFACE_H_ + +#include "internal_defs.h" + +extern "C" { + +WEAK INTERFACE const char *__scudo_default_options(); + +// Post-allocation & pre-deallocation hooks. +// They must be thread-safe and not use heap related functions. +WEAK INTERFACE void __scudo_allocate_hook(void *ptr, size_t size); +WEAK INTERFACE void __scudo_deallocate_hook(void *ptr); + +WEAK INTERFACE void __scudo_print_stats(void); + +typedef void (*iterate_callback)(uintptr_t base, size_t size, void *arg); + +} // extern "C" + +#endif // SCUDO_INTERFACE_H_ Index: lib/scudo/standalone/tests/CMakeLists.txt =================================================================== --- lib/scudo/standalone/tests/CMakeLists.txt +++ lib/scudo/standalone/tests/CMakeLists.txt @@ -52,6 +52,7 @@ atomic_test.cc bytemap_test.cc checksum_test.cc + flags_test.cc list_test.cc map_test.cc mutex_test.cc Index: lib/scudo/standalone/tests/flags_test.cc =================================================================== --- /dev/null +++ lib/scudo/standalone/tests/flags_test.cc @@ -0,0 +1,134 @@ +//===-- flags_test.cc -------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "flags.h" +#include "flags_parser.h" + +#include "gtest/gtest.h" + +#include + +static const char FlagName[] = "flag_name"; +static const char FlagDesc[] = "flag description"; + +template +static void testFlag(T StartValue, const char *Env, T FinalValue) { + scudo::FlagParser Parser; + T Flag = StartValue; + scudo::registerFlag(&Parser, FlagName, FlagDesc, &Flag); + Parser.parseString(Env); + EXPECT_EQ(FinalValue, Flag); + // Reporting unrecognized flags is needed to reset them. + scudo::reportUnrecognizedFlags(); +} + +template <> +void testFlag(const char *StartValue, const char *Env, const char *FinalValue) { + const char *Flag = StartValue; + scudo::FlagParser Parser; + scudo::registerFlag(&Parser, FlagName, FlagDesc, &Flag); + Parser.parseString(Env); + EXPECT_EQ(0, strcmp(FinalValue, Flag)); + // Reporting unrecognized flags is needed to reset them. + scudo::reportUnrecognizedFlags(); +} + +TEST(ScudoFlagsTest, BooleanFlags) { + testFlag(false, "flag_name=1", true); + testFlag(false, "flag_name=yes", true); + testFlag(false, "flag_name=true", true); + testFlag(true, "flag_name=0", false); + testFlag(true, "flag_name=no", false); + testFlag(true, "flag_name=false", false); +} + +TEST(ScudoFlagsDeathTest, BooleanFlags) { + EXPECT_DEATH(testFlag(false, "flag_name", true), "expected '='"); + EXPECT_DEATH(testFlag(false, "flag_name=", true), + "invalid value for bool option: ''"); + EXPECT_DEATH(testFlag(false, "flag_name=2", true), + "invalid value for bool option: '2'"); + EXPECT_DEATH(testFlag(false, "flag_name=-1", true), + "invalid value for bool option: '-1'"); + EXPECT_DEATH(testFlag(false, "flag_name=on", true), + "invalid value for bool option: 'on'"); +} + +TEST(ScudoFlagsTest, IntFlags) { + testFlag(-11, 0, -11); + testFlag(-11, "flag_name=0", 0); + testFlag(-11, "flag_name=42", 42); + testFlag(-11, "flag_name=-42", -42); + + // Unrecognized flags are ignored. + testFlag(-11, "--flag_name=42", -11); + testFlag(-11, "zzzzzzz=42", -11); +} + +TEST(ScudoFlagsDeathTest, IntFlags) { + EXPECT_DEATH(testFlag(-11, "flag_name", 0), "expected '='"); + EXPECT_DEATH(testFlag(-11, "flag_name=42U", 0), + "invalid value for int option"); +} + +TEST(ScudoFlagsTest, StrFlags) { + testFlag("zzz", 0, "zzz"); + testFlag("zzz", "flag_name=", ""); + testFlag("zzz", "flag_name=abc", "abc"); + testFlag("", "flag_name=abc", "abc"); + testFlag("", "flag_name='abc zxc'", "abc zxc"); +} + +static void testTwoFlags(const char *Env, bool ExpectedFlag1, + const char *ExpectedFlag2, const char *Name1 = "flag1", + const char *Name2 = "flag2") { + scudo::FlagParser Parser; + bool Flag1 = !ExpectedFlag1; + const char *Flag2 = ""; + scudo::registerFlag(&Parser, Name1, FlagDesc, &Flag1); + scudo::registerFlag(&Parser, Name2, FlagDesc, &Flag2); + Parser.parseString(Env); + EXPECT_EQ(ExpectedFlag1, Flag1); + EXPECT_EQ(0, strcmp(Flag2, ExpectedFlag2)); + // Reporting unrecognized flags is needed to reset them. + scudo::reportUnrecognizedFlags(); +} + +TEST(ScudoFlagsTest, MultipleFlags) { + testTwoFlags("flag1=1 flag2='zzz'", true, "zzz"); + testTwoFlags("flag2='qxx' flag1=0", false, "qxx"); + testTwoFlags("flag1=false:flag2='zzz'", false, "zzz"); + testTwoFlags("flag2=qxx:flag1=yes", true, "qxx"); + testTwoFlags("flag2=qxx\nflag1=yes", true, "qxx"); + testTwoFlags("flag2=qxx\r\nflag1=yes", true, "qxx"); + testTwoFlags("flag2=qxx\tflag1=yes", true, "qxx"); +} + +TEST(ScudoFlagsTest, CommonSuffixFlags) { + testTwoFlags("flag=1 other_flag='zzz'", true, "zzz", "flag", "other_flag"); + testTwoFlags("other_flag='zzz' flag=1", true, "zzz", "flag", "other_flag"); + testTwoFlags("other_flag=' flag=0 ' flag=1", true, " flag=0 ", "flag", + "other_flag"); + testTwoFlags("flag=1 other_flag=' flag=0 '", true, " flag=0 ", "flag", + "other_flag"); +} + +TEST(ScudoFlagsTest, AllocatorFlags) { + scudo::FlagParser Parser; + scudo::Flags Flags; + scudo::registerFlags(&Parser, &Flags); + Flags.setDefaults(); + Flags.dealloc_type_mismatch = false; + Flags.delete_size_mismatch = false; + Flags.quarantine_max_chunk_size = 1024; + Parser.parseString("dealloc_type_mismatch=true:delete_size_mismatch=true:" + "quarantine_max_chunk_size=2048"); + EXPECT_TRUE(Flags.dealloc_type_mismatch); + EXPECT_TRUE(Flags.delete_size_mismatch); + EXPECT_EQ(2048, Flags.quarantine_max_chunk_size); +}