Skip to content
Open
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
12 changes: 11 additions & 1 deletion lib/wasix/src/syscalls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,10 +340,18 @@ where
if let Poll::Ready(res) = Pin::new(&mut self.pinned_work).poll(cx) {
return Poll::Ready(Ok(res));
}
if let Err(err) = WasiEnv::do_pending_link_operations(self.ctx, false) {
return Poll::Ready(Err(err));
}
if let Some(signals) = self.ctx.data().thread.pop_signals_or_subscribe(cx.waker()) {
let only_sigwakeup = signals.iter().all(|sig| matches!(sig, Signal::Sigwakeup));
if let Err(err) = WasiEnv::process_signals_internal(self.ctx, signals) {
return Poll::Ready(Err(err));
}
if only_sigwakeup {
self.ctx.data().thread.signals_subscribe(cx.waker());
return Poll::Pending;
}
return Poll::Ready(Ok(Err(Errno::Intr)));
}
Poll::Pending
Expand Down Expand Up @@ -381,7 +389,9 @@ where
return Poll::Ready(Ok(res));
}

WasiEnv::do_pending_link_operations(self.ctx, false);
if let Err(err) = WasiEnv::do_pending_link_operations(self.ctx, false) {
return Poll::Ready(Err(err));
}

let env = self.ctx.data();
if let Some(forced_exit) = env.thread.try_join() {
Expand Down
599 changes: 292 additions & 307 deletions lib/wasix/src/syscalls/wasi/fd_read.rs

Large diffs are not rendered by default.

50 changes: 49 additions & 1 deletion lib/wasix/tests/wasm_tests/fd_tests.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,59 @@
use super::{run_build_script, run_wasm};
use super::{run_build_script, run_wasm, run_wasm_with_stdin};
use wasmer_wasix::Pipe;

#[test]
fn test_fd_allocate() {
let wasm = run_build_script(file!(), "fd-allocate").unwrap();
run_wasm(&wasm, wasm.parent().unwrap()).unwrap();
}

/// Regression test for fd_read blocking DL operations.
///
/// One WASM thread blocks inside `fd_read` on stdin (which never produces
/// data). While that thread is parked, the main WASM thread must be able to
/// call `dlopen` and load a shared library without deadlocking. Before the
/// fix, `fd_read` held a lock that prevented DL operations from proceeding.
#[test]
fn test_stdin_read_does_not_block_dlopen() {
let wasm = run_build_script(file!(), "stdin-dlopen-race").unwrap();

// Keep the write end alive so stdin remains blocked for the whole guest
// run; the guest exits explicitly after proving dlopen works.
let (_pipe_tx, pipe_rx) = Pipe::channel();

let result = run_wasm_with_stdin(&wasm, wasm.parent().unwrap(), Box::new(pipe_rx)).unwrap();
Comment thread
Arshia001 marked this conversation as resolved.

let stdout = String::from_utf8_lossy(&result.stdout);
assert_eq!(
stdout.trim(),
"reader_ready\ndlopen_succeeded_after_reader_ready\nside_value=42\nsequence_ok",
"stderr: {}",
String::from_utf8_lossy(&result.stderr)
);
assert_eq!(result.exit_code, Some(0));
}

#[test]
fn test_stdin_read_is_interrupted_by_signal() {
let wasm = run_build_script(file!(), "stdin-signal-eintr").unwrap();

// Keep stdin blocked for the duration of the test; the guest reader thread
// should wake because of a signal and return EINTR rather than waiting for
// input or EOF.
let (_pipe_tx, pipe_rx) = Pipe::channel();

let result = run_wasm_with_stdin(&wasm, wasm.parent().unwrap(), Box::new(pipe_rx)).unwrap();

let stdout = String::from_utf8_lossy(&result.stdout);
assert_eq!(
stdout.trim(),
"reader_ready\nsignal_sent\nhandler_called\nread_errno=EINTR\nsequence_ok",
"stderr: {}",
String::from_utf8_lossy(&result.stderr)
);
assert_eq!(result.exit_code, Some(0));
}

#[test]
fn test_fd_open_readonly() {
let wasm = run_build_script(file!(), "fd-open-readonly").unwrap();
Expand Down
10 changes: 10 additions & 0 deletions lib/wasix/tests/wasm_tests/fd_tests/stdin-dlopen-race/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail

export WASIXCC_PIC=yes

# Compile the shared library
$CC -shared side.c -o libside.so

# Compile the main executable
$CC main.c -o main
Comment thread
Arshia001 marked this conversation as resolved.
71 changes: 71 additions & 0 deletions lib/wasix/tests/wasm_tests/fd_tests/stdin-dlopen-race/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#include <dlfcn.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static int reader_ready = 0;

typedef int (*side_value_t)(void);

static void* reader_thread(void* arg) {
(void)arg;

/* Signal to the main thread that we are about to enter fd_read. */
pthread_mutex_lock(&mutex);
reader_ready = 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);

/* Block in fd_read on stdin. The host provides a pipe whose write end is
* never written to, so this call parks indefinitely. The process will be
* terminated by main() returning before this ever unblocks. */
char buf[64];
read(STDIN_FILENO, buf, sizeof(buf));

return NULL;
}

int main(void) {
pthread_t t;
pthread_create(&t, NULL, reader_thread, NULL);

/* Wait until the reader thread has set reader_ready (it is about to call
* read(), if it hasn't already). */
pthread_mutex_lock(&mutex);
while (!reader_ready) pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);

printf("reader_ready\n");

/* Give the reader thread time to actually enter the blocking read before
* attempting dlopen, so this test exercises the intended race reliably. */
sleep(1);

/* Load a shared library while the other thread is blocked in fd_read.
* Before the fix this would deadlock because fd_read held a lock that
* the DL subsystem also needed. */
void* handle = dlopen("libside.so", RTLD_NOW | RTLD_LOCAL);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}

