//===--- rtsan_test_interceptors.cpp - Realtime Sanitizer -------*- 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 "gtest/gtest.h"
#include <sanitizer_common/sanitizer_platform.h>
#include <sanitizer_common/sanitizer_platform_interceptors.h>
#include "rtsan_test_utilities.h"
#if SANITIZER_APPLE
#include <libkern/OSAtomic.h>
#include <os/lock.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#if SANITIZER_INTERCEPT_MEMALIGN || SANITIZER_INTERCEPT_PVALLOC
#include <malloc.h>
#endif
#include <atomic>
#include <chrono>
#include <string>
#include <thread>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/uio.h>
using namespace testing;
using namespace rtsan_testing;
using namespace std::chrono_literals;
void *FakeThreadEntryPoint(void *) { return nullptr; }
class RtsanFileTest : public ::testing::Test {
protected:
void SetUp() override {
const ::testing::TestInfo *const test_info =
::testing::UnitTest::GetInstance()->current_test_info();
file_path_ = std::string("/tmp/rtsan_temporary_test_file_") +
test_info->name() + ".txt";
RemoveTemporaryFile();
}
// Gets a file path with the test's name in it
// This file will be removed if it exists at the end of the test
const char *GetTemporaryFilePath() const { return file_path_.c_str(); }
void TearDown() override { RemoveTemporaryFile(); }
private:
void RemoveTemporaryFile() const { std::remove(GetTemporaryFilePath()); }
std::string file_path_;
};
/*
Allocation and deallocation
*/
TEST(TestRtsanInterceptors, MallocDiesWhenRealtime) {
auto Func = []() { EXPECT_NE(nullptr, malloc(1)); };
ExpectRealtimeDeath(Func, "malloc");
ExpectNonRealtimeSurvival(Func);
}
TEST(TestRtsanInterceptors, CallocDiesWhenRealtime) {
auto Func = []() { EXPECT_NE(nullptr, calloc(2, 4)); };
ExpectRealtimeDeath(Func, "calloc");
ExpectNonRealtimeSurvival(Func);
}
TEST(TestRtsanInterceptors, ReallocDiesWhenRealtime) {
void *ptr_1 = malloc(1);
auto Func = [ptr_1]() { EXPECT_NE(nullptr, realloc(ptr_1, 8)); };
ExpectRealtimeDeath(Func, "realloc");
ExpectNonRealtimeSurvival(Func);
}
#if SANITIZER_APPLE
TEST(TestRtsanInterceptors, ReallocfDiesWhenRealtime) {
void *ptr_1 = malloc(1);
auto Func = [ptr_1]() { EXPECT_NE(nullptr, reallocf(ptr_1, 8)); };
ExpectRealtimeDeath(Func, "reallocf");
ExpectNonRealtimeSurvival(Func);
}
#endif
TEST(TestRtsanInterceptors, VallocDiesWhenRealtime) {
auto Func = []() { EXPECT_NE(nullptr, valloc(4)); };
ExpectRealtimeDeath(Func, "valloc");
ExpectNonRealtimeSurvival(Func);
}
#if SANITIZER_INTERCEPT_ALIGNED_ALLOC
TEST(TestRtsanInterceptors, AlignedAllocDiesWhenRealtime) {
auto Func = []() { EXPECT_NE(nullptr, aligned_alloc(16, 32)); };
ExpectRealtimeDeath(Func, "aligned_alloc");
ExpectNonRealtimeSurvival(Func);
}
#endif
// free_sized and free_aligned_sized (both C23) are not yet supported
TEST(TestRtsanInterceptors, FreeDiesWhenRealtime) {
void *ptr_1 = malloc(1);
void *ptr_2 = malloc(1);
ExpectRealtimeDeath([ptr_1]() { free(ptr_1); }, "free");
ExpectNonRealtimeSurvival([ptr_2]() { free(ptr_2); });
// Prevent malloc/free pair being optimised out
ASSERT_NE(nullptr, ptr_1);
ASSERT_NE(nullptr, ptr_2);
}
TEST(TestRtsanInterceptors, FreeSurvivesWhenRealtimeIfArgumentIsNull) {
RealtimeInvoke([]() { free(NULL); });
ExpectNonRealtimeSurvival([]() { free(NULL); });
}
TEST(TestRtsanInterceptors, PosixMemalignDiesWhenRealtime) {
auto Func = []() {
void *ptr;
posix_memalign(&ptr, 4, 4);
};
ExpectRealtimeDeath(Func, "posix_memalign");
ExpectNonRealtimeSurvival(Func);
}
#if SANITIZER_INTERCEPT_MEMALIGN
TEST(TestRtsanInterceptors, MemalignDiesWhenRealtime) {
auto Func = []() { EXPECT_NE(memalign(2, 2048), nullptr); };
ExpectRealtimeDeath(Func, "memalign");
ExpectNonRealtimeSurvival(Func);
}
#endif
#if SANITIZER_INTERCEPT_PVALLOC
TEST(TestRtsanInterceptors, PvallocDiesWhenRealtime) {
auto Func = []() { EXPECT_NE(pvalloc(2048), nullptr); };
ExpectRealtimeDeath(Func, "pvalloc");
ExpectNonRealtimeSurvival(Func);
}
#endif
/*
Sleeping
*/
TEST(TestRtsanInterceptors, SleepDiesWhenRealtime) {
auto Func = []() { sleep(0u); };
ExpectRealtimeDeath(Func, "sleep");
ExpectNonRealtimeSurvival(Func);
}
TEST(TestRtsanInterceptors, UsleepDiesWhenRealtime) {
auto Func = []() { usleep(1u); };
ExpectRealtimeDeath(Func, "usleep");
ExpectNonRealtimeSurvival(Func);
}
TEST(TestRtsanInterceptors, NanosleepDiesWhenRealtime) {
auto Func = []() {
timespec T{};
nanosleep(&T, &T);
};
ExpectRealtimeDeath(Func, "nanosleep");
ExpectNonRealtimeSurvival(Func);
}
/*
Filesystem
*/
TEST_F(RtsanFileTest, OpenDiesWhenRealtime) {
auto func = [this]() { open(GetTemporaryFilePath(), O_RDONLY); };
ExpectRealtimeDeath(func, "open");
ExpectNonRealtimeSurvival(func);
}
TEST_F(RtsanFileTest, OpenatDiesWhenRealtime) {
auto func = [this]() { openat(0, GetTemporaryFilePath(), O_RDONLY); };
ExpectRealtimeDeath(func, "openat");
ExpectNonRealtimeSurvival(func);
}
TEST_F(RtsanFileTest, OpenCreatesFileWithProperMode) {
const mode_t existing_umask = umask(0);
umask(existing_umask);
const int mode = S_IRGRP | S_IROTH | S_IRUSR | S_IWUSR;
const int fd = open(GetTemporaryFilePath(), O_CREAT | O_WRONLY, mode);
ASSERT_THAT(fd, Ne(-1));
close(fd);
struct stat st;
ASSERT_THAT(stat(GetTemporaryFilePath(), &st), Eq(0));
// Mask st_mode to get permission bits only
const mode_t actual_mode = st.st_mode & 0777;
const mode_t expected_mode = mode & ~existing_umask;
ASSERT_THAT(actual_mode, Eq(expected_mode));
}
TEST_F(RtsanFileTest, CreatDiesWhenRealtime) {
auto func = [this]() { creat(GetTemporaryFilePath(), S_IWOTH | S_IROTH); };
ExpectRealtimeDeath(func, "creat");
ExpectNonRealtimeSurvival(func);
}
TEST(TestRtsanInterceptors, FcntlDiesWhenRealtime) {
auto func = []() { fcntl(0, F_GETFL); };
ExpectRealtimeDeath(func, "fcntl");
ExpectNonRealtimeSurvival(func);
}
TEST_F(RtsanFileTest, FcntlFlockDiesWhenRealtime) {
int fd = creat(GetTemporaryFilePath(), S_IRUSR | S_IWUSR);
ASSERT_THAT(fd, Ne(-1));
auto func = [fd]() {
struct flock lock {};
lock.l_type = F_RDLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
lock.l_pid = ::getpid();
ASSERT_THAT(fcntl(fd, F_GETLK, &lock), Eq(0));
ASSERT_THAT(lock.l_type, F_UNLCK);
};
ExpectRealtimeDeath(func, "fcntl");
ExpectNonRealtimeSurvival(func);
close(fd);
}
TEST_F(RtsanFileTest, FcntlSetFdDiesWhenRealtime) {
int fd = creat(GetTemporaryFilePath(), S_IRUSR | S_IWUSR);
ASSERT_THAT(fd, Ne(-1));
auto func = [fd]() {
int old_flags = fcntl(fd, F_GETFD);
ASSERT_THAT(fcntl(fd, F_SETFD, FD_CLOEXEC), Eq(0));
int flags = fcntl(fd, F_GETFD);
ASSERT_THAT(flags, Ne(-1));
ASSERT_THAT(flags & FD_CLOEXEC, Eq(FD_CLOEXEC));
ASSERT_THAT(fcntl(fd, F_SETFD, old_flags), Eq(0));
ASSERT_THAT(fcntl(fd, F_GETFD), Eq(old_flags));
};
ExpectRealtimeDeath(func, "fcntl");
ExpectNonRealtimeSurvival(func);
close(fd);
}
TEST(TestRtsanInterceptors, CloseDiesWhenRealtime) {
auto func = []() { close(0); };
ExpectRealtimeDeath(func, "close");
ExpectNonRealtimeSurvival(func);
}
TEST_F(RtsanFileTest, FopenDiesWhenRealtime) {
auto func = [this]() {
auto fd = fopen(GetTemporaryFilePath(), "w");
EXPECT_THAT(fd, Ne(nullptr));
};
ExpectRealtimeDeath(func, "fopen");
ExpectNonRealtimeSurvival(func);
}
class RtsanOpenedFileTest : public RtsanFileTest {
protected:
void SetUp() override {
RtsanFileTest::SetUp();
file = fopen(GetTemporaryFilePath(), "w");
ASSERT_THAT(file, Ne(nullptr));
fd = fileno(file);
ASSERT_THAT(fd, Ne(-1));
}
void TearDown() override {
if (file != nullptr)
fclose(file);
RtsanFileTest::TearDown();
}
FILE *GetOpenFile() { return file; }
int GetOpenFd() { return fd; }
private:
FILE *file = nullptr;
int fd = -1;
};
TEST_F(RtsanOpenedFileTest, FreadDiesWhenRealtime) {
auto func = [this]() {
char c{};
fread(&c, 1, 1, GetOpenFile());
};
ExpectRealtimeDeath(func, "fread");
ExpectNonRealtimeSurvival(func);
}
TEST_F(RtsanOpenedFileTest, FwriteDiesWhenRealtime) {
const char *message = "Hello, world!";
auto func = [&]() { fwrite(&message, 1, 4, GetOpenFile()); };
ExpectRealtimeDeath(func, "fwrite");
ExpectNonRealtimeSurvival(func);
}
TEST_F(RtsanFileTest, FcloseDiesWhenRealtime) {
auto fd = fopen(GetTemporaryFilePath(), "w");
EXPECT_THAT(fd, Ne(nullptr));
auto func = [fd]() { fclose(fd); };
ExpectRealtimeDeath(func, "fclose");
ExpectNonRealtimeSurvival(func);
}
TEST(TestRtsanInterceptors, PutsDiesWhenRealtime) {
auto func = []() { puts("Hello, world!\n"); };
ExpectRealtimeDeath(func);
ExpectNonRealtimeSurvival(func);
}
TEST_F(RtsanOpenedFileTest, FputsDiesWhenRealtime) {
auto func = [this]() { fputs("Hello, world!\n", GetOpenFile()); };
ExpectRealtimeDeath(func);
ExpectNonRealtimeSurvival(func);
}
TEST_F(RtsanOpenedFileTest, ReadDiesWhenRealtime) {
auto Func = [this]() {
char c{};
read(GetOpenFd(), &c, 1);
};
ExpectRealtimeDeath(Func, "read");
ExpectNonRealtimeSurvival(Func);
}
TEST_F(RtsanOpenedFileTest, WriteDiesWhenRealtime) {
auto Func = [this]() {
char c = 'a';
write(GetOpenFd(), &c, 1);
};
ExpectRealtimeDeath(Func, "write");
ExpectNonRealtimeSurvival(Func);
}
TEST_F(RtsanOpenedFileTest, PreadDiesWhenRealtime) {
auto Func = [this]() {
char c{};
pread(GetOpenFd(), &c, 1, 0);
};
ExpectRealtimeDeath(Func, "pread");
ExpectNonRealtimeSurvival(Func);
}
TEST_F(RtsanOpenedFileTest, ReadvDiesWhenRealtime) {
auto Func = [this]() {
char c{};
iovec iov{&c, 1};
readv(GetOpenFd(), &iov, 1);
};
ExpectRealtimeDeath(Func, "readv");
ExpectNonRealtimeSurvival(Func);
}
TEST_F(RtsanOpenedFileTest, PwriteDiesWhenRealtime) {
auto Func = [this]() {
char c = 'a';
pwrite(GetOpenFd(), &c, 1, 0);
};
ExpectRealtimeDeath(Func, "pwrite");
ExpectNonRealtimeSurvival(Func);
}
TEST_F(RtsanOpenedFileTest, WritevDiesWhenRealtime) {
auto Func = [this]() {
char c = 'a';
iovec iov{&c, 1};
writev(GetOpenFd(), &iov, 1);
};
ExpectRealtimeDeath(Func, "writev");
ExpectNonRealtimeSurvival(Func);
}
/*
Concurrency
*/
TEST(TestRtsanInterceptors, PthreadCreateDiesWhenRealtime) {
auto Func = []() {
pthread_t thread{};
const pthread_attr_t attr{};
struct thread_info *thread_info{};
pthread_create(&thread, &attr, &FakeThreadEntryPoint, thread_info);
};
ExpectRealtimeDeath(Func, "pthread_create");
ExpectNonRealtimeSurvival(Func);
}
class PthreadMutexLockTest : public ::testing::Test {
protected:
void SetUp() override {
pthread_mutex_init(&mutex, nullptr);
is_locked = false;
}
void TearDown() override {
if (is_locked)
Unlock();
pthread_mutex_destroy(&mutex);
}
void Lock() {
ASSERT_TRUE(!is_locked);
pthread_mutex_lock(&mutex);
is_locked = true;
}
void Unlock() {
ASSERT_TRUE(is_locked);
pthread_mutex_unlock(&mutex);
is_locked = false;
}
private:
pthread_mutex_t mutex;
bool is_locked;
};
TEST_F(PthreadMutexLockTest, PthreadMutexLockDiesWhenRealtime) {
auto Func = [this]() { Lock(); };
ExpectRealtimeDeath(Func, "pthread_mutex_lock");
}
TEST_F(PthreadMutexLockTest, PthreadMutexLockSurvivesWhenNotRealtime) {
auto Func = [this]() { Lock(); };
ExpectNonRealtimeSurvival(Func);
}
TEST_F(PthreadMutexLockTest, PthreadMutexUnlockDiesWhenRealtime) {
Lock();
auto Func = [this]() { Unlock(); };
ExpectRealtimeDeath(Func, "pthread_mutex_unlock");
ExpectNonRealtimeSurvival(Func);
}
TEST_F(PthreadMutexLockTest, PthreadMutexUnlockSurvivesWhenNotRealtime) {
Lock();
auto Func = [this]() { Unlock(); };
ExpectNonRealtimeSurvival(Func);
}
TEST(TestRtsanInterceptors, PthreadJoinDiesWhenRealtime) {
pthread_t thread{};
ASSERT_EQ(0,
pthread_create(&thread, nullptr, &FakeThreadEntryPoint, nullptr));
auto Func = [&thread]() { pthread_join(thread, nullptr); };
ExpectRealtimeDeath(Func, "pthread_join");
ExpectNonRealtimeSurvival(Func);
}
#if SANITIZER_APPLE
#pragma clang diagnostic push
// OSSpinLockLock is deprecated, but still in use in libc++
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
TEST(TestRtsanInterceptors, OsSpinLockLockDiesWhenRealtime) {
auto Func = []() {
OSSpinLock spin_lock{};
OSSpinLockLock(&spin_lock);
};
ExpectRealtimeDeath(Func, "OSSpinLockLock");
ExpectNonRealtimeSurvival(Func);
}
#pragma clang diagnostic pop
TEST(TestRtsanInterceptors, OsUnfairLockLockDiesWhenRealtime) {
auto Func = []() {
os_unfair_lock_s unfair_lock{};
os_unfair_lock_lock(&unfair_lock);
};
ExpectRealtimeDeath(Func, "os_unfair_lock_lock");
ExpectNonRealtimeSurvival(Func);
}
#endif
#if SANITIZER_LINUX
TEST(TestRtsanInterceptors, SpinLockLockDiesWhenRealtime) {
pthread_spinlock_t spin_lock;
pthread_spin_init(&spin_lock, PTHREAD_PROCESS_SHARED);
auto Func = [&]() { pthread_spin_lock(&spin_lock); };
ExpectRealtimeDeath(Func, "pthread_spin_lock");
ExpectNonRealtimeSurvival(Func);
}
#endif
TEST(TestRtsanInterceptors, PthreadCondSignalDiesWhenRealtime) {
pthread_cond_t cond{};
pthread_cond_init(&cond, NULL);
auto Func = [&cond]() { pthread_cond_signal(&cond); };
ExpectRealtimeDeath(Func, "pthread_cond_signal");
ExpectNonRealtimeSurvival(Func);
pthread_cond_destroy(&cond);
}
TEST(TestRtsanInterceptors, PthreadCondBroadcastDiesWhenRealtime) {
pthread_cond_t cond{};
pthread_cond_init(&cond, NULL);
auto Func = [&cond]() { pthread_cond_broadcast(&cond); };
ExpectRealtimeDeath(Func, "pthread_cond_broadcast");
ExpectNonRealtimeSurvival(Func);
pthread_cond_destroy(&cond);
}
TEST(TestRtsanInterceptors, PthreadCondWaitDiesWhenRealtime) {
pthread_cond_t cond;
pthread_mutex_t mutex;
ASSERT_EQ(0, pthread_cond_init(&cond, nullptr));
ASSERT_EQ(0, pthread_mutex_init(&mutex, nullptr));
auto Func = [&]() { pthread_cond_wait(&cond, &mutex); };
ExpectRealtimeDeath(Func, "pthread_cond_wait");
// It's very difficult to test the success case here without doing some
// sleeping, which is at the mercy of the scheduler. What's really important
// here is the interception - so we're only testing that for now.
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
}
class PthreadRwlockTest : public ::testing::Test {
protected:
void SetUp() override {
pthread_rwlock_init(&rw_lock, nullptr);
is_locked = false;
}
void TearDown() override {
if (is_locked)
Unlock();
pthread_rwlock_destroy(&rw_lock);
}
void RdLock() {
ASSERT_TRUE(!is_locked);
pthread_rwlock_rdlock(&rw_lock);
is_locked = true;
}
void WrLock() {
ASSERT_TRUE(!is_locked);
pthread_rwlock_wrlock(&rw_lock);
is_locked = true;
}
void Unlock() {
ASSERT_TRUE(is_locked);
pthread_rwlock_unlock(&rw_lock);
is_locked = false;
}
private:
pthread_rwlock_t rw_lock;
bool is_locked;
};
TEST_F(PthreadRwlockTest, PthreadRwlockRdlockDiesWhenRealtime) {
auto Func = [this]() { RdLock(); };
ExpectRealtimeDeath(Func, "pthread_rwlock_rdlock");
}
TEST_F(PthreadRwlockTest, PthreadRwlockRdlockSurvivesWhenNonRealtime) {
auto Func = [this]() { RdLock(); };
ExpectNonRealtimeSurvival(Func);
}
TEST_F(PthreadRwlockTest, PthreadRwlockUnlockDiesWhenRealtime) {
RdLock();
auto Func = [this]() { Unlock(); };
ExpectRealtimeDeath(Func, "pthread_rwlock_unlock");
}
TEST_F(PthreadRwlockTest, PthreadRwlockUnlockSurvivesWhenNonRealtime) {
RdLock();
auto Func = [this]() { Unlock(); };
ExpectNonRealtimeSurvival(Func);
}
TEST_F(PthreadRwlockTest, PthreadRwlockWrlockDiesWhenRealtime) {
auto Func = [this]() { WrLock(); };
ExpectRealtimeDeath(Func, "pthread_rwlock_wrlock");
}
TEST_F(PthreadRwlockTest, PthreadRwlockWrlockSurvivesWhenNonRealtime) {
auto Func = [this]() { WrLock(); };
ExpectNonRealtimeSurvival(Func);
}
/*
Sockets
*/
TEST(TestRtsanInterceptors, OpeningASocketDiesWhenRealtime) {
auto Func = []() { socket(PF_INET, SOCK_STREAM, 0); };
ExpectRealtimeDeath(Func, "socket");
ExpectNonRealtimeSurvival(Func);
}
TEST(TestRtsanInterceptors, SendToASocketDiesWhenRealtime) {
auto Func = []() { send(0, nullptr, 0, 0); };
ExpectRealtimeDeath(Func, "send");
ExpectNonRealtimeSurvival(Func);
}
TEST(TestRtsanInterceptors, SendmsgToASocketDiesWhenRealtime) {
msghdr msg{};
auto Func = [&]() { sendmsg(0, &msg, 0); };
ExpectRealtimeDeath(Func, "sendmsg");
ExpectNonRealtimeSurvival(Func);
}
TEST(TestRtsanInterceptors, SendtoToASocketDiesWhenRealtime) {
sockaddr addr{};
socklen_t len{};
auto Func = [&]() { sendto(0, nullptr, 0, 0, &addr, len); };
ExpectRealtimeDeath(Func, "sendto");
ExpectNonRealtimeSurvival(Func);
}
TEST(TestRtsanInterceptors, RecvFromASocketDiesWhenRealtime) {
auto Func = []() { recv(0, nullptr, 0, 0); };
ExpectRealtimeDeath(Func, "recv");
ExpectNonRealtimeSurvival(Func);
}
TEST(TestRtsanInterceptors, RecvfromOnASocketDiesWhenRealtime) {
sockaddr addr{};
socklen_t len{};
auto Func = [&]() { recvfrom(0, nullptr, 0, 0, &addr, &len); };
ExpectRealtimeDeath(Func, "recvfrom");
ExpectNonRealtimeSurvival(Func);
}
TEST(TestRtsanInterceptors, RecvmsgOnASocketDiesWhenRealtime) {
msghdr msg{};
auto Func = [&]() { recvmsg(0, &msg, 0); };
ExpectRealtimeDeath(Func, "recvmsg");
ExpectNonRealtimeSurvival(Func);
}
TEST(TestRtsanInterceptors, ShutdownOnASocketDiesWhenRealtime) {
auto Func = [&]() { shutdown(0, 0); };
ExpectRealtimeDeath(Func, "shutdown");
ExpectNonRealtimeSurvival(Func);
}