diff options
author | Kostya Kortchinsky <kostyak@google.com> | 2019-03-20 14:31:23 +0000 |
---|---|---|
committer | Kostya Kortchinsky <kostyak@google.com> | 2019-03-20 14:31:23 +0000 |
commit | 61744d4573fc49b8feff294c53c8996e4650cd49 (patch) | |
tree | 5199970f19f2e639fb46cf37297b26970bacafe6 /lib/scudo/standalone | |
parent | ef821f937fff68b0fe9cd71c116ad2cb387b8e5b (diff) | |
download | compiler-rt-61744d4573fc49b8feff294c53c8996e4650cd49.tar.gz |
[scudo][standalone] Add error reports
Summary:
This change adds fatal error messages for various error conditions that
will be added later in the code.
This also addresses a `TODO` now that we have `reportCheckFailed` (which
lead me to notice a few variables that were not cased properly so I
changed that as well).
Reviewers: morehouse, hctim, vitalybuka
Reviewed By: morehouse, hctim, vitalybuka
Subscribers: mgorny, delcypher, jfb, jdoerfert, #sanitizers, llvm-commits
Tags: #llvm, #sanitizers
Differential Revision: https://reviews.llvm.org/D59551
git-svn-id: https://llvm.org/svn/llvm-project/compiler-rt/trunk@356556 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'lib/scudo/standalone')
-rw-r--r-- | lib/scudo/standalone/CMakeLists.txt | 2 | ||||
-rw-r--r-- | lib/scudo/standalone/internal_defs.h | 77 | ||||
-rw-r--r-- | lib/scudo/standalone/report.cc | 192 | ||||
-rw-r--r-- | lib/scudo/standalone/report.h | 57 | ||||
-rw-r--r-- | lib/scudo/standalone/tests/CMakeLists.txt | 1 | ||||
-rw-r--r-- | lib/scudo/standalone/tests/report_test.cc | 47 |
6 files changed, 339 insertions, 37 deletions
diff --git a/lib/scudo/standalone/CMakeLists.txt b/lib/scudo/standalone/CMakeLists.txt index f655c3077..87f4808eb 100644 --- a/lib/scudo/standalone/CMakeLists.txt +++ b/lib/scudo/standalone/CMakeLists.txt @@ -39,6 +39,7 @@ set(SCUDO_SOURCES common.cc fuchsia.cc linux.cc + report.cc string_utils.cc) # Enable the SSE 4.2 instruction set for crc32_hw.cc, if available. @@ -61,6 +62,7 @@ set(SCUDO_HEADERS list.h mutex.h platform.h + report.h stats.h string_utils.h vector.h) diff --git a/lib/scudo/standalone/internal_defs.h b/lib/scudo/standalone/internal_defs.h index 41ad59173..cca9c42af 100644 --- a/lib/scudo/standalone/internal_defs.h +++ b/lib/scudo/standalone/internal_defs.h @@ -32,21 +32,21 @@ #define WEAK __attribute__((weak)) #define INLINE inline #define ALWAYS_INLINE inline __attribute__((always_inline)) -#define ALIAS(x) __attribute__((alias(x))) +#define ALIAS(X) __attribute__((alias(X))) // Please only use the ALIGNED macro before the type. Using ALIGNED after the // variable declaration is not portable. -#define ALIGNED(x) __attribute__((aligned(x))) -#define FORMAT(f, a) __attribute__((format(printf, f, a))) +#define ALIGNED(X) __attribute__((aligned(X))) +#define FORMAT(F, A) __attribute__((format(printf, F, A))) #define NOINLINE __attribute__((noinline)) #define NORETURN __attribute__((noreturn)) #define THREADLOCAL __thread -#define LIKELY(x) __builtin_expect(!!(x), 1) -#define UNLIKELY(x) __builtin_expect(!!(x), 0) +#define LIKELY(X) __builtin_expect(!!(X), 1) +#define UNLIKELY(X) __builtin_expect(!!(X), 0) #if defined(__i386__) || defined(__x86_64__) -// __builtin_prefetch(x) generates prefetchnt0 on x86 -#define PREFETCH(x) __asm__("prefetchnta (%0)" : : "r"(x)) +// __builtin_prefetch(X) generates prefetchnt0 on x86 +#define PREFETCH(X) __asm__("prefetchnta (%0)" : : "r"(X)) #else -#define PREFETCH(x) __builtin_prefetch(x) +#define PREFETCH(X) __builtin_prefetch(X) #endif #define UNUSED __attribute__((unused)) #define USED __attribute__((used)) @@ -79,47 +79,50 @@ void NORETURN die(); #define RAW_CHECK(Expr) RAW_CHECK_MSG(Expr, #Expr) -// TODO(kostyak): use reportCheckFailed when checked-in. -#define CHECK_IMPL(c1, op, c2) \ +void NORETURN reportCheckFailed(const char *File, int Line, + const char *Condition, u64 Value1, u64 Value2); + +#define CHECK_IMPL(C1, Op, C2) \ do { \ - u64 v1 = (u64)(c1); \ - u64 v2 = (u64)(c2); \ - if (UNLIKELY(!(v1 op v2))) { \ - outputRaw("CHECK failed: (" #c1 ") " #op " (" #c2 ")\n"); \ + u64 V1 = (u64)(C1); \ + u64 V2 = (u64)(C2); \ + if (UNLIKELY(!(V1 Op V2))) { \ + reportCheckFailed(__FILE__, __LINE__, "(" #C1 ") " #Op " (" #C2 ")", V1, \ + V2); \ die(); \ } \ } while (false) -#define CHECK(a) CHECK_IMPL((a), !=, 0) -#define CHECK_EQ(a, b) CHECK_IMPL((a), ==, (b)) -#define CHECK_NE(a, b) CHECK_IMPL((a), !=, (b)) -#define CHECK_LT(a, b) CHECK_IMPL((a), <, (b)) -#define CHECK_LE(a, b) CHECK_IMPL((a), <=, (b)) -#define CHECK_GT(a, b) CHECK_IMPL((a), >, (b)) -#define CHECK_GE(a, b) CHECK_IMPL((a), >=, (b)) +#define CHECK(A) CHECK_IMPL((A), !=, 0) +#define CHECK_EQ(A, B) CHECK_IMPL((A), ==, (B)) +#define CHECK_NE(A, B) CHECK_IMPL((A), !=, (B)) +#define CHECK_LT(A, B) CHECK_IMPL((A), <, (B)) +#define CHECK_LE(A, B) CHECK_IMPL((A), <=, (B)) +#define CHECK_GT(A, B) CHECK_IMPL((A), >, (B)) +#define CHECK_GE(A, B) CHECK_IMPL((A), >=, (B)) #if SCUDO_DEBUG -#define DCHECK(a) CHECK(a) -#define DCHECK_EQ(a, b) CHECK_EQ(a, b) -#define DCHECK_NE(a, b) CHECK_NE(a, b) -#define DCHECK_LT(a, b) CHECK_LT(a, b) -#define DCHECK_LE(a, b) CHECK_LE(a, b) -#define DCHECK_GT(a, b) CHECK_GT(a, b) -#define DCHECK_GE(a, b) CHECK_GE(a, b) +#define DCHECK(A) CHECK(A) +#define DCHECK_EQ(A, B) CHECK_EQ(A, B) +#define DCHECK_NE(A, B) CHECK_NE(A, B) +#define DCHECK_LT(A, B) CHECK_LT(A, B) +#define DCHECK_LE(A, B) CHECK_LE(A, B) +#define DCHECK_GT(A, B) CHECK_GT(A, B) +#define DCHECK_GE(A, B) CHECK_GE(A, B) #else -#define DCHECK(a) -#define DCHECK_EQ(a, b) -#define DCHECK_NE(a, b) -#define DCHECK_LT(a, b) -#define DCHECK_LE(a, b) -#define DCHECK_GT(a, b) -#define DCHECK_GE(a, b) +#define DCHECK(A) +#define DCHECK_EQ(A, B) +#define DCHECK_NE(A, B) +#define DCHECK_LT(A, B) +#define DCHECK_LE(A, B) +#define DCHECK_GT(A, B) +#define DCHECK_GE(A, B) #endif // The superfluous die() call effectively makes this macro NORETURN. -#define UNREACHABLE(msg) \ +#define UNREACHABLE(Msg) \ do { \ - CHECK(0 && msg); \ + CHECK(0 && Msg); \ die(); \ } while (0) diff --git a/lib/scudo/standalone/report.cc b/lib/scudo/standalone/report.cc new file mode 100644 index 000000000..2e453a10a --- /dev/null +++ b/lib/scudo/standalone/report.cc @@ -0,0 +1,192 @@ +//===-- report.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 "report.h" + +#include "atomic_helpers.h" +#include "string_utils.h" + +#include <stdarg.h> + +namespace scudo { + +class ScopedErrorReport { +public: + ScopedErrorReport() : Message(512) { Message.append("Scudo ERROR: "); } + void append(const char *Format, ...) { + va_list Args; + va_start(Args, Format); + Message.append(Format, Args); + va_end(Args); + } + NORETURN ~ScopedErrorReport() { + outputRaw(Message.data()); + setAbortMessage(Message.data()); + die(); + } + +private: + ScopedString Message; +}; + +INLINE void NORETURN trap() { __builtin_trap(); } + +// This could potentially be called recursively if a CHECK fails in the reports. +void NORETURN reportCheckFailed(const char *File, int Line, + const char *Condition, u64 Value1, u64 Value2) { + static atomic_u32 NumberOfCalls; + if (atomic_fetch_add(&NumberOfCalls, 1, memory_order_relaxed) > 2) { + // TODO(kostyak): maybe sleep here? + trap(); + } + ScopedErrorReport Report; + Report.append("CHECK failed @ %s:%d %s (%llu, %llu)\n", File, Line, Condition, + Value1, Value2); +} + +// Generic string fatal error message. +void NORETURN reportError(const char *Message) { + ScopedErrorReport Report; + Report.append("%s", Message); +} + +void NORETURN reportInvalidFlag(const char *FlagType, const char *Value) { + ScopedErrorReport Report; + Report.append("invalid value for %s option: '%s'\n", FlagType, Value); +} + +// The checksum of a chunk header is invalid. This could be caused by an +// {over,under}write of the header, a pointer that is not an actual chunk. +void NORETURN reportHeaderCorruption(void *Ptr) { + ScopedErrorReport Report; + Report.append("corrupted chunk header at address %p\n", Ptr); +} + +// Two threads have attempted to modify a chunk header at the same time. This is +// symptomatic of a race-condition in the application code, or general lack of +// proper locking. +void NORETURN reportHeaderRace(void *Ptr) { + ScopedErrorReport Report; + Report.append("race on chunk header at address %p\n", Ptr); +} + +// The allocator was compiled with parameters that conflict with field size +// requirements. +void NORETURN reportSanityCheckError(const char *Field) { + ScopedErrorReport Report; + Report.append("maximum possible %s doesn't fit in header\n", Field); +} + +// We enforce a maximum alignment, to keep fields smaller and generally prevent +// integer overflows, or unexpected corner cases. +void NORETURN reportAlignmentTooBig(uptr Alignment, uptr MaxAlignment) { + ScopedErrorReport Report; + Report.append("invalid allocation alignment: %zu exceeds maximum supported " + "alignment of %zu\n", + Alignment, MaxAlignment); +} + +// See above, we also enforce a maximum size. +void NORETURN reportAllocationSizeTooBig(uptr UserSize, uptr TotalSize, + uptr MaxSize) { + ScopedErrorReport Report; + Report.append("requested allocation size %zu (%zu after adjustments) exceeds " + "maximum supported size of %zu\n", + UserSize, TotalSize, MaxSize); +} + +void NORETURN reportOutOfMemory(uptr RequestedSize) { + ScopedErrorReport Report; + Report.append("out of memory trying to allocate %zu bytes\n", RequestedSize); +} + +static const char *stringifyAction(AllocatorAction Action) { + switch (Action) { + case AllocatorAction::Recycling: + return "recycling"; + case AllocatorAction::Deallocating: + return "deallocating"; + case AllocatorAction::Reallocating: + return "reallocating"; + case AllocatorAction::Sizing: + return "sizing"; + } + return "<invalid action>"; +} + +// The chunk is not in a state congruent with the operation we want to perform. +// This is usually the case with a double-free, a realloc of a freed pointer. +void NORETURN reportInvalidChunkState(AllocatorAction Action, void *Ptr) { + ScopedErrorReport Report; + Report.append("invalid chunk state when %s address %p\n", + stringifyAction(Action), Ptr); +} + +void NORETURN reportMisalignedPointer(AllocatorAction Action, void *Ptr) { + ScopedErrorReport Report; + Report.append("misaligned pointer when %s address %p\n", + stringifyAction(Action), Ptr); +} + +// The deallocation function used is at odds with the one used to allocate the +// chunk (eg: new[]/delete or malloc/delete, and so on). +void NORETURN reportDeallocTypeMismatch(AllocatorAction Action, void *Ptr, + u8 TypeA, u8 TypeB) { + ScopedErrorReport Report; + Report.append("allocation type mismatch when %s address %p (%d vs %d)\n", + stringifyAction(Action), Ptr, TypeA, TypeB); +} + +// The size specified to the delete operator does not match the one that was +// passed to new when allocating the chunk. +void NORETURN reportDeleteSizeMismatch(void *Ptr, uptr Size, + uptr ExpectedSize) { + ScopedErrorReport Report; + Report.append( + "invalid sized delete when deallocating address %p (%zu vs %zu)\n", Ptr, + Size, ExpectedSize); +} + +void NORETURN reportAlignmentNotPowerOfTwo(uptr Alignment) { + ScopedErrorReport Report; + Report.append( + "invalid allocation alignment: %zu, alignment must be a power of two\n", + Alignment); +} + +void NORETURN reportCallocOverflow(uptr Count, uptr Size) { + ScopedErrorReport Report; + Report.append("calloc parameters overflow: count * size (%zu * %zu) cannot " + "be represented with type size_t\n", + Count, Size); +} + +void NORETURN reportInvalidPosixMemalignAlignment(uptr Alignment) { + ScopedErrorReport Report; + Report.append( + "invalid alignment requested in posix_memalign: %zu, alignment must be a " + "power of two and a multiple of sizeof(void *) == %zu\n", + Alignment, sizeof(void *)); +} + +void NORETURN reportPvallocOverflow(uptr Size) { + ScopedErrorReport Report; + Report.append("pvalloc parameters overflow: size %zu rounded up to system " + "page size %zu cannot be represented in type size_t\n", + Size, getPageSizeCached()); +} + +void NORETURN reportInvalidAlignedAllocAlignment(uptr Alignment, uptr Size) { + ScopedErrorReport Report; + Report.append("invalid alignment requested in aligned_alloc: %zu, alignment " + "must be a power of two and the requested size %zu must be a " + "multiple of alignment\n", + Alignment, Size); +} + +} // namespace scudo diff --git a/lib/scudo/standalone/report.h b/lib/scudo/standalone/report.h new file mode 100644 index 000000000..14e4e799b --- /dev/null +++ b/lib/scudo/standalone/report.h @@ -0,0 +1,57 @@ +//===-- report.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_REPORT_H_ +#define SCUDO_REPORT_H_ + +#include "internal_defs.h" + +namespace scudo { + +// Reports are *fatal* unless stated otherwise. + +// Generic error. +void NORETURN reportError(const char *Message); + +// Flags related errors. +void NORETURN reportInvalidFlag(const char *FlagType, const char *Value); + +// Chunk header related errors. +void NORETURN reportHeaderCorruption(void *Ptr); +void NORETURN reportHeaderRace(void *Ptr); + +// Sanity checks related error. +void NORETURN reportSanityCheckError(const char *Field); + +// Combined allocator errors. +void NORETURN reportAlignmentTooBig(uptr Alignment, uptr MaxAlignment); +void NORETURN reportAllocationSizeTooBig(uptr UserSize, uptr TotalSize, + uptr MaxSize); +void NORETURN reportOutOfMemory(uptr RequestedSize); +enum class AllocatorAction : u8 { + Recycling, + Deallocating, + Reallocating, + Sizing, +}; +void NORETURN reportInvalidChunkState(AllocatorAction Action, void *Ptr); +void NORETURN reportMisalignedPointer(AllocatorAction Action, void *Ptr); +void NORETURN reportDeallocTypeMismatch(AllocatorAction Action, void *Ptr, + u8 TypeA, u8 TypeB); +void NORETURN reportDeleteSizeMismatch(void *Ptr, uptr Size, uptr ExpectedSize); + +// C wrappers errors. +void NORETURN reportAlignmentNotPowerOfTwo(uptr Alignment); +void NORETURN reportInvalidPosixMemalignAlignment(uptr Alignment); +void NORETURN reportCallocOverflow(uptr Count, uptr Size); +void NORETURN reportPvallocOverflow(uptr Size); +void NORETURN reportInvalidAlignedAllocAlignment(uptr Size, uptr Alignment); + +} // namespace scudo + +#endif // SCUDO_REPORT_H_ diff --git a/lib/scudo/standalone/tests/CMakeLists.txt b/lib/scudo/standalone/tests/CMakeLists.txt index a7406c54e..a30b9a174 100644 --- a/lib/scudo/standalone/tests/CMakeLists.txt +++ b/lib/scudo/standalone/tests/CMakeLists.txt @@ -55,6 +55,7 @@ set(SCUDO_UNIT_TEST_SOURCES list_test.cc map_test.cc mutex_test.cc + report_test.cc stats_test.cc strings_test.cc vector_test.cc diff --git a/lib/scudo/standalone/tests/report_test.cc b/lib/scudo/standalone/tests/report_test.cc new file mode 100644 index 000000000..ce7eda59c --- /dev/null +++ b/lib/scudo/standalone/tests/report_test.cc @@ -0,0 +1,47 @@ +//===-- report_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 "scudo/standalone/report.h" +#include "gtest/gtest.h" + +TEST(ScudoReportTest, Generic) { + void *P = reinterpret_cast<void *>(0x42424242U); + EXPECT_DEATH(scudo::reportError("TEST123"), "Scudo ERROR.*TEST123"); + EXPECT_DEATH(scudo::reportInvalidFlag("ABC", "DEF"), "Scudo ERROR.*ABC.*DEF"); + EXPECT_DEATH(scudo::reportHeaderCorruption(P), "Scudo ERROR.*42424242"); + EXPECT_DEATH(scudo::reportHeaderRace(P), "Scudo ERROR.*42424242"); + EXPECT_DEATH(scudo::reportSanityCheckError("XYZ"), "Scudo ERROR.*XYZ"); + EXPECT_DEATH(scudo::reportAlignmentTooBig(123, 456), "Scudo ERROR.*123.*456"); + EXPECT_DEATH(scudo::reportAllocationSizeTooBig(123, 456, 789), + "Scudo ERROR.*123.*456.*789"); + EXPECT_DEATH(scudo::reportOutOfMemory(4242), "Scudo ERROR.*4242"); + EXPECT_DEATH( + scudo::reportInvalidChunkState(scudo::AllocatorAction::Recycling, P), + "Scudo ERROR.*recycling.*42424242"); + EXPECT_DEATH( + scudo::reportInvalidChunkState(scudo::AllocatorAction::Sizing, P), + "Scudo ERROR.*sizing.*42424242"); + EXPECT_DEATH( + scudo::reportMisalignedPointer(scudo::AllocatorAction::Deallocating, P), + "Scudo ERROR.*deallocating.*42424242"); + EXPECT_DEATH(scudo::reportDeallocTypeMismatch( + scudo::AllocatorAction::Reallocating, P, 0, 1), + "Scudo ERROR.*reallocating.*42424242"); + EXPECT_DEATH(scudo::reportDeleteSizeMismatch(P, 123, 456), + "Scudo ERROR.*42424242.*123.*456"); +} + +TEST(ScudoReportTest, CSpecific) { + EXPECT_DEATH(scudo::reportAlignmentNotPowerOfTwo(123), "Scudo ERROR.*123"); + EXPECT_DEATH(scudo::reportCallocOverflow(123, 456), "Scudo ERROR.*123.*456"); + EXPECT_DEATH(scudo::reportInvalidPosixMemalignAlignment(789), + "Scudo ERROR.*789"); + EXPECT_DEATH(scudo::reportPvallocOverflow(123), "Scudo ERROR.*123"); + EXPECT_DEATH(scudo::reportInvalidAlignedAllocAlignment(123, 456), + "Scudo ERROR.*123.*456"); +} |