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:
parent
73b0c1f947
commit
acfe668ecb
29
.github/workflows/sanitizer.yml
vendored
29
.github/workflows/sanitizer.yml
vendored
@ -14,7 +14,7 @@ on:
|
||||
|
||||
jobs:
|
||||
sanitizers:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup
|
||||
@ -22,6 +22,25 @@ jobs:
|
||||
sudo apt-get install ninja-build
|
||||
- name: 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
|
||||
run: |
|
||||
mkdir build-tsan
|
||||
@ -31,11 +50,3 @@ jobs:
|
||||
continue-on-error: true # currently permit failures
|
||||
run: |
|
||||
./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
|
||||
|
||||
@ -36,13 +36,19 @@ if(QEMU)
|
||||
add_definitions(-D__QEMU__=1)
|
||||
endif()
|
||||
|
||||
# Note: these are mutually exclusive.
|
||||
option(ASAN "Enable AddressSanitizer (ASan)" OFF)
|
||||
option(MSAN "Enable MemorySanitizer (MSan)" OFF)
|
||||
option(TSAN "Enable ThreadSanitizer (TSan)" OFF)
|
||||
|
||||
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.")
|
||||
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)
|
||||
add_definitions(-D__ASAN__=1)
|
||||
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")
|
||||
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)
|
||||
add_definitions(-D__TSAN__=1)
|
||||
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -fsanitize=thread")
|
||||
|
||||
@ -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) {
|
||||
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 */
|
||||
|
||||
if (statbuf.st_type == VDIR)
|
||||
|
||||
@ -516,7 +516,7 @@ done:
|
||||
if (result == -1 && errno == EOPNOTSUPP) {
|
||||
struct stat buf;
|
||||
ssize_t rc;
|
||||
rc = fstat(req->file, &buf);
|
||||
rc = uv__fstat(req->file, &buf);
|
||||
if (rc == 0 && S_ISDIR(buf.st_mode)) {
|
||||
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. */
|
||||
struct stat st;
|
||||
int ret;
|
||||
ret = lstat(req->path, &st);
|
||||
ret = uv__lstat(req->path, &st);
|
||||
if (ret != 0)
|
||||
return -1;
|
||||
if (!S_ISLNK(st.st_mode)) {
|
||||
@ -1281,7 +1281,7 @@ static ssize_t uv__fs_copyfile(uv_fs_t* req) {
|
||||
return srcfd;
|
||||
|
||||
/* Get the source file's mode. */
|
||||
if (fstat(srcfd, &src_statsbuf)) {
|
||||
if (uv__fstat(srcfd, &src_statsbuf)) {
|
||||
err = UV__ERR(errno);
|
||||
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. */
|
||||
if ((req->flags & UV_FS_COPYFILE_EXCL) == 0) {
|
||||
/* Get the destination file's mode. */
|
||||
if (fstat(dstfd, &dst_statsbuf)) {
|
||||
if (uv__fstat(dstfd, &dst_statsbuf)) {
|
||||
err = UV__ERR(errno);
|
||||
goto out;
|
||||
}
|
||||
@ -1588,7 +1588,7 @@ static int uv__fs_stat(const char *path, uv_stat_t *buf) {
|
||||
if (ret != UV_ENOSYS)
|
||||
return ret;
|
||||
|
||||
ret = stat(path, &pbuf);
|
||||
ret = uv__stat(path, &pbuf);
|
||||
if (ret == 0)
|
||||
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)
|
||||
return ret;
|
||||
|
||||
ret = lstat(path, &pbuf);
|
||||
ret = uv__lstat(path, &pbuf);
|
||||
if (ret == 0)
|
||||
uv__to_stat(&pbuf, buf);
|
||||
|
||||
@ -1620,7 +1620,7 @@ static int uv__fs_fstat(int fd, uv_stat_t *buf) {
|
||||
if (ret != UV_ENOSYS)
|
||||
return ret;
|
||||
|
||||
ret = fstat(fd, &pbuf);
|
||||
ret = uv__fstat(fd, &pbuf);
|
||||
if (ret == 0)
|
||||
uv__to_stat(&pbuf, buf);
|
||||
|
||||
|
||||
@ -33,6 +33,22 @@
|
||||
#include <stdio.h>
|
||||
#include <errno.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__)
|
||||
# define inline __inline
|
||||
@ -341,6 +357,36 @@ UV_UNUSED(static char* uv__basename_r(const char* path)) {
|
||||
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__)
|
||||
int uv__inotify_fork(uv_loop_t* loop, void* old_watchers);
|
||||
ssize_t
|
||||
|
||||
@ -529,7 +529,7 @@ int uv_fs_event_start(uv_fs_event_t* handle,
|
||||
handle->realpath_len = 0;
|
||||
handle->cf_flags = flags;
|
||||
|
||||
if (fstat(fd, &statbuf))
|
||||
if (uv__fstat(fd, &statbuf))
|
||||
goto fallback;
|
||||
/* FSEvents works only with directories */
|
||||
if (!(statbuf.st_mode & S_IFDIR))
|
||||
|
||||
@ -34,16 +34,18 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/sysinfo.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if defined(__arm__)
|
||||
# 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
|
||||
return errno = ENOSYS, -1;
|
||||
#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
|
||||
}
|
||||
|
||||
@ -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
|
||||
return errno = ENOSYS, -1;
|
||||
#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
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
#if defined(__i386__)
|
||||
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. */
|
||||
rc = syscall(/* __NR_socketcall */ 102, 19 /* SYS_RECVMMSG */, args);
|
||||
uv__recvmmsg_unpoison(mmsg, rc);
|
||||
if (rc == -1)
|
||||
if (errno == EINVAL)
|
||||
errno = ENOSYS;
|
||||
|
||||
return rc;
|
||||
#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
|
||||
return errno = ENOSYS, -1;
|
||||
#endif
|
||||
|
||||
@ -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 */
|
||||
if (stat(name_buffer, &pipe_stat) == -1) {
|
||||
if (uv__stat(name_buffer, &pipe_stat) == -1) {
|
||||
uv__free(name_buffer);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ int uv__random_readpath(const char* path, void* buf, size_t buflen) {
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
|
||||
if (fstat(fd, &s)) {
|
||||
if (uv__fstat(fd, &s)) {
|
||||
uv__close(fd);
|
||||
return UV__ERR(errno);
|
||||
}
|
||||
|
||||
@ -113,7 +113,7 @@ static int uv__tty_is_slave(const int fd) {
|
||||
}
|
||||
|
||||
/* Lookup stat structure behind the file descriptor. */
|
||||
if (fstat(fd, &sb) != 0)
|
||||
if (uv__fstat(fd, &sb) != 0)
|
||||
abort();
|
||||
|
||||
/* Assert character device. */
|
||||
@ -365,7 +365,7 @@ uv_handle_type uv_guess_handle(uv_file file) {
|
||||
if (isatty(file))
|
||||
return UV_TTY;
|
||||
|
||||
if (fstat(file, &s)) {
|
||||
if (uv__fstat(file, &s)) {
|
||||
#if defined(__PASE__)
|
||||
/* On ibmi receiving RST from TCP instead of FIN immediately puts fd into
|
||||
* an error state. fstat will return EINVAL, getsockname will also return
|
||||
|
||||
@ -344,6 +344,7 @@ long int process_output_size(process_info_t *p) {
|
||||
/* Size of the p->stdout_file */
|
||||
struct stat buf;
|
||||
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
int r = fstat(fileno(p->stdout_file), &buf);
|
||||
if (r < 0) {
|
||||
return -1;
|
||||
|
||||
@ -1239,6 +1239,8 @@ static int test_sendfile(void (*setup)(int), uv_fs_cb cb, off_t expected_size) {
|
||||
ASSERT(r == 0);
|
||||
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_file2", &s2));
|
||||
ASSERT(s2.st_size == expected_size);
|
||||
@ -1413,6 +1415,7 @@ TEST_IMPL(fs_fstat) {
|
||||
uv_fs_req_cleanup(&req);
|
||||
|
||||
#ifndef _WIN32
|
||||
memset(&t, 0, sizeof(t));
|
||||
ASSERT(0 == fstat(file, &t));
|
||||
ASSERT(0 == uv_fs_fstat(NULL, &req, file, NULL));
|
||||
ASSERT(req.result == 0);
|
||||
@ -3707,9 +3710,9 @@ static void test_fs_partial(int doread) {
|
||||
ctx.doread = doread;
|
||||
ctx.interval = 1000;
|
||||
ctx.size = sizeof(test_buf) * iovcount;
|
||||
ctx.data = malloc(ctx.size);
|
||||
ctx.data = calloc(ctx.size, 1);
|
||||
ASSERT_NOT_NULL(ctx.data);
|
||||
buffer = malloc(ctx.size);
|
||||
buffer = calloc(ctx.size, 1);
|
||||
ASSERT_NOT_NULL(buffer);
|
||||
|
||||
for (index = 0; index < iovcount; ++index)
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
|
||||
#include "uv.h"
|
||||
#include "task.h"
|
||||
#include <string.h>
|
||||
|
||||
TEST_IMPL(pipe_set_chmod) {
|
||||
uv_pipe_t pipe_handle;
|
||||
@ -48,7 +49,8 @@ TEST_IMPL(pipe_set_chmod) {
|
||||
}
|
||||
ASSERT(r == 0);
|
||||
#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_IRGRP);
|
||||
ASSERT(stat_buf.st_mode & S_IROTH);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user