diff --git a/libc-bottom-half/mman/mman.c b/libc-bottom-half/mman/mman.c index 08e1acc1f..75cb8689b 100644 --- a/libc-bottom-half/mman/mman.c +++ b/libc-bottom-half/mman/mman.c @@ -90,6 +90,10 @@ void *mmap(void *addr, size_t length, int prot, int flags, int fd, if (nread < 0) { if (errno == EINTR) continue; + // Free the header allocation; pread already set errno. + int saved_errno = errno; + free(map); + errno = saved_errno; return MAP_FAILED; } if (nread == 0) diff --git a/libc-bottom-half/sources/accept-wasip1.c b/libc-bottom-half/sources/accept-wasip1.c index 94ee1a9e8..42514c823 100644 --- a/libc-bottom-half/sources/accept-wasip1.c +++ b/libc-bottom-half/sources/accept-wasip1.c @@ -18,11 +18,16 @@ int accept(int socket, struct sockaddr *restrict addr, return -1; } - // Clear sockaddr to indicate undefined address - memset(addr, 0, *addrlen); - // might be AF_UNIX or AF_INET - addr->sa_family = AF_UNSPEC; - *addrlen = sizeof(struct sockaddr); + // POSIX allows both addr and addrlen to be NULL when the peer address is + // unused. wasip2/p3 accept paths use __wasilibc_sockaddr_validate for the + // same both-null case. + if (addr != NULL && addrlen != NULL) { + // Clear sockaddr to indicate undefined address + memset(addr, 0, *addrlen); + // might be AF_UNIX or AF_INET + addr->sa_family = AF_UNSPEC; + *addrlen = sizeof(struct sockaddr); + } return ret; } @@ -44,11 +49,13 @@ int accept4(int socket, struct sockaddr *restrict addr, return -1; } - // Clear sockaddr to indicate undefined address - memset(addr, 0, *addrlen); - // might be AF_UNIX or AF_INET - addr->sa_family = AF_UNSPEC; - *addrlen = sizeof(struct sockaddr); + if (addr != NULL && addrlen != NULL) { + // Clear sockaddr to indicate undefined address + memset(addr, 0, *addrlen); + // might be AF_UNIX or AF_INET + addr->sa_family = AF_UNSPEC; + *addrlen = sizeof(struct sockaddr); + } return ret; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2634f2598..72d753cb8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -442,6 +442,10 @@ add_wasilibc_test(clock_nanosleep.c) add_wasilibc_test(chdir.c FS) add_wasilibc_test(close.c FS) add_wasilibc_test(pread.c FS) +add_wasilibc_test(mmap-pread-fail.c FS + CFLAGS -D_WASI_EMULATED_MMAN + LDFLAGS -lwasi-emulated-mman + SHARED_LIBS "${SYSROOT_LIB}/libwasi-emulated-mman.so") add_wasilibc_test(external_env.c ENV VAR1=foo VAR2=bar) add_wasilibc_test(fadvise.c FS) # I'm not really entirely sure why this test is failing, it seemingly exits with @@ -712,6 +716,7 @@ if (NOT (WASI STREQUAL "p1")) add_wasilibc_test(sockets-nonblocking-accept-multiple.c NETWORK) add_wasilibc_test(sockets-nonblocking-shutdown.c NETWORK) add_wasilibc_test(sockets-select-connecting-rw.c NETWORK) + add_wasilibc_test(sockets-accept-null-addr.c NETWORK) add_wasilibc_test(sockets-listen-backlog.c NETWORK) add_wasilibc_test(sockets-sendto-connected.c NETWORK) add_wasilibc_test(sockets-recvfrom-connected.c NETWORK) diff --git a/test/src/mmap-pread-fail.c b/test/src/mmap-pread-fail.c new file mode 100644 index 000000000..72f9b9fb9 --- /dev/null +++ b/test/src/mmap-pread-fail.c @@ -0,0 +1,54 @@ +#include "test.h" +#include +#include +#include +#include +#include + +#define TEST(c) \ + do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ + } while (0) + +// Emulated mmap allocates a header+body block, then fills it with pread for +// file-backed maps. On pread failure it must free that allocation, return +// MAP_FAILED, and leave errno set. Without free, repeated failures grow heap +// until mmap(MAP_ANON) fails with ENOMEM. +int main(void) { + char tmp[] = "testsuite-mmap-pread-fail"; + int wr_fd; + TEST((wr_fd = open(tmp, O_WRONLY | O_CREAT | O_EXCL, 0600)) > 2); + TEST(write(wr_fd, "x", 1) == 1); + + // Single failing file-backed mmap: pread on O_WRONLY must fail. + errno = 0; + void *p = mmap(NULL, 4096, PROT_READ, MAP_PRIVATE, wr_fd, 0); + int mmap_errno = errno; + TEST(p == MAP_FAILED); + TEST(mmap_errno != 0); + + // Leak stress: many failing mmaps of 1 MiB. With free, this stays small; + // without free it exhausts wasm linear memory. + const size_t map_len = 1u << 20; + for (int i = 0; i < 256; i++) { + errno = 0; + p = mmap(NULL, map_len, PROT_READ, MAP_PRIVATE, wr_fd, 0); + mmap_errno = errno; + TEST(p == MAP_FAILED); + TEST(mmap_errno != 0); + } + + // After the failing path, MAP_ANON still works (memory was not leaked away). + errno = 0; + void *anon = + mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + TEST(anon != MAP_FAILED); + memset(anon, 0xab, 4096); + TEST(munmap(anon, 4096) == 0); + + TEST(close(wr_fd) == 0); + TEST(unlink(tmp) == 0); + return t_status; +} diff --git a/test/src/sockets-accept-null-addr.c b/test/src/sockets-accept-null-addr.c new file mode 100644 index 000000000..f5e56f3b8 --- /dev/null +++ b/test/src/sockets-accept-null-addr.c @@ -0,0 +1,117 @@ +#include "test.h" +#include +#include +#include +#include +#include +#include +#include + +#define TEST(c) \ + do { \ + errno = 0; \ + if (!(c)) \ + t_error("%s failed (errno = %d)\n", #c, errno); \ + } while (0) + +// POSIX allows accept/accept4 peer address args to both be NULL when the +// caller does not need the peer address. wasip1 previously null-dereferenced +// after a successful sock_accept on that shape. +// +// Handshake matches sockets-select-connecting-rw.c: nonblocking connect must +// complete (select + SO_ERROR) before accept/send, or send fails with +// ENOTCONN/EAGAIN and the test hangs on recv. +static void wait_connected(int cfd) { + fd_set rfds, wfds, efds; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&efds); + FD_SET(cfd, &rfds); + FD_SET(cfd, &wfds); + FD_SET(cfd, &efds); + TEST(select(cfd + 1, &rfds, &wfds, &efds, NULL) >= 0); + TEST(FD_ISSET(cfd, &wfds)); + + int err = -1; + socklen_t err_len = sizeof(err); + TEST(getsockopt(cfd, SOL_SOCKET, SO_ERROR, &err, &err_len) == 0); + TEST(err == 0); +} + +static int listen_loopback(struct sockaddr_in *addr) { + int lfd = socket(AF_INET, SOCK_STREAM, 0); + TEST(lfd >= 0); + socklen_t addr_len = sizeof(*addr); + memset(addr, 0, sizeof(*addr)); + addr->sin_family = AF_INET; + addr->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr->sin_port = 0; + TEST(bind(lfd, (struct sockaddr *)addr, sizeof(*addr)) == 0); + TEST(getsockname(lfd, (struct sockaddr *)addr, &addr_len) == 0); + TEST(listen(lfd, 1) == 0); + return lfd; +} + +static int connect_client(const struct sockaddr_in *addr) { + int cfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); + TEST(cfd >= 0); + errno = 0; + int rc = connect(cfd, (const struct sockaddr *)addr, sizeof(*addr)); + if (rc != 0 && errno != EINPROGRESS) + t_error("connect failed (rc = %d, errno = %d)\n", rc, errno); + wait_connected(cfd); + return cfd; +} + +static void check_ping(int cfd, int sfd) { + TEST(send(cfd, "ping", 4, 0) == 4); + char buf[4] = {0}; + TEST(recv(sfd, buf, sizeof(buf), 0) == 4); + TEST(memcmp(buf, "ping", 4) == 0); +} + +int main(void) { + // accept(fd, NULL, NULL) + { + struct sockaddr_in addr; + int lfd = listen_loopback(&addr); + int cfd = connect_client(&addr); + int sfd = accept(lfd, NULL, NULL); + TEST(sfd >= 0); + check_ping(cfd, sfd); + TEST(close(sfd) == 0); + TEST(close(cfd) == 0); + TEST(close(lfd) == 0); + } + + // accept4(fd, NULL, NULL, 0) + { + struct sockaddr_in addr; + int lfd = listen_loopback(&addr); + int cfd = connect_client(&addr); + int sfd = accept4(lfd, NULL, NULL, 0); + TEST(sfd >= 0); + check_ping(cfd, sfd); + TEST(close(sfd) == 0); + TEST(close(cfd) == 0); + TEST(close(lfd) == 0); + } + + // Non-null peer address path still works. + { + struct sockaddr_in addr; + int lfd = listen_loopback(&addr); + int cfd = connect_client(&addr); + struct sockaddr_storage peer; + socklen_t peer_len = sizeof(peer); + int sfd = accept(lfd, (struct sockaddr *)&peer, &peer_len); + TEST(sfd >= 0); + TEST(peer_len > 0); + check_ping(cfd, sfd); + TEST(close(sfd) == 0); + TEST(close(cfd) == 0); + TEST(close(lfd) == 0); + } + + return t_status; +}