Skip to content

[TSan] Add interceptors for mach_vm_[de]allocate #153

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler-rt/lib/tsan/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ set(TSAN_CXX_SOURCES
if(APPLE)
list(APPEND TSAN_SOURCES
rtl/tsan_interceptors_mac.cc
rtl/tsan_interceptors_mach_vm.cpp
rtl/tsan_platform_mac.cc
rtl/tsan_platform_posix.cc)
elseif(UNIX)
Expand Down
14 changes: 4 additions & 10 deletions compiler-rt/lib/tsan/rtl/tsan_interceptors.cc
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,8 @@ TSAN_INTERCEPTOR(char*, strdup, const char *str) {
return REAL(strdup)(str);
}

// Zero out addr if it points into shadow memory and was provided as a hint
// only, i.e., MAP_FIXED is not set.
static bool fix_mmap_addr(void **addr, long_t sz, int flags) {
if (*addr) {
if (!IsAppMem((uptr)*addr) || !IsAppMem((uptr)*addr + sz - 1)) {
Expand All @@ -790,22 +792,14 @@ static void *mmap_interceptor(ThreadState *thr, uptr pc, Mmap real_mmap,
void *res = real_mmap(addr, sz, prot, flags, fd, off);
if (res != MAP_FAILED) {
if (fd > 0) FdAccess(thr, pc, fd);
if (thr->ignore_reads_and_writes == 0)
MemoryRangeImitateWrite(thr, pc, (uptr)res, sz);
else
MemoryResetRange(thr, pc, (uptr)res, sz);
MemoryRangeImitateWriteOrResetRange(thr, pc, (uptr)res, sz);
}
return res;
}

TSAN_INTERCEPTOR(int, munmap, void *addr, long_t sz) {
SCOPED_TSAN_INTERCEPTOR(munmap, addr, sz);
if (sz != 0) {
// If sz == 0, munmap will return EINVAL and don't unmap any memory.
DontNeedShadowFor((uptr)addr, sz);
ScopedGlobalProcessor sgp;
ctx->metamap.ResetRange(thr->proc(), (uptr)addr, (uptr)sz);
}
UnmapShadow(thr, (uptr)addr, sz);
int res = REAL(munmap)(addr, sz);
return res;
}
Expand Down
52 changes: 52 additions & 0 deletions compiler-rt/lib/tsan/rtl/tsan_interceptors_mach_vm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//===-- tsan_interceptors_mach_vm.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 ThreadSanitizer (TSan), a race detector.
//
// Interceptors for mach_vm_* user space memory routines on Darwin.
//===----------------------------------------------------------------------===//

#include "interception/interception.h"
#include "tsan_interceptors.h"
#include "tsan_platform.h"

#include <mach/mach.h>

namespace __tsan {

static bool intersects_with_shadow(mach_vm_address_t *address,
mach_vm_size_t size, int flags) {
// VM_FLAGS_FIXED is 0x0, so we have to test for VM_FLAGS_ANYWHERE.
if (flags & VM_FLAGS_ANYWHERE) return false;
uptr ptr = *address;
return !IsAppMem(ptr) || !IsAppMem(ptr + size - 1);
}

TSAN_INTERCEPTOR(kern_return_t, mach_vm_allocate, vm_map_t target,
mach_vm_address_t *address, mach_vm_size_t size, int flags) {
SCOPED_TSAN_INTERCEPTOR(mach_vm_allocate, target, address, size, flags);
if (target != mach_task_self())
return REAL(mach_vm_allocate)(target, address, size, flags);
if (intersects_with_shadow(address, size, flags))
return KERN_NO_SPACE;
kern_return_t res = REAL(mach_vm_allocate)(target, address, size, flags);
if (res == KERN_SUCCESS)
MemoryRangeImitateWriteOrResetRange(thr, pc, *address, size);
return res;
}

TSAN_INTERCEPTOR(kern_return_t, mach_vm_deallocate, vm_map_t target,
mach_vm_address_t address, mach_vm_size_t size) {
SCOPED_TSAN_INTERCEPTOR(mach_vm_deallocate, target, address, size);
if (target != mach_task_self())
return REAL(mach_vm_deallocate)(target, address, size);
UnmapShadow(thr, address, size);
return REAL(mach_vm_deallocate)(target, address, size);
}

} // namespace __tsan
17 changes: 17 additions & 0 deletions compiler-rt/lib/tsan/rtl/tsan_rtl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,15 @@ void DontNeedShadowFor(uptr addr, uptr size) {
ReleaseMemoryPagesToOS(MemToShadow(addr), MemToShadow(addr + size));
}

#if !SANITIZER_GO
void UnmapShadow(ThreadState *thr, uptr addr, uptr size) {
if (size == 0) return;
DontNeedShadowFor(addr, size);
ScopedGlobalProcessor sgp;
ctx->metamap.ResetRange(thr->proc(), addr, size);
}
#endif

void MapShadow(uptr addr, uptr size) {
// Global data is not 64K aligned, but there are no adjacent mappings,
// so we can get away with unaligned mapping.
Expand Down Expand Up @@ -986,6 +995,14 @@ void MemoryRangeImitateWrite(ThreadState *thr, uptr pc, uptr addr, uptr size) {
MemoryRangeSet(thr, pc, addr, size, s.raw());
}

void MemoryRangeImitateWriteOrResetRange(ThreadState *thr, uptr pc, uptr addr,
uptr size) {
if (thr->ignore_reads_and_writes == 0)
MemoryRangeImitateWrite(thr, pc, addr, size);
else
MemoryResetRange(thr, pc, addr, size);
}

ALWAYS_INLINE USED
void FuncEntry(ThreadState *thr, uptr pc) {
StatInc(thr, StatFuncEnter);
Expand Down
3 changes: 3 additions & 0 deletions compiler-rt/lib/tsan/rtl/tsan_rtl.h
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,7 @@ void ALWAYS_INLINE StatSet(ThreadState *thr, StatType typ, u64 n) {
void MapShadow(uptr addr, uptr size);
void MapThreadTrace(uptr addr, uptr size, const char *name);
void DontNeedShadowFor(uptr addr, uptr size);
void UnmapShadow(ThreadState *thr, uptr addr, uptr size);
void InitializeShadowMemory();
void InitializeInterceptors();
void InitializeLibIgnore();
Expand Down Expand Up @@ -760,6 +761,8 @@ void ALWAYS_INLINE MemoryWriteAtomic(ThreadState *thr, uptr pc,
void MemoryResetRange(ThreadState *thr, uptr pc, uptr addr, uptr size);
void MemoryRangeFreed(ThreadState *thr, uptr pc, uptr addr, uptr size);
void MemoryRangeImitateWrite(ThreadState *thr, uptr pc, uptr addr, uptr size);
void MemoryRangeImitateWriteOrResetRange(ThreadState *thr, uptr pc, uptr addr,
uptr size);

void ThreadIgnoreBegin(ThreadState *thr, uptr pc, bool save_stack = true);
void ThreadIgnoreEnd(ThreadState *thr, uptr pc);
Expand Down
73 changes: 73 additions & 0 deletions compiler-rt/test/tsan/Darwin/mach_vm_allocate.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Test that mach_vm_[de]allocate resets shadow memory status.
//
// RUN: %clang_tsan %s -o %t
// RUN: %run %t 2>&1 | FileCheck %s --implicit-check-not='ThreadSanitizer'

#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <pthread.h>
#include <assert.h>
#include <stdio.h>

#include "../test.h"

void AnnotateIgnoreReadsBegin(const char *f, int l);
void AnnotateIgnoreReadsEnd(const char *f, int l);
void AnnotateIgnoreWritesBegin(const char *f, int l);
void AnnotateIgnoreWritesEnd(const char *f, int l);

static int *global_ptr;
const mach_vm_size_t alloc_size = sizeof(int);

static int *alloc() {
mach_vm_address_t addr;
kern_return_t res =
mach_vm_allocate(mach_task_self(), &addr, alloc_size, VM_FLAGS_ANYWHERE);
assert(res == KERN_SUCCESS);
return (int *)addr;
}

static void alloc_fixed(int *ptr) {
mach_vm_address_t addr = (mach_vm_address_t)ptr;
kern_return_t res =
mach_vm_allocate(mach_task_self(), &addr, alloc_size, VM_FLAGS_FIXED);
assert(res == KERN_SUCCESS);
}

static void dealloc(int *ptr) {
kern_return_t res =
mach_vm_deallocate(mach_task_self(), (mach_vm_address_t)ptr, alloc_size);
assert(res == KERN_SUCCESS);
}

static void *Thread(void *arg) {
*global_ptr = 7; // Assignment 1

// We want to test that TSan does not report a race between the two
// assignments to global_ptr when memory is re-allocated here. The calls to
// the API itself are racy though, so ignore them.
AnnotateIgnoreWritesBegin(__FILE__, __LINE__);
dealloc(global_ptr);
alloc_fixed(global_ptr);
AnnotateIgnoreWritesEnd(__FILE__, __LINE__);

barrier_wait(&barrier);
return NULL;;
}

int main(int argc, const char *argv[]) {
barrier_init(&barrier, 2);
global_ptr = alloc();
pthread_t t;
pthread_create(&t, NULL, Thread, NULL);

barrier_wait(&barrier);
*global_ptr = 8; // Assignment 2

pthread_join(t, NULL);
dealloc(global_ptr);
printf("Done.\n");
return 0;
}

// CHECK: Done.