printf("dlopen_succeeded_after_reader_ready\n");

side_value_t side_value = (side_value_t)dlsym(handle, "side_value");
if (!side_value) {
fprintf(stderr, "dlsym failed: %s\n", dlerror());
dlclose(handle);
return 1;
}

printf("side_value=%d\n", side_value());
printf("sequence_ok\n");
dlclose(handle);

fflush(stdout);
_Exit(0);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
int side_value(void) { return 42; }
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail

$CC main.c -o main
94 changes: 94 additions & 0 deletions lib/wasix/tests/wasm_tests/fd_tests/stdin-signal-eintr/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static int reader_ready = 0;
static volatile sig_atomic_t handler_called = 0;
static int read_errno = 0;

static void signal_handler(int sig) {
(void)sig;
handler_called = 1;
}

static void* reader_thread(void* arg) {
(void)arg;

pthread_mutex_lock(&mutex);
reader_ready = 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);

char buf[64];
ssize_t ret = read(STDIN_FILENO, buf, sizeof(buf));
if (ret >= 0) {
fprintf(stderr, "read unexpectedly succeeded: %zd\n", ret);
return (void*)1;
}

read_errno = errno;
return NULL;
}

int main(void) {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGUSR1, &sa, NULL) != 0) {
perror("sigaction");
return 1;
}

pthread_t t;
if (pthread_create(&t, NULL, reader_thread, NULL) != 0) {
perror("pthread_create");
return 1;
}

pthread_mutex_lock(&mutex);
while (!reader_ready) pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);

printf("reader_ready\n");

// Give the reader thread time to actually enter the blocking read.
sleep(1);

if (pthread_kill(t, SIGUSR1) != 0) {
perror("pthread_kill");
return 1;
}

printf("signal_sent\n");

void* thread_ret = NULL;
if (pthread_join(t, &thread_ret) != 0) {
perror("pthread_join");
return 1;
}
if (thread_ret != NULL) {
return 1;
}

if (!handler_called) {
fprintf(stderr, "signal handler was not called\n");
return 1;
}
printf("handler_called\n");

if (read_errno != EINTR) {
fprintf(stderr, "expected EINTR, got %d\n", read_errno);
return 1;
}

printf("read_errno=EINTR\n");
printf("sequence_ok\n");
return 0;
}
Loading
Loading