build: add MemorySanitizer (MSAN) support (#3788)

- unpoison results from linux system call wrappers

- unpoison results from stat/fstat/lstat to pacify clang 14
  (fixed in later versions)

- add MSAN build option

- turn on MSAN CI build
This commit is contained in:
Ben Noordhuis 2022-10-18 23:21:42 +02:00 committed by GitHub
parent 73b0c1f947
commit acfe668ecb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 155 additions and 30 deletions

View File

@ -14,7 +14,7 @@ on:
jobs: jobs:
sanitizers: sanitizers:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Setup - name: Setup
@ -22,6 +22,25 @@ jobs:
sudo apt-get install ninja-build sudo apt-get install ninja-build
- name: Envinfo - name: Envinfo
run: npx envinfo run: npx envinfo
- name: ASAN Build
run: |
mkdir build-asan
(cd build-asan && cmake .. -G Ninja -DBUILD_TESTING=ON -DASAN=ON -DCMAKE_BUILD_TYPE=Debug)
cmake --build build-asan
- name: ASAN Test
run: |
./build-asan/uv_run_tests_a
- name: MSAN Build
run: |
mkdir build-msan
(cd build-msan && cmake .. -G Ninja -DBUILD_TESTING=ON -DMSAN=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang)
cmake --build build-msan
- name: MSAN Test
run: |
./build-msan/uv_run_tests_a
- name: TSAN Build - name: TSAN Build
run: | run: |
mkdir build-tsan mkdir build-tsan
@ -31,11 +50,3 @@ jobs:
continue-on-error: true # currently permit failures continue-on-error: true # currently permit failures
run: | run: |
./build-tsan/uv_run_tests_a ./build-tsan/uv_run_tests_a
- name: ASAN Build
run: |
mkdir build-asan
(cd build-asan && cmake .. -G Ninja -DBUILD_TESTING=ON -DASAN=ON -DCMAKE_BUILD_TYPE=Debug)
cmake --build build-asan
- name: ASAN Test
run: |
./build-asan/uv_run_tests_a

View File

@ -36,13 +36,19 @@ if(QEMU)
add_definitions(-D__QEMU__=1) add_definitions(-D__QEMU__=1)
endif() endif()
# Note: these are mutually exclusive.
option(ASAN "Enable AddressSanitizer (ASan)" OFF) option(ASAN "Enable AddressSanitizer (ASan)" OFF)
option(MSAN "Enable MemorySanitizer (MSan)" OFF)
option(TSAN "Enable ThreadSanitizer (TSan)" OFF) option(TSAN "Enable ThreadSanitizer (TSan)" OFF)
if((ASAN OR TSAN) AND NOT (CMAKE_C_COMPILER_ID MATCHES "AppleClang|GNU|Clang")) if((ASAN OR TSAN) AND NOT (CMAKE_C_COMPILER_ID MATCHES "AppleClang|GNU|Clang"))
message(SEND_ERROR "Sanitizer support requires clang or gcc. Try again with -DCMAKE_C_COMPILER.") message(SEND_ERROR "Sanitizer support requires clang or gcc. Try again with -DCMAKE_C_COMPILER.")
endif() endif()
if(MSAN AND NOT CMAKE_C_COMPILER_ID MATCHES "AppleClang|Clang")
message(SEND_ERROR "MemorySanitizer requires clang. Try again with -DCMAKE_C_COMPILER=clang")
endif()
if(ASAN) if(ASAN)
add_definitions(-D__ASAN__=1) add_definitions(-D__ASAN__=1)
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -fsanitize=address") set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -fsanitize=address")
@ -50,6 +56,13 @@ if(ASAN)
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=address") set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=address")
endif() endif()
if(MSAN)
add_definitions(-D__MSAN__=1)
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -fsanitize=memory")
set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=memory")
set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=memory")
endif()
if(TSAN) if(TSAN)
add_definitions(-D__TSAN__=1) add_definitions(-D__TSAN__=1)
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -fsanitize=thread") set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -fsanitize=thread")

View File

