diff --git a/.github/workflows/sanitizer.yml b/.github/workflows/sanitizer.yml index 3c39117f..d4b04aa1 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,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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 640f8a19..358bcd6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") diff --git a/src/unix/aix.c b/src/unix/aix.c index 6a013d43..f413f108 100644 --- a/src/unix/aix.c +++ b/src/unix/aix.c @@ -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) diff --git a/src/unix/fs.c b/src/unix/fs.c index 62e88f42..3ec4b6ae 100644 --- a/src/unix/fs.c +++ b/src/unix/fs.c @@ -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); diff --git a/src/unix/internal.h b/src/unix/internal.h index 806bd672..9f32ebb0 100644 --- a/src/unix/internal.h +++ b/src/unix/internal.h @@ -33,6 +33,22 @@ #include #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(__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 diff --git a/src/unix/kqueue.c b/src/unix/kqueue.c index c7fbba94..9e34a684 100644 --- a/src/unix/kqueue.c +++ b/src/unix/kqueue.c @@ -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)) diff --git a/src/unix/linux.c b/src/unix/linux.c index 22c06fef..13e7661f 100644 --- a/src/unix/linux.c +++ b/src/unix/linux.c @@ -34,16 +34,18 @@ #include #include +#include #include #include #include #include #include +#include #include #include -#include -#include +#include #include +#include #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 diff --git a/src/unix/pipe.c b/src/unix/pipe.c index e8cfa148..610b09b3 100644 --- a/src/unix/pipe.c +++ b/src/unix/pipe.c @@ -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; } diff --git a/src/unix/random-devurandom.c b/src/unix/random-devurandom.c index 05e52a56..d6336f2c 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 4c220e56..62328087 100644 --- a/src/unix/tty.c +++ b/src/unix/tty.c @@ -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 diff --git a/test/runner-unix.c b/test/runner-unix.c index c165aab9..09191dbd 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 e49eaa9b..8d3a388c 100644 --- a/test/test-fs.c +++ b/test/test-fs.c @@ -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) diff --git a/test/test-pipe-set-fchmod.c b/test/test-pipe-set-fchmod.c index 91e47665..985f4ba2 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);