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
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ $(BUILD_DIR)/test-tgkill-directed: tests/test-tgkill-directed.c | $(BUILD_DIR)
@echo " CROSS $< (with -lpthread)"
$(Q)$(CROSS_COMPILE)gcc -D_GNU_SOURCE -static -O2 -o $@ $< -lpthread

# test-sigtimedwait uses a sender pthread to verify thread-directed waits.
$(BUILD_DIR)/test-sigtimedwait: tests/test-sigtimedwait.c | $(BUILD_DIR)
@echo " CROSS $< (with -lpthread)"
$(Q)$(CROSS_COMPILE)gcc -D_GNU_SOURCE -static -O2 -o $@ $< -lpthread

# test-osync-requeue drives a raw FUTEX_REQUEUE against a plain-FUTEX_WAIT
# waiter (musl unlock_requeue pattern) to guard the os_sync wake-at-source
# degradation; needs -lpthread.
Expand Down
1 change: 1 addition & 0 deletions scripts/check-syscall-coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"eventfd2": {"eventfd"},
"rt_sigaction": {"sigaction"},
"rt_sigprocmask": {"sigprocmask"},
"rt_sigtimedwait": {"sigwait", "sigwaitinfo", "sigtimedwait"},
"signalfd4": {"signalfd"},
}

Expand Down
1 change: 1 addition & 0 deletions src/syscall/abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
#define SYS_rt_sigaction 134
#define SYS_rt_sigprocmask 135
#define SYS_rt_sigpending 136
#define SYS_rt_sigtimedwait 137
#define SYS_rt_sigqueueinfo 138
#define SYS_rt_sigreturn 139
#define SYS_setpriority 140
Expand Down
1 change: 1 addition & 0 deletions src/syscall/dispatch.tbl
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ SYS_rt_sigsuspend sc_rt_sigsuspend 1
SYS_rt_sigaction sc_rt_sigaction 1
SYS_rt_sigprocmask sc_rt_sigprocmask 1
SYS_rt_sigpending sc_rt_sigpending 0
SYS_rt_sigtimedwait sc_rt_sigtimedwait 1
SYS_rt_sigreturn sc_rt_sigreturn 1
SYS_rt_sigqueueinfo sc_rt_sigqueueinfo 1
SYS_rt_tgsigqueueinfo sc_rt_tgsigqueueinfo 1
Expand Down
157 changes: 156 additions & 1 deletion src/syscall/signal.c
Original file line number Diff line number Diff line change
Expand Up @@ -1332,7 +1332,162 @@ int64_t signal_rt_sigpending(guest_t *g, uint64_t set_gva, uint64_t sigsetsize)
return 0;
}

/* sigaltstack. */
/* rt_sigtimedwait. */

/* Try to consume one signal from the set under sig_lock.
* Returns the signal number if one was found and dequeued, 0 otherwise.
* Populates *info_out with the queued siginfo metadata.
*/
static int sigtimedwait_try_dequeue(uint64_t mask, signal_rt_info_t *info_out)
{
pthread_mutex_lock(&sig_lock);

/* Private (thread-directed) set first, then shared (process-directed),
* matching Linux dequeue_signal() priority.
*/
signal_pending_t *tp = current_thread ? &current_thread->tpending : NULL;
uint64_t thread_m = tp ? (tp->pending & mask) : 0;
uint64_t shared_m = sig_state.shared.pending & mask;

if ((thread_m | shared_m) == 0) {
pthread_mutex_unlock(&sig_lock);
return 0;
}

int signum;
signal_pending_t *src;
if (thread_m) {
signum = bit_ctz64(thread_m) + 1;
src = tp;
} else {
signum = bit_ctz64(shared_m) + 1;
src = &sig_state.shared;
}

/* Dequeue: same logic as signal_deliver. */
if (signum >= LINUX_SIGRTMIN) {
signal_rt_dequeue_locked(src, signum, info_out);
} else {
*info_out = signal_standard_peek_locked(src, signum);
src->std_info_valid[signum - 1] = false;
src->pending &= ~sig_bit(signum);
}
refresh_pending_hint_locked();

pthread_mutex_unlock(&sig_lock);
return signum;
}

int64_t signal_rt_sigtimedwait(guest_t *g,
uint64_t set_gva,
uint64_t info_gva,
uint64_t timeout_gva,
uint64_t sigsetsize)
{
if (sigsetsize != 8)
return -LINUX_EINVAL;
if (!set_gva)
return -LINUX_EFAULT;

uint64_t mask;
if (guest_read_small(g, set_gva, &mask, sizeof(mask)) < 0)
return -LINUX_EFAULT;

/* SIGKILL and SIGSTOP cannot be caught or waited for. Remove them from
* the wait mask silently, matching Linux do_sigtimedwait behavior.
*/
uint64_t unmaskable = sig_bit(LINUX_SIGKILL) | sig_bit(LINUX_SIGSTOP);
mask &= ~unmaskable;

/* Determine deadline: NULL timeout_gva means block indefinitely.
* Zero timespec means poll once.
*/
bool has_timeout = (timeout_gva != 0);
int64_t remaining_ns = 0;

if (has_timeout) {
linux_timespec_t lts;
if (guest_read_small(g, timeout_gva, &lts, sizeof(lts)) < 0)
return -LINUX_EFAULT;
/* Negative tv_sec is invalid (matches kernel hrtimer validation). */
if (lts.tv_sec < 0 || lts.tv_nsec < 0 || lts.tv_nsec >= 1000000000LL)
return -LINUX_EINVAL;
/* Saturate to a large positive to avoid overflow. */
if (lts.tv_sec > (INT64_MAX / 1000000000LL))
remaining_ns = INT64_MAX;
else
remaining_ns = lts.tv_sec * 1000000000LL + lts.tv_nsec;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The saturation check uses > instead of >=, so a guest-supplied timespec with tv_sec == INT64_MAX/1000000000 and a large tv_nsec can make tv_sec * 1000000000LL + tv_nsec exceed INT64_MAX, causing signed overflow (UB) rather than saturating to INT64_MAX.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/syscall/signal.c, line 1419:

<comment>The saturation check uses `>` instead of `>=`, so a guest-supplied timespec with tv_sec == INT64_MAX/1000000000 and a large tv_nsec can make `tv_sec * 1000000000LL + tv_nsec` exceed INT64_MAX, causing signed overflow (UB) rather than saturating to INT64_MAX.</comment>

<file context>
@@ -1332,7 +1332,162 @@ int64_t signal_rt_sigpending(guest_t *g, uint64_t set_gva, uint64_t sigsetsize)
+        if (lts.tv_sec > (INT64_MAX / 1000000000LL))
+            remaining_ns = INT64_MAX;
+        else
+            remaining_ns = lts.tv_sec * 1000000000LL + lts.tv_nsec;
+    }
+
</file context>

}

/* Poll/wait loop. */
#define SIGWAIT_CHUNK_NS 1000000LL /* 1ms chunks */

while (1) {
signal_rt_info_t info;
int signum = sigtimedwait_try_dequeue(mask, &info);
if (signum > 0) {
/* Populate guest siginfo_t if the caller wants it. */
if (info_gva) {
linux_siginfo_t si;
memset(&si, 0, sizeof(si));
si.si_signo = signum;
si.si_code = info.si_code;
si.si_pid = info.si_pid;
si.si_uid = (int32_t) info.si_uid;
si.si_value = info.si_ptr;
if (guest_write_small(g, info_gva, &si, sizeof(si)) < 0)
return -LINUX_EFAULT;
}
return signum;
}

/* For a zero timeout (poll-once), bail immediately. */
if (has_timeout && remaining_ns <= 0)
return -LINUX_EAGAIN;

/* Exit if the process is tearing down. */
if (proc_exit_group_requested())
return -LINUX_EINTR;

/* If a non-waited signal is pending, return -EINTR so the caller's
* library (musl/glibc) can restart or propagate.
*/
uint64_t *blocked = thread_blocked_ptr();
uint64_t deliverable;
pthread_mutex_lock(&sig_lock);
deliverable = self_pending_locked() & ~*blocked & ~mask;
pthread_mutex_unlock(&sig_lock);
if (deliverable)
return -LINUX_EINTR;

/* Sleep one chunk, then recheck. */
int64_t sleep_ns =
has_timeout ? (remaining_ns < SIGWAIT_CHUNK_NS ? remaining_ns
: SIGWAIT_CHUNK_NS)
: SIGWAIT_CHUNK_NS;
struct timespec req = {
.tv_sec = sleep_ns / 1000000000LL,
.tv_nsec = sleep_ns % 1000000000LL,
};
struct timespec rem = {0};
if (nanosleep(&req, &rem) < 0) {
/* Host EINTR: account for time already slept. */
int64_t slept =
sleep_ns - (rem.tv_sec * 1000000000LL + rem.tv_nsec);
if (slept < 0)
slept = 0;
if (has_timeout)
remaining_ns -= slept;
/* Recheck immediately (will catch deliverable signal). */
continue;
}
if (has_timeout)
remaining_ns -= sleep_ns;
}

#undef SIGWAIT_CHUNK_NS
}


int64_t signal_sigaltstack(guest_t *g, uint64_t ss_gva, uint64_t old_ss_gva)
{
Expand Down
14 changes: 14 additions & 0 deletions src/syscall/signal.h
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,20 @@ int64_t signal_rt_sigsuspend(guest_t *g,
/* Handle rt_sigpending (SYS 136). */
int64_t signal_rt_sigpending(guest_t *g, uint64_t set_gva, uint64_t sigsetsize);

/* Handle rt_sigtimedwait (SYS 137).
* Synchronously consume a pending signal whose number is in *set*.
* info_gva (may be 0): if non-zero, populate the guest siginfo_t there.
* timeout_gva (may be 0): if zero, block indefinitely; otherwise block for
* at most the specified duration. Returns the signal number on success,
* -EAGAIN if the timeout expired with no matching signal, or -EINTR if an
* unrelated signal arrived while waiting.
*/
int64_t signal_rt_sigtimedwait(guest_t *g,
uint64_t set_gva,
uint64_t info_gva,
uint64_t timeout_gva,
uint64_t sigsetsize);

/* Handle sigaltstack (SYS 132). ss_gva is pointer to new stack_t (or 0),
* old_ss_gva is pointer to receive current stack_t (or 0).
*/
Expand Down
2 changes: 2 additions & 0 deletions src/syscall/syscall.c
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,8 @@ SC_FORWARD(sc_rt_sigprocmask, signal_rt_sigprocmask(g, (int) x0, x1, x2, x3))
SC_FORWARD(sc_sigaltstack, signal_sigaltstack(g, x0, x1))
SC_FORWARD(sc_rt_sigsuspend, signal_rt_sigsuspend(g, x0, x1))
SC_FORWARD(sc_rt_sigpending, signal_rt_sigpending(g, x0, x1))
SC_FORWARD(sc_rt_sigtimedwait,
signal_rt_sigtimedwait(g, x0, x1, x2, x3))

/* System info */
SC_FORWARD(sc_uname, sys_uname(g, x0))
Expand Down
Loading
Loading