diff --git a/.github/workflows/sanitizer.yml b/.github/workflows/sanitizer.yml index 3c39117faa3..9580bc49cba 100644 --- a/.github/workflows/sanitizer.yml +++ b/.github/workflows/sanitizer.yml @@ -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,4 @@ 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 + diff --git a/CMakeLists.txt b/CMakeLists.txt index 5bd2c014d52..c9c72d8866b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,13 +30,20 @@ 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") @@ -44,6 +51,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") diff --git a/src/unix/aix.c b/src/unix/aix.c index 8ed982494c2..efe439503c4 100644 --- a/src/unix/aix.c +++ b/src/unix/aix.c @@ -423,7 +423,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) @@ -888,7 +888,7 @@ char** uv_setup_args(int argc, char** argv) { size = sizeof(exepath); if (uv__search_path(argv[0], exepath, &size) == 0) { uv_once(&process_title_mutex_once, init_process_title_mutex_once); - uv_mutex_lock(&process_title_mutex); + uv_mutex_lock(&process_title_mutex); original_exepath = uv__strdup(exepath); uv_mutex_unlock(&process_title_mutex); } diff --git a/src/unix/fs.c b/src/unix/fs.c index e24fddb2319..c1357d5a5bd 100644 --- a/src/unix/fs.c +++ b/src/unix/fs.c @@ -576,7 +576,7 @@ static ssize_t uv__fs_read(uv_fs_t* req) { 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; } @@ -768,7 +768,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)) { @@ -1349,7 +1349,7 @@ static ssize_t uv__fs_copyfile(uv_fs_t* req) { srcfd = fs_req.result; /* Get the source file's mode. */ - if (fstat(srcfd, &src_statsbuf)) { + if (uv__fstat(srcfd, &src_statsbuf)) { err = UV__ERR(errno); goto out; } @@ -1378,7 +1378,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; } @@ -1657,7 +1657,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); @@ -1673,7 +1673,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); @@ -1689,7 +1689,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); diff --git a/src/unix/internal.h b/src/unix/internal.h index 9b6140d5a31..cd30cd4b95b 100644 --- a/src/unix/internal.h +++ b/src/unix/internal.h @@ -33,6 +33,23 @@ #include #include +#include +#include + +#define uv__msan_unpoison(p, n) \ + do { \ + (void) (p); \ + (void) (n); \ + } while (0) + +#if defined(__has_feature) +# if __has_feature(memory_sanitizer) +# include +# undef uv__msan_unpoison +# define uv__msan_unpoison __msan_unpoison +# endif +#endif + #if defined(__linux__) # include "linux-syscalls.h" #endif /* __linux__ */ @@ -310,6 +327,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; +} + typedef int (*uv__peersockfunc)(int, struct sockaddr*, socklen_t*); int uv__getsockpeername(const uv_handle_t* handle, diff --git a/src/unix/kqueue.c b/src/unix/kqueue.c index df396a30f40..a9b6994ecac 100644 --- a/src/unix/kqueue.c +++ b/src/unix/kqueue.c @@ -509,7 +509,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)) diff --git a/src/unix/linux-syscalls.c b/src/unix/linux-syscalls.c index 5071cd56d1f..7e1dd9d8cb4 100644 --- a/src/unix/linux-syscalls.c +++ b/src/unix/linux-syscalls.c @@ -20,11 +20,15 @@ */ #include "linux-syscalls.h" +#include "internal.h" +#include #include #include #include #include #include +#include + #if defined(__arm__) # if defined(__thumb__) || defined(__ARM_EABI__) @@ -167,6 +171,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]; @@ -180,13 +213,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 @@ -250,15 +289,27 @@ 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 } -ssize_t uv__getrandom(void* buf, size_t buflen, unsigned flags) { + 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 } diff --git a/src/unix/linux-syscalls.h b/src/unix/linux-syscalls.h index b4d9082d46f..2927cf97842 100644 --- a/src/unix/linux-syscalls.h +++ b/src/unix/linux-syscalls.h @@ -28,6 +28,7 @@ #include #include + struct uv__statx_timestamp { int64_t tv_sec; uint32_t tv_nsec; diff --git a/src/unix/pipe.c b/src/unix/pipe.c index 0e3e7116b3c..4d873d1570a 100644 --- a/src/unix/pipe.c +++ b/src/unix/pipe.c @@ -189,7 +189,7 @@ void uv_pipe_connect(uv_connect_t* req, size_t name_len; name_len = strlen(name); - + if (name_len > sizeof(saddr.sun_path) - 1) { err = -ENAMETOOLONG; goto out; @@ -371,7 +371,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; } diff --git a/src/unix/random-devurandom.c b/src/unix/random-devurandom.c index 05e52a56a36..d6336f2c98c 100644 --- a/src/unix/random-devurandom.c +++ b/src/unix/random-devurandom.c @@ -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); } diff --git a/src/unix/tty.c b/src/unix/tty.c index 37467b8e6d2..c5360826256 100644 --- a/src/unix/tty.c +++ b/src/unix/tty.c @@ -52,7 +52,7 @@ */ static int isreallyatty(int file) { int rc; - + rc = !ioctl(file, TXISATTY + 0x81, NULL); if (!rc && errno != EBADF) errno = ENOTTY; @@ -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. */ @@ -360,7 +360,7 @@ uv_handle_type uv_guess_handle(uv_os_fd_t 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 diff --git a/test/runner-unix.c b/test/runner-unix.c index c165aab9305..09191dbdaa1 100644 --- a/test/runner-unix.c +++ b/test/runner-unix.c @@ -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; diff --git a/test/test-fs.c b/test/test-fs.c index 68d3b875edd..06965d840a9 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -317,7 +317,7 @@ static void chown_root_cb(uv_fs_t* req) { ASSERT(req->result == UV_EINVAL); # elif defined(__PASE__) /* On IBMi PASE, there is no root user. uid 0 is user qsecofr. - * User may grant qsecofr's privileges, including changing + * User may grant qsecofr's privileges, including changing * the file's ownership to uid 0. */ ASSERT(req->result == 0 || req->result == UV_EPERM); @@ -1290,6 +1290,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); @@ -1462,6 +1464,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); @@ -3931,9 +3934,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) diff --git a/test/test-pipe-set-fchmod.c b/test/test-pipe-set-fchmod.c index 91e476652e0..985f4ba2eba 100644 --- a/test/test-pipe-set-fchmod.c +++ b/test/test-pipe-set-fchmod.c @@ -22,6 +22,7 @@ #include "uv.h" #include "task.h" +#include 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);