llvm-project/compiler-rt/lib/lsan/lsan_common_fuchsia.cpp
Leonard Chan 4db6803dc7 [lsan][fuchsia] Add extra check for allocator cache to avoid overflow
Prior to this, we would check if the end of the allocator cache was located
before the end of the chunk passed to the tls check. However, if the actual
allocator cache comes after the end of the chunk, then the sub in the
`end - params->allocator_caches[i]` bit overflows. Since the resulting type
is an unsigned uptr, this is not UB, but if the signed result would be a
negative value (ie. `end < params->allocator_caches[i]`) then this will
actually result in a very large unsigned value much bigger than the compared
`sizeof(AllocatorCache)` which will almost always be true. This can cause
ScanRangeForPointers to accept incorrect values: a begin pointing to some
address, and `params->allocator_caches[i]` pointing to some much larger
address way past the end of the chunk which can result in a page fault/stack overflow.

Differential Revision: https://reviews.llvm.org/D159518
2023-09-14 23:03:16 +00:00

169 lines
6.2 KiB
C++

//=-- lsan_common_fuchsia.cpp --------------------------------------------===//
//
// 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
//
//===---------------------------------------------------------------------===//
//
// This file is a part of LeakSanitizer.
// Implementation of common leak checking functionality. Fuchsia-specific code.
//
//===---------------------------------------------------------------------===//
#include "lsan_common.h"
#include "lsan_thread.h"
#include "sanitizer_common/sanitizer_platform.h"
#if CAN_SANITIZE_LEAKS && SANITIZER_FUCHSIA
#include <zircon/sanitizer.h>
#include "lsan_allocator.h"
#include "sanitizer_common/sanitizer_flags.h"
#include "sanitizer_common/sanitizer_stoptheworld_fuchsia.h"
#include "sanitizer_common/sanitizer_thread_registry.h"
// Ensure that the Zircon system ABI is linked in.
#pragma comment(lib, "zircon")
namespace __lsan {
void InitializePlatformSpecificModules() {}
LoadedModule *GetLinker() { return nullptr; }
__attribute__((tls_model("initial-exec"))) THREADLOCAL int disable_counter;
bool DisabledInThisThread() { return disable_counter > 0; }
void DisableInThisThread() { disable_counter++; }
void EnableInThisThread() {
if (disable_counter == 0) {
DisableCounterUnderflow();
}
disable_counter--;
}
// There is nothing left to do after the globals callbacks.
void ProcessGlobalRegions(Frontier *frontier) {}
// Nothing to do here.
void ProcessPlatformSpecificAllocations(Frontier *frontier) {}
// On Fuchsia, we can intercept _Exit gracefully, and return a failing exit
// code if required at that point. Calling Die() here is undefined
// behavior and causes rare race conditions.
void HandleLeaks() {}
// This is defined differently in asan_fuchsia.cpp and lsan_fuchsia.cpp.
bool UseExitcodeOnLeak();
int ExitHook(int status) {
if (common_flags()->detect_leaks && common_flags()->leak_check_at_exit) {
if (UseExitcodeOnLeak())
DoLeakCheck();
else
DoRecoverableLeakCheckVoid();
}
return status == 0 && HasReportedLeaks() ? common_flags()->exitcode : status;
}
void LockStuffAndStopTheWorld(StopTheWorldCallback callback,
CheckForLeaksParam *argument) {
ScopedStopTheWorldLock lock;
struct Params {
InternalMmapVector<uptr> allocator_caches;
StopTheWorldCallback callback;
CheckForLeaksParam *argument;
} params = {{}, callback, argument};
// Callback from libc for globals (data/bss modulo relro), when enabled.
auto globals = +[](void *chunk, size_t size, void *data) {
auto params = static_cast<const Params *>(data);
uptr begin = reinterpret_cast<uptr>(chunk);
uptr end = begin + size;
ScanGlobalRange(begin, end, &params->argument->frontier);
};
// Callback from libc for thread stacks.
auto stacks = +[](void *chunk, size_t size, void *data) {
auto params = static_cast<const Params *>(data);
uptr begin = reinterpret_cast<uptr>(chunk);
uptr end = begin + size;
ScanRangeForPointers(begin, end, &params->argument->frontier, "STACK",
kReachable);
};
// Callback from libc for thread registers.
auto registers = +[](void *chunk, size_t size, void *data) {
auto params = static_cast<const Params *>(data);
uptr begin = reinterpret_cast<uptr>(chunk);
uptr end = begin + size;
ScanRangeForPointers(begin, end, &params->argument->frontier, "REGISTERS",
kReachable);
};
if (flags()->use_tls) {
// Collect the allocator cache range from each thread so these
// can all be excluded from the reported TLS ranges.
GetAllThreadAllocatorCachesLocked(&params.allocator_caches);
__sanitizer::Sort(params.allocator_caches.data(),
params.allocator_caches.size());
}
// Callback from libc for TLS regions. This includes thread_local
// variables as well as C11 tss_set and POSIX pthread_setspecific.
auto tls = +[](void *chunk, size_t size, void *data) {
auto params = static_cast<const Params *>(data);
uptr begin = reinterpret_cast<uptr>(chunk);
uptr end = begin + size;
auto i = __sanitizer::InternalLowerBound(params->allocator_caches, begin);
if (i < params->allocator_caches.size() &&
params->allocator_caches[i] >= begin &&
params->allocator_caches[i] <= end &&
end - params->allocator_caches[i] >= sizeof(AllocatorCache)) {
// Split the range in two and omit the allocator cache within.
ScanRangeForPointers(begin, params->allocator_caches[i],
&params->argument->frontier, "TLS", kReachable);
uptr begin2 = params->allocator_caches[i] + sizeof(AllocatorCache);
ScanRangeForPointers(begin2, end, &params->argument->frontier, "TLS",
kReachable);
} else {
ScanRangeForPointers(begin, end, &params->argument->frontier, "TLS",
kReachable);
}
};
// This stops the world and then makes callbacks for various memory regions.
// The final callback is the last thing before the world starts up again.
__sanitizer_memory_snapshot(
flags()->use_globals ? globals : nullptr,
flags()->use_stacks ? stacks : nullptr,
flags()->use_registers ? registers : nullptr,
flags()->use_tls ? tls : nullptr,
[](zx_status_t, void *data) {
auto params = static_cast<const Params *>(data);
// We don't use the thread registry at all for enumerating the threads
// and their stacks, registers, and TLS regions. So use it separately
// just for the allocator cache, and to call ScanExtraStackRanges,
// which ASan needs.
if (flags()->use_stacks) {
InternalMmapVector<Range> ranges;
GetThreadExtraStackRangesLocked(&ranges);
ScanExtraStackRanges(ranges, &params->argument->frontier);
}
params->callback(SuspendedThreadsListFuchsia(), params->argument);
},
&params);
}
} // namespace __lsan
// This is declared (in extern "C") by <zircon/sanitizer.h>.
// _Exit calls this directly to intercept and change the status value.
int __sanitizer_process_exit_hook(int status) {
return __lsan::ExitHook(status);
}
#endif