Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions libc-bottom-half/mman/mman.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
27 changes: 17 additions & 10 deletions libc-bottom-half/sources/accept-wasip1.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
}
5 changes: 5 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
54 changes: 54 additions & 0 deletions test/src/mmap-pread-fail.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#include "test.h"
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

#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;
}
117 changes: 117 additions & 0 deletions test/src/sockets-accept-null-addr.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#include "test.h"
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>

#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;
}