[HWASan] Report proper error on allocator failures instead of CHECK(0)-ing

Summary:
Currently many allocator specific errors (OOM, for example) are reported as
a text message and CHECK(0) termination, not stack, no details, not too
helpful nor informative. To improve the situation, detailed and
structured errors were defined and reported under the appropriate conditions.

Reviewers: eugenis

Subscribers: kubamracek, delcypher, llvm-commits, #sanitizers

Differential Revision: https://reviews.llvm.org/D47798

llvm-svn: 334248
This commit is contained in:
Alex Shlyapnikov 2018-06-07 23:33:33 +00:00
parent db8d205fbf
commit af12729185
14 changed files with 315 additions and 35 deletions

View File

@ -1,4 +1,4 @@
//===-- hwasan.cc -----------------------------------------------------------===//
//===-- hwasan.cc ---------------------------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
@ -14,8 +14,9 @@
#include "hwasan.h"
#include "hwasan_mapping.h"
#include "hwasan_thread.h"
#include "hwasan_poisoning.h"
#include "hwasan_report.h"
#include "hwasan_thread.h"
#include "sanitizer_common/sanitizer_atomic.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_flags.h"

View File

@ -1,4 +1,4 @@
//===-- hwasan.h --------------------------------------------------*- C++ -*-===//
//===-- hwasan.h ------------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
@ -97,15 +97,6 @@ void PrintWarning(uptr pc, uptr bp);
void GetStackTrace(BufferedStackTrace *stack, uptr max_s, uptr pc, uptr bp,
void *context, bool request_fast_unwind);
void ReportInvalidAccess(StackTrace *stack, u32 origin);
void ReportTagMismatch(StackTrace *stack, uptr addr, uptr access_size,
bool is_store);
void ReportStats();
void ReportAtExitStatistics();
void DescribeMemoryRange(const void *x, uptr size);
void ReportInvalidAccessInsideAddressRange(const char *what, const void *start, uptr size,
uptr offset);
// Returns a "chained" origin id, pointing to the given stack trace followed by
// the previous origin id.
u32 ChainOrigin(u32 id, StackTrace *stack);

View File