@ -425,7 +425,7 @@ static char* uv__rawname(const char* cp, char (*dst)[FILENAME_MAX+1]) {
static int uv__path_is_a_directory(char* filename) { static int uv__path_is_a_directory(char* filename) {
struct stat statbuf; struct stat statbuf;
if (stat(filename, &statbuf) < 0) if (uv__stat(filename, &statbuf) < 0)
return -1; /* failed: not a directory, assume it is a file */ return -1; /* failed: not a directory, assume it is a file */
if (statbuf.st_type == VDIR) if (statbuf.st_type == VDIR)

View File

@ -516,7 +516,7 @@ done:
if (result == -1 && errno == EOPNOTSUPP) { if (result == -1 && errno == EOPNOTSUPP) {
struct stat buf; struct stat buf;
ssize_t rc; ssize_t rc;
rc = fstat(req->file, &buf); rc = uv__fstat(req->file, &buf);
if (rc == 0 && S_ISDIR(buf.st_mode)) { if (rc == 0 && S_ISDIR(buf.st_mode)) {
errno = EISDIR; errno = EISDIR;
} }
@ -708,7 +708,7 @@ static ssize_t uv__fs_readlink(uv_fs_t* req) {
/* We may not have a real PATH_MAX. Read size of link. */ /* We may not have a real PATH_MAX. Read size of link. */
struct stat st; struct stat st;
int ret; int ret;
ret = lstat(req->path, &st); ret = uv__lstat(req->path, &st);
if (ret != 0) if (ret != 0)
return -1; return -1;
if (!S_ISLNK(st.st_mode)) { if (!S_ISLNK(st.st_mode)) {
@ -1281,7 +1281,7 @@ static ssize_t uv__fs_copyfile(uv_fs_t* req) {
return srcfd; return srcfd;
/* Get the source file's mode. */ /* Get the source file's mode. */
if (fstat(srcfd, &src_statsbuf)) { if (uv__fstat(srcfd, &src_statsbuf)) {
err = UV__ERR(errno); err = UV__ERR(errno);
goto out; goto out;
} }
@ -1309,7 +1309,7 @@ static ssize_t uv__fs_copyfile(uv_fs_t* req) {
destination are not the same file. If they are the same, bail out early. */ destination are not the same file. If they are the same, bail out early. */
if ((req->flags & UV_FS_COPYFILE_EXCL) == 0) { if ((req->flags & UV_FS_COPYFILE_EXCL) == 0) {
/* Get the destination file's mode. */ /* Get the destination file's mode. */
if (fstat(dstfd, &dst_statsbuf)) { if (uv__fstat(dstfd, &dst_statsbuf)) {
err = UV__ERR(errno); err = UV__ERR(errno);
goto out; goto out;
} }
@ -1588,7 +1588,7 @@ static int uv__fs_stat(const char *path, uv_stat_t *buf) {
if (ret != UV_ENOSYS) if (ret != UV_ENOSYS)
return ret; return ret;
ret = stat(path, &pbuf); ret = uv__stat(path, &pbuf);
if (ret == 0) if (ret == 0)
uv__to_stat(&pbuf, buf); uv__to_stat(&pbuf, buf);
@ -1604,7 +1604,7 @@ static int uv__fs_lstat(const char *path, uv_stat_t *buf) {
if (ret != UV_ENOSYS) if (ret != UV_ENOSYS)
return ret; return ret;
ret = lstat(path, &pbuf); ret = uv__lstat(path, &pbuf);
if (ret == 0) if (ret == 0)
uv__to_stat(&pbuf, buf); uv__to_stat(&pbuf, buf);
@ -1620,7 +1620,7 @@ static int uv__fs_fstat(int fd, uv_stat_t *buf) {
if (ret != UV_ENOSYS) if (ret != UV_ENOSYS)
return ret; return ret;
ret = fstat(fd, &pbuf); ret = uv__fstat(fd, &pbuf);
if (ret == 0) if (ret == 0)
uv__to_stat(&pbuf, buf); uv__to_stat(&pbuf, buf);

View File

@ -33,6 +33,22 @@
#include <stdio.h> #include <stdio.h>
#include <errno.h> #include <errno.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#define uv__msan_unpoison(p, n) \
do { \
(void) (p); \
(void) (n); \
} while (0)
#if defined(__has_feature)
# if __has_feature(memory_sanitizer)
# include <sanitizer/msan_interface.h>
# undef uv__msan_unpoison
# define uv__msan_unpoison __msan_unpoison
# endif
#endif
#if defined(__STRICT_ANSI__) #if defined(__STRICT_ANSI__)
# define inline __inline # define inline __inline
@ -341,6 +357,36 @@ UV_UNUSED(static char* uv__basename_r(const char* path)) {
return s + 1; return s + 1;
} }
UV_UNUSED(static int uv__fstat(int fd, struct stat* s)) {
int rc;
rc = fstat(fd, s);
if (rc >= 0)
uv__msan_unpoison(s, sizeof(*s));
return rc;
}
UV_UNUSED(static int uv__lstat(const char* path, struct stat* s)) {
int rc;
rc = lstat(path, s);
if (rc >= 0)
uv__msan_unpoison(s, sizeof(*s));
return rc;
}
UV_UNUSED(static int uv__stat(const char* path, struct stat* s)) {
int rc;
rc = stat(path, s);
if (rc >= 0)
uv__msan_unpoison(s, sizeof(*s));
return rc;
}
#if defined(__linux__) #if defined(__linux__)
int uv__inotify_fork(uv_loop_t* loop, void* old_watchers); int uv__inotify_fork(uv_loop_t* loop, void* old_watchers);
ssize_t ssize_t

View File

@ -529,7 +529,7 @@ int uv_fs_event_start(uv_fs_event_t* handle,
handle->realpath_len = 0; handle->realpath_len = 0;
handle->cf_flags = flags; handle->cf_flags = flags;
if (fstat(fd, &statbuf)) if (uv__fstat(fd, &statbuf))
goto fallback; goto fallback;
/* FSEvents works only with directories */ /* FSEvents works only with directories */
if (!(statbuf.st_mode & S_IFDIR)) if (!(statbuf.st_mode & S_IFDIR))

View File

@ -34,16 +34,18 @@
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h>
#include <net/if.h> #include <net/if.h>
#include <sys/epoll.h> #include <sys/epoll.h>
#include <sys/inotify.h> #include <sys/inotify.h>
#include <sys/param.h> #include <sys/param.h>
#include <sys/prctl.h> #include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/syscall.h> #include <sys/syscall.h>
#include <sys/sysinfo.h> #include <sys/sysinfo.h>
#include <unistd.h> #include <sys/types.h>
#include <fcntl.h>
#include <time.h> #include <time.h>
#include <unistd.h>
#if defined(__arm__) #if defined(__arm__)
# if defined(__thumb__) || defined(__ARM_EABI__) # if defined(__thumb__) || defined(__ARM_EABI__)
@ -212,7 +214,13 @@ int uv__statx(int dirfd,
#if !defined(__NR_statx) || defined(__ANDROID_API__) && __ANDROID_API__ < 30 #if !defined(__NR_statx) || defined(__ANDROID_API__) && __ANDROID_API__ < 30
return errno = ENOSYS, -1; return errno = ENOSYS, -1;
#else #else
return syscall(__NR_statx, dirfd, path, flags, mask, statxbuf); int rc;
rc = syscall(__NR_statx, dirfd, path, flags, mask, statxbuf);
if (rc >= 0)
uv__msan_unpoison(statxbuf, sizeof(*statxbuf));
return rc;
#endif #endif
} }
@ -221,7 +229,13 @@ ssize_t uv__getrandom(void* buf, size_t buflen, unsigned flags) {
#if !defined(__NR_getrandom) || defined(__ANDROID_API__) && __ANDROID_API__ < 28 #if !defined(__NR_getrandom) || defined(__ANDROID_API__) && __ANDROID_API__ < 28
return errno = ENOSYS, -1; return errno = ENOSYS, -1;
#else #else
return syscall(__NR_getrandom, buf, buflen, flags); ssize_t rc;
rc = syscall(__NR_getrandom, buf, buflen, flags);
if (rc >= 0)
uv__msan_unpoison(buf, buflen);
return rc;
#endif #endif
} }
@ -1685,6 +1699,35 @@ int uv__sendmmsg(int fd, struct uv__mmsghdr* mmsg, unsigned int vlen) {
} }
static void uv__recvmmsg_unpoison(struct uv__mmsghdr* mmsg, int rc) {
struct uv__mmsghdr* m;
struct msghdr* h;
struct iovec* v;
size_t j;
int i;
for (i = 0; i < rc; i++) {
m = mmsg + i;
uv__msan_unpoison(m, sizeof(*m));
h = &m->msg_hdr;
if (h->msg_name != NULL)
uv__msan_unpoison(h->msg_name, h->msg_namelen);
if (h->msg_iov != NULL)
uv__msan_unpoison(h->msg_iov, h->msg_iovlen * sizeof(*h->msg_iov));
for (j = 0; j < h->msg_iovlen; j++) {
v = h->msg_iov + j;
uv__msan_unpoison(v->iov_base, v->iov_len);
}
if (h->msg_control != NULL)
uv__msan_unpoison(h->msg_control, h->msg_controllen);
}
}
int uv__recvmmsg(int fd, struct uv__mmsghdr* mmsg, unsigned int vlen) { int uv__recvmmsg(int fd, struct uv__mmsghdr* mmsg, unsigned int vlen) {
#if defined(__i386__) #if defined(__i386__)
unsigned long args[5]; unsigned long args[5];
@ -1698,13 +1741,19 @@ int uv__recvmmsg(int fd, struct uv__mmsghdr* mmsg, unsigned int vlen) {
/* socketcall() raises EINVAL when SYS_RECVMMSG is not supported. */ /* socketcall() raises EINVAL when SYS_RECVMMSG is not supported. */
rc = syscall(/* __NR_socketcall */ 102, 19 /* SYS_RECVMMSG */, args); rc = syscall(/* __NR_socketcall */ 102, 19 /* SYS_RECVMMSG */, args);
uv__recvmmsg_unpoison(mmsg, rc);
if (rc == -1) if (rc == -1)
if (errno == EINVAL) if (errno == EINVAL)
errno = ENOSYS; errno = ENOSYS;
return rc; return rc;
#elif defined(__NR_recvmmsg) #elif defined(__NR_recvmmsg)
return syscall(__NR_recvmmsg, fd, mmsg, vlen, /* flags */ 0, /* timeout */ 0); int rc;
rc = syscall(__NR_recvmmsg, fd, mmsg, vlen, /* flags */ 0, /* timeout */ 0);
uv__recvmmsg_unpoison(mmsg, rc);
return rc;
#else #else
return errno = ENOSYS, -1; return errno = ENOSYS, -1;
#endif #endif

View File

@ -357,7 +357,7 @@ int uv_pipe_chmod(uv_pipe_t* handle, int mode) {
} }
/* stat must be used as fstat has a bug on Darwin */ /* stat must be used as fstat has a bug on Darwin */
if (stat(name_buffer, &pipe_stat) == -1) { if (uv__stat(name_buffer, &pipe_stat) == -1) {
uv__free(name_buffer); uv__free(name_buffer);
return -errno; return -errno;
} }

View File

@ -40,7 +40,7 @@ int uv__random_readpath(const char* path, void* buf, size_t buflen) {
if (fd < 0) if (fd < 0)
return fd; return fd;
if (fstat(fd, &s)) { if (uv__fstat(fd, &s)) {
uv__close(fd); uv__close(fd);
return UV__ERR(errno); return UV__ERR(errno);
} }

View File

@ -113,7 +113,7 @@ static int uv__tty_is_slave(const int fd) {
} }
/* Lookup stat structure behind the file descriptor. */ /* Lookup stat structure behind the file descriptor. */
if (fstat(fd, &sb) != 0) if (uv__fstat(fd, &sb) != 0)
abort(); abort();
/* Assert character device. */ /* Assert character device. */
@ -365,7 +365,7 @@ uv_handle_type uv_guess_handle(uv_file file) {
if (isatty(file)) if (isatty(file))
return UV_TTY; return UV_TTY;
if (fstat(file, &s)) { if (uv__fstat(file, &s)) {
#if defined(__PASE__) #if defined(__PASE__)
/* On ibmi receiving RST from TCP instead of FIN immediately puts fd into /* On ibmi receiving RST from TCP instead of FIN immediately puts fd into
* an error state. fstat will return EINVAL, getsockname will also return * an error state. fstat will return EINVAL, getsockname will also return

View File

@ -344,6 +344,7 @@ long int process_output_size(process_info_t *p) {
/* Size of the p->stdout_file */ /* Size of the p->stdout_file */
struct stat buf; struct stat buf;
memset(&buf, 0, sizeof(buf));
int r = fstat(fileno(p->stdout_file), &buf); int r = fstat(fileno(p->stdout_file), &buf);
if (r < 0) { if (r < 0) {
return -1; return -1;

View File

@ -1239,6 +1239,8 @@ static int test_sendfile(void (*setup)(int), uv_fs_cb cb, off_t expected_size) {
ASSERT(r == 0); ASSERT(r == 0);
uv_fs_req_cleanup(&close_req); uv_fs_req_cleanup(&close_req);
memset(&s1, 0, sizeof(s1));
memset(&s2, 0, sizeof(s2));
ASSERT(0 == stat("test_file", &s1)); ASSERT(0 == stat("test_file", &s1));
ASSERT(0 == stat("test_file2", &s2)); ASSERT(0 == stat("test_file2", &s2));
ASSERT(s2.st_size == expected_size); ASSERT(s2.st_size == expected_size);
@ -1413,6 +1415,7 @@ TEST_IMPL(fs_fstat) {
uv_fs_req_cleanup(&req); uv_fs_req_cleanup(&req);
#ifndef _WIN32 #ifndef _WIN32
memset(&t, 0, sizeof(t));
ASSERT(0 == fstat(file, &t)); ASSERT(0 == fstat(file, &t));
ASSERT(0 == uv_fs_fstat(NULL, &req, file, NULL)); ASSERT(0 == uv_fs_fstat(NULL, &req, file, NULL));
ASSERT(req.result == 0); ASSERT(req.result == 0);
@ -3707,9 +3710,9 @@ static void test_fs_partial(int doread) {
ctx.doread = doread; ctx.doread = doread;
ctx.interval = 1000; ctx.interval = 1000;
ctx.size = sizeof(test_buf) * iovcount; ctx.size = sizeof(test_buf) * iovcount;
ctx.data = malloc(ctx.size); ctx.data = calloc(ctx.size, 1);
ASSERT_NOT_NULL(ctx.data); ASSERT_NOT_NULL(ctx.data);
buffer = malloc(ctx.size); buffer = calloc(ctx.size, 1);
ASSERT_NOT_NULL(buffer); ASSERT_NOT_NULL(buffer);
for (index = 0; index < iovcount; ++index) for (index = 0; index < iovcount; ++index)

View File

@ -22,6 +22,7 @@
#include "uv.h" #include "uv.h"
#include "task.h" #include "task.h"
#include <string.h>
TEST_IMPL(pipe_set_chmod) { TEST_IMPL(pipe_set_chmod) {
uv_pipe_t pipe_handle; uv_pipe_t pipe_handle;
@ -48,7 +49,8 @@ TEST_IMPL(pipe_set_chmod) {
} }
ASSERT(r == 0); ASSERT(r == 0);
#ifndef _WIN32 #ifndef _WIN32
stat(TEST_PIPENAME, &stat_buf); memset(&stat_buf, 0, sizeof(stat_buf));
ASSERT_EQ(0, stat(TEST_PIPENAME, &stat_buf));
ASSERT(stat_buf.st_mode & S_IRUSR); ASSERT(stat_buf.st_mode & S_IRUSR);
ASSERT(stat_buf.st_mode & S_IRGRP); ASSERT(stat_buf.st_mode & S_IRGRP);
ASSERT(stat_buf.st_mode & S_IROTH); ASSERT(stat_buf.st_mode & S_IROTH);