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:
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

View File

@ -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")

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) {
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)

View File

@ -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);

View File

@ -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

View File

@ -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))

View File

@ -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

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 */
if (stat(name_buffer, &pipe_stat) == -1) {
if (uv__stat(name_buffer, &pipe_stat) == -1) {
uv__free(name_buffer);
return -errno;
}

View File

@ -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);
}

View File

@ -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

View File

@ -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;

View File

@ -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)

View File

@ -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);