@ -1,4 +1,4 @@
//===-- hwasan_allocator.cc --------------------------- ---------------------===//
//===-- hwasan_allocator.cc ------------------------- ---------------------===//
//
// The LLVM Compiler Infrastructure
//
@ -15,6 +15,7 @@
#include "sanitizer_common/sanitizer_allocator.h"
#include "sanitizer_common/sanitizer_allocator_checks.h"
#include "sanitizer_common/sanitizer_allocator_interface.h"
#include "sanitizer_common/sanitizer_allocator_report.h"
#include "sanitizer_common/sanitizer_atomic.h"
#include "sanitizer_common/sanitizer_errno.h"
#include "sanitizer_common/sanitizer_stackdepot.h"
@ -127,9 +128,12 @@ static void *HwasanAllocate(StackTrace *stack, uptr size, uptr alignment,
size = RoundUpTo(size, kShadowAlignment);
if (size > kMaxAllowedMallocSize) {
Report("WARNING: HWAddressSanitizer failed to allocate %p bytes\n",
(void *)size);
return ReturnNullOrDieOnFailure::OnBadRequest();
if (AllocatorMayReturnNull()) {
Report("WARNING: HWAddressSanitizer failed to allocate 0x%zx bytes\n",
size);
return nullptr;
}
ReportAllocationSizeTooBig(size, kMaxAllowedMallocSize, stack);
}
HwasanThread *t = GetCurrentThread();
void *allocated;
@ -141,8 +145,12 @@ static void *HwasanAllocate(StackTrace *stack, uptr size, uptr alignment,
AllocatorCache *cache = &fallback_allocator_cache;
allocated = allocator.Allocate(cache, size, alignment);
}
if (UNLIKELY(!allocated))
return ReturnNullOrDieOnFailure::OnOOM();
if (UNLIKELY(!allocated)) {
SetAllocatorOutOfMemory();
if (AllocatorMayReturnNull())
return nullptr;
ReportOutOfMemory(size, stack);
}
Metadata *meta =
reinterpret_cast<Metadata *>(allocator.GetMetaData(allocated));
meta->state = CHUNK_ALLOCATED;
@ -224,6 +232,15 @@ void *HwasanReallocate(StackTrace *stack, void *user_old_p, uptr new_size,
return new_p;
}
void *HwasanCalloc(StackTrace *stack, uptr nmemb, uptr size) {
if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) {
if (AllocatorMayReturnNull())
return nullptr;
ReportCallocOverflow(nmemb, size, stack);
}
return HwasanAllocate(stack, nmemb * size, sizeof(u64), true);
}
HwasanChunkView FindHeapChunkByAddress(uptr address) {
void *block = allocator.GetBlockBegin(reinterpret_cast<void*>(address));
if (!block)
@ -247,9 +264,7 @@ void *hwasan_malloc(uptr size, StackTrace *stack) {
}
void *hwasan_calloc(uptr nmemb, uptr size, StackTrace *stack) {
if (UNLIKELY(CheckForCallocOverflow(size, nmemb)))
return SetErrnoOnNull(ReturnNullOrDieOnFailure::OnBadRequest());
return SetErrnoOnNull(HwasanAllocate(stack, nmemb * size, sizeof(u64), true));
return SetErrnoOnNull(HwasanCalloc(stack, nmemb, size));
}
void *hwasan_realloc(void *ptr, uptr size, StackTrace *stack) {
@ -263,14 +278,17 @@ void *hwasan_realloc(void *ptr, uptr size, StackTrace *stack) {
}
void *hwasan_valloc(uptr size, StackTrace *stack) {
return SetErrnoOnNull(HwasanAllocate(stack, size, GetPageSizeCached(), false));
return SetErrnoOnNull(
HwasanAllocate(stack, size, GetPageSizeCached(), false));
}
void *hwasan_pvalloc(uptr size, StackTrace *stack) {
uptr PageSize = GetPageSizeCached();
if (UNLIKELY(CheckForPvallocOverflow(size, PageSize))) {
errno = errno_ENOMEM;
return ReturnNullOrDieOnFailure::OnBadRequest();
if (AllocatorMayReturnNull())
return nullptr;
ReportPvallocOverflow(size, stack);
}
// pvalloc(0) should allocate one page.
size = size ? RoundUpTo(size, PageSize) : PageSize;
@ -280,7 +298,9 @@ void *hwasan_pvalloc(uptr size, StackTrace *stack) {
void *hwasan_aligned_alloc(uptr alignment, uptr size, StackTrace *stack) {
if (UNLIKELY(!CheckAlignedAllocAlignmentAndSize(alignment, size))) {
errno = errno_EINVAL;
return ReturnNullOrDieOnFailure::OnBadRequest();
if (AllocatorMayReturnNull())
return nullptr;
ReportInvalidAlignedAllocAlignment(size, alignment, stack);
}
return SetErrnoOnNull(HwasanAllocate(stack, size, alignment, false));
}
@ -288,7 +308,9 @@ void *hwasan_aligned_alloc(uptr alignment, uptr size, StackTrace *stack) {
void *hwasan_memalign(uptr alignment, uptr size, StackTrace *stack) {
if (UNLIKELY(!IsPowerOfTwo(alignment))) {
errno = errno_EINVAL;
return ReturnNullOrDieOnFailure::OnBadRequest();
if (AllocatorMayReturnNull())
return nullptr;
ReportInvalidAllocationAlignment(alignment, stack);
}
return SetErrnoOnNull(HwasanAllocate(stack, size, alignment, false));
}
@ -296,18 +318,20 @@ void *hwasan_memalign(uptr alignment, uptr size, StackTrace *stack) {
int hwasan_posix_memalign(void **memptr, uptr alignment, uptr size,
StackTrace *stack) {
if (UNLIKELY(!CheckPosixMemalignAlignment(alignment))) {
ReturnNullOrDieOnFailure::OnBadRequest();
return errno_EINVAL;
if (AllocatorMayReturnNull())
return errno_EINVAL;
ReportInvalidPosixMemalignAlignment(alignment, stack);
}
void *ptr = HwasanAllocate(stack, size, alignment, false);
if (UNLIKELY(!ptr))
// OOM error is already taken care of by HwasanAllocate.
return errno_ENOMEM;
CHECK(IsAligned((uptr)ptr, alignment));
*memptr = ptr;
return 0;
}
} // namespace __hwasan
} // namespace __hwasan
using namespace __hwasan;

View File

@ -20,6 +20,7 @@
#include "hwasan_mapping.h"
#include "hwasan_thread.h"
#include "hwasan_poisoning.h"
#include "hwasan_report.h"
#include "sanitizer_common/sanitizer_platform_limits_posix.h"
#include "sanitizer_common/sanitizer_allocator.h"
#include "sanitizer_common/sanitizer_allocator_interface.h"

View File

@ -20,6 +20,7 @@
#include "hwasan_dynamic_shadow.h"
#include "hwasan_interface_internal.h"
#include "hwasan_mapping.h"
#include "hwasan_report.h"
#include "hwasan_thread.h"
#include <elf.h>

View File

@ -1,4 +1,4 @@
//===-- hwasan_new_delete.cc ------------------------------------------------===//
//===-- hwasan_new_delete.cc ----------------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
@ -15,6 +15,7 @@
#include "hwasan.h"
#include "interception/interception.h"
#include "sanitizer_common/sanitizer_allocator.h"
#include "sanitizer_common/sanitizer_allocator_report.h"
#if HWASAN_REPLACE_OPERATORS_NEW_AND_DELETE
@ -32,7 +33,7 @@ namespace std {
#define OPERATOR_NEW_BODY(nothrow) \
GET_MALLOC_STACK_TRACE; \
void *res = hwasan_malloc(size, &stack);\
if (!nothrow && UNLIKELY(!res)) DieOnFailure::OnOOM();\
if (!nothrow && UNLIKELY(!res)) ReportOutOfMemory(size, &stack);\
return res
INTERCEPTOR_ATTRIBUTE

View File

@ -1,4 +1,4 @@
//===-- hwasan_report.cc ----------------------------------------------------===//
//===-- hwasan_report.cc --------------------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
@ -37,9 +37,9 @@ static StackTrace GetStackTraceFromId(u32 id) {
class Decorator: public __sanitizer::SanitizerCommonDecorator {
public:
Decorator() : SanitizerCommonDecorator() { }
const char *Allocation() { return Magenta(); }
const char *Origin() { return Magenta(); }
const char *Name() { return Green(); }
const char *Allocation() const { return Magenta(); }
const char *Origin() const { return Magenta(); }
const char *Name() const { return Green(); }
};
struct HeapAddressDescription {
@ -130,5 +130,4 @@ void ReportTagMismatch(StackTrace *stack, uptr addr, uptr access_size,
ReportErrorSummary("tag-mismatch", stack);
}
} // namespace __hwasan

View File

@ -0,0 +1,36 @@
//===-- hwasan_report.h -----------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file is a part of HWAddressSanitizer. HWASan-private header for error
/// reporting functions.
///
//===----------------------------------------------------------------------===//
#ifndef HWASAN_REPORT_H
#define HWASAN_REPORT_H
#include "sanitizer_common/sanitizer_internal_defs.h"
#include "sanitizer_common/sanitizer_stacktrace.h"
namespace __hwasan {
void ReportInvalidAccess(StackTrace *stack, u32 origin);
void ReportStats();
void ReportInvalidAccessInsideAddressRange(const char *what, const void *start,
uptr size, uptr offset);
void ReportTagMismatch(StackTrace *stack, uptr addr, uptr access_size,
bool is_store);
void ReportAtExitStatistics();
} // namespace __hwasan
#endif // HWASAN_REPORT_H

View File

@ -0,0 +1,25 @@
// RUN: %clangxx_hwasan -O0 %s -o %t
// RUN: %env_hwasan_opts=allocator_may_return_null=0 not %run %t 2>&1 | FileCheck %s
// RUN: %env_hwasan_opts=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-NULL
// UNSUPPORTED: android
// REQUIRES: stable-runtime
#include <stdio.h>
#include <stdlib.h>
extern void *aligned_alloc(size_t alignment, size_t size);
int main() {
void *p = aligned_alloc(17, 100);
// CHECK: ERROR: HWAddressSanitizer: invalid alignment requested in aligned_alloc: 17
// CHECK: {{#0 0x.* in .*}}{{aligned_alloc|memalign}}
// CHECK: {{#1 0x.* in main .*aligned_alloc-alignment.cc:}}[[@LINE-3]]
// CHECK: SUMMARY: HWAddressSanitizer: invalid-aligned-alloc-alignment
printf("pointer after failed aligned_alloc: %zd\n", (size_t)p);
// CHECK-NULL: pointer after failed aligned_alloc: 0
return 0;
}

View File

@ -0,0 +1,9 @@
def getRoot(config):
if not config.parent:
return config
return getRoot(config.parent)
root = getRoot(config)
if root.host_os not in ['Linux']:
config.unsupported = True

View File

@ -0,0 +1,46 @@
// RUN: %clangxx_hwasan -O0 %s -o %t
// RUN: %env_hwasan_opts=allocator_may_return_null=0 not %run %t m1 2>&1 | FileCheck %s
// RUN: %env_hwasan_opts=allocator_may_return_null=1 %run %t m1 2>&1 | FileCheck %s --check-prefix=CHECK-NULL
// RUN: %env_hwasan_opts=allocator_may_return_null=0 not %run %t psm1 2>&1 | FileCheck %s
// RUN: %env_hwasan_opts=allocator_may_return_null=1 %run %t psm1 2>&1 | FileCheck %s --check-prefix=CHECK-NULL
// UNSUPPORTED: android
// REQUIRES: stable-runtime
// Checks that pvalloc overflows are caught. If the allocator is allowed to
// return null, the errno should be set to ENOMEM.
#include <assert.h>
#include <errno.h>
#include <malloc.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
assert(argc == 2);
const char *action = argv[1];
const size_t page_size = sysconf(_SC_PAGESIZE);
void *p = nullptr;
if (!strcmp(action, "m1")) {
p = pvalloc((uintptr_t)-1);
} else if (!strcmp(action, "psm1")) {
p = pvalloc((uintptr_t)-(page_size - 1));
} else {
assert(0);
}
fprintf(stderr, "errno: %d\n", errno);
return p != nullptr;
}
// CHECK: {{ERROR: HWAddressSanitizer: pvalloc parameters overflow: size .* rounded up to system page size .* cannot be represented in type size_t}}
// CHECK: {{#0 0x.* in .*pvalloc}}
// CHECK: {{#1 0x.* in main .*pvalloc-overflow.cc:}}
// CHECK: SUMMARY: HWAddressSanitizer: pvalloc-overflow
// CHECK-NULL: errno: 12

View File

@ -0,0 +1,9 @@
def getRoot(config):
if not config.parent:
return config
return getRoot(config.parent)
root = getRoot(config)
if root.host_os in ['Windows']:
config.unsupported = True

View File

@ -0,0 +1,22 @@
// RUN: %clangxx_hwasan -O0 %s -o %t
// RUN: %env_hwasan_opts=allocator_may_return_null=0 not %run %t 2>&1 | FileCheck %s
// RUN: %env_hwasan_opts=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-NULL
// REQUIRES: stable-runtime
#include <stdio.h>
#include <stdlib.h>
int main() {
void *p = reinterpret_cast<void*>(42);
int res = posix_memalign(&p, 17, 100);
// CHECK: ERROR: HWAddressSanitizer: invalid alignment requested in posix_memalign: 17
// CHECK: {{#0 0x.* in .*posix_memalign}}
// CHECK: {{#1 0x.* in main .*posix_memalign-alignment.cc:}}[[@LINE-3]]
// CHECK: SUMMARY: HWAddressSanitizer: invalid-posix-memalign-alignment
printf("pointer after failed posix_memalign: %zd\n", (size_t)p);
// CHECK-NULL: pointer after failed posix_memalign: 42
return 0;
}

View File

@ -0,0 +1,115 @@
// Test the behavior of malloc/calloc/realloc/new when the allocation size
// exceeds the HWASan allocator's max allowed one.
// By default (allocator_may_return_null=0) the process should crash. With
// allocator_may_return_null=1 the allocator should return 0 and set errno to
// the appropriate error code.
//
// RUN: %clangxx_hwasan -O0 %s -o %t
// RUN: not %run %t malloc 2>&1 | FileCheck %s --check-prefix=CHECK-mCRASH
// RUN: %env_hwasan_opts=allocator_may_return_null=0 not %run %t malloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-mCRASH
// RUN: %env_hwasan_opts=allocator_may_return_null=1 %run %t malloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-mNULL
// RUN: %env_hwasan_opts=allocator_may_return_null=0 not %run %t calloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-cCRASH
// RUN: %env_hwasan_opts=allocator_may_return_null=1 %run %t calloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-cNULL
// RUN: %env_hwasan_opts=allocator_may_return_null=0 not %run %t calloc-overflow 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-coCRASH
// RUN: %env_hwasan_opts=allocator_may_return_null=1 %run %t calloc-overflow 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-coNULL
// RUN: %env_hwasan_opts=allocator_may_return_null=0 not %run %t realloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-rCRASH
// RUN: %env_hwasan_opts=allocator_may_return_null=1 %run %t realloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-rNULL
// RUN: %env_hwasan_opts=allocator_may_return_null=0 not %run %t realloc-after-malloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-mrCRASH
// RUN: %env_hwasan_opts=allocator_may_return_null=1 %run %t realloc-after-malloc 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-mrNULL
// RUN: %env_hwasan_opts=allocator_may_return_null=0 not %run %t new 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-nCRASH
// RUN: %env_hwasan_opts=allocator_may_return_null=1 not %run %t new 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-nCRASH-OOM
// RUN: %env_hwasan_opts=allocator_may_return_null=0 not %run %t new-nothrow 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-nnCRASH
// RUN: %env_hwasan_opts=allocator_may_return_null=1 %run %t new-nothrow 2>&1 \
// RUN: | FileCheck %s --check-prefix=CHECK-nnNULL
// REQUIRES: stable-runtime
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits>
#include <new>
int main(int argc, char **argv) {
assert(argc == 2);
const char *action = argv[1];
fprintf(stderr, "%s:\n", action);
static const size_t kMaxAllowedMallocSizePlusOne = (2UL << 30) + 1;
void *x = nullptr;
if (!strcmp(action, "malloc")) {
x = malloc(kMaxAllowedMallocSizePlusOne);
} else if (!strcmp(action, "calloc")) {
x = calloc((kMaxAllowedMallocSizePlusOne / 4) + 1, 4);
} else if (!strcmp(action, "calloc-overflow")) {
volatile size_t kMaxSizeT = std::numeric_limits<size_t>::max();
size_t kArraySize = 4096;
volatile size_t kArraySize2 = kMaxSizeT / kArraySize + 10;
x = calloc(kArraySize, kArraySize2);
} else if (!strcmp(action, "realloc")) {
x = realloc(0, kMaxAllowedMallocSizePlusOne);
} else if (!strcmp(action, "realloc-after-malloc")) {
char *t = (char*)malloc(100);
*t = 42;
x = realloc(t, kMaxAllowedMallocSizePlusOne);
assert(*t == 42);
free(t);
} else if (!strcmp(action, "new")) {
x = operator new(kMaxAllowedMallocSizePlusOne);
} else if (!strcmp(action, "new-nothrow")) {
x = operator new(kMaxAllowedMallocSizePlusOne, std::nothrow);
} else {
assert(0);
}
fprintf(stderr, "errno: %d\n", errno);
free(x);
return x != nullptr;
}
// CHECK-mCRASH: malloc:
// CHECK-mCRASH: SUMMARY: HWAddressSanitizer: allocation-size-too-big
// CHECK-cCRASH: calloc:
// CHECK-cCRASH: SUMMARY: HWAddressSanitizer: allocation-size-too-big
// CHECK-coCRASH: calloc-overflow:
// CHECK-coCRASH: SUMMARY: HWAddressSanitizer: calloc-overflow
// CHECK-rCRASH: realloc:
// CHECK-rCRASH: SUMMARY: HWAddressSanitizer: allocation-size-too-big
// CHECK-mrCRASH: realloc-after-malloc:
// CHECK-mrCRASH: SUMMARY: HWAddressSanitizer: allocation-size-too-big
// CHECK-nCRASH: new:
// CHECK-nCRASH: SUMMARY: HWAddressSanitizer: allocation-size-too-big
// CHECK-nCRASH-OOM: new:
// CHECK-nCRASH-OOM: SUMMARY: HWAddressSanitizer: out-of-memory
// CHECK-nnCRASH: new-nothrow:
// CHECK-nnCRASH: SUMMARY: HWAddressSanitizer: allocation-size-too-big
// CHECK-mNULL: malloc:
// CHECK-mNULL: errno: 12
// CHECK-cNULL: calloc:
// CHECK-cNULL: errno: 12
// CHECK-coNULL: calloc-overflow:
// CHECK-coNULL: errno: 12
// CHECK-rNULL: realloc:
// CHECK-rNULL: errno: 12
// CHECK-mrNULL: realloc-after-malloc:
// CHECK-mrNULL: errno: 12
// CHECK-nnNULL: new-nothrow: