Repository navigation
Conversation
jserv
left a comment
There was a problem hiding this comment.
Check https://github.com/sysprog21/elfuse/blob/main/CONTRIBUTING.md carefully and rework git commit messages and refine per reviews.
| SYS_nanosleep sc_nanosleep 1 | ||
| SYS_getitimer sc_getitimer 1 | ||
| SYS_setitimer sc_setitimer 1 | ||
| SYS_timer_create sc_timer_create 0 |
There was a problem hiding this comment.
None of the five timer_* entries has a test reference under tests/, and none is in INDIRECT_COVERAGE, so scripts/check-syscall-coverage.py fails make check. A small C test covering create/settime/gettime/getoverrun/delete, TIMER_ABSTIME, a nonzero overrun and SIGEV_NONE would clear the gate. It should also reject an out-of-range signal number. The CLONE_PIDFD path and its failure rollback are untested as well.
| thread_entry_t *t = current_thread; | ||
|
|
||
| pthread_mutex_lock(&sig_lock); | ||
| memset(posix_timers, 0, sizeof(posix_timers)); |
There was a problem hiding this comment.
This clears the timer slots but leaves their already-queued SI_TIMER signals in the shared pending set. Trigger: block the timer's signal, let it expire, then execve. When the new image unblocks the signal it receives a stale timer signal, and with SIG_DFL on SIGALRM that terminates it. Linux drops pending timer signals at exec (flush_itimer_signals). Remove the si_code == LINUX_SI_TIMER entries from sig_state.shared here and call refresh_pending_hint_locked().
| return -LINUX_EINVAL; | ||
| } | ||
| posix_timer_value_locked(timer, old); | ||
| timer->last_overrun = 0; |
There was a problem hiding this comment.
Re-arming resets last_overrun but leaves overruns and signal_pending alone. Trigger: let a blocked periodic timer accumulate overruns, then timer_settime it before the signal is consumed. The first expiry of the new arm is folded into the old queued signal as an overrun instead of producing its own signal. When the old signal is consumed, it reports the previous arm's count. Linux resets the accounting on set and treats the old queued signal as stale. Reset overruns here and tag the queued entry with an arm generation so a stale dequeue cannot overwrite the new state.
| posix_timer_refresh_armed_locked(); | ||
| pthread_mutex_unlock(&sig_lock); | ||
| if (value_ns) | ||
| attention_raise(); |
There was a problem hiding this comment.
Expiry is only noticed when a vCPU passes through the host, the same as ITIMER_REAL. A guest spinning in user code waiting for the handler never sees the signal, because attention_raise() kicks once at arm time and nothing kicks at the deadline. This matches the existing setitimer limitation, so it can be a follow-up: have the preemption thread track the earliest POSIX or itimer deadline and call thread_interrupt_all() when it passes.
|
|
| pthread_mutex_lock(&sig_lock); | ||
| guest_posix_timer_t *timer = posix_timer_find_locked(id); | ||
| if (timer) | ||
| memset(timer, 0, sizeof(*timer)); |
There was a problem hiding this comment.
timer_delete clears the slot but leaves the timer's queued SI_TIMER signal in sig_state.shared. Trigger: block the timer's signal, let it expire, timer_delete it, free whatever sigev_value.sival_ptr pointed at, then unblock. The handler still runs and gets the stale si_ptr. Linux removes the timer's queued signal on delete, so drop the si_code == LINUX_SI_TIMER entries whose si_pid matches this id before the memset, then call refresh_pending_hint_locked(). The exec cleanup needs the same walk, so one helper can serve both.
POSIX timer syscalls return ENOSYS. Add support for up to 64 realtime or monotonic timers with signal or silent notification. Use the existing signal queues for delivery and overrun counts. Fork does not inherit timers, and exec removes them.
e6f74d3 to
cc91943
Compare
clone leaves the CLONE_PIDFD output untouched, and clone3 ignores pidfd allocation and copyout errors after starting the child. Create a close-on-exec pidfd before either child path can run. Cancel creation on failure and keep clone3's pidfd and parent-TID outputs separate. Thread pidfds remain unsupported.
Queued timer signals can survive delete, rearm and exec, while fast periodic timers can starve a waiting notification. Discard stale entries without changing other pending signals, and give timers a turn on each signal queue. Clear the interval on disarm and advance silent timers on query without keeping the polling hint set. Accept BOOTTIME with monotonic time, reject signals outside 1..64 and return ENOSYS for SIGEV_THREAD_ID.
Two flags encode three valid notification states. Use one state field and name the polling hint for both armed and waiting timers. Keep the BOOTTIME constant in the shared Linux definitions.
cc91943 to
1516851
Compare
There was a problem hiding this comment.
3 issues found across 11 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/runtime/forkipc.c">
<violation number="1" location="src/runtime/forkipc.c:1261">
P2: `CLONE_PIDFD` with `CLONE_VM` now creates a pidfd that can never become readable. `pidfd_create(g, child_tid, -1, ...)` takes the `host_pid <= 0` branch in proc-pidfd.c, which arms no exit monitor, and nothing calls `proc_pidfd_notify_exit` when a VM-clone child is reaped (its exit goes through `thread_ptrace_wait`, not any proc.c path). A guest blocking on this pidfd hangs forever, even after the child exits. Linux supports pidfds for `CLONE_VM` non-thread children; since the emulator cannot watch in-process children, reject `CLONE_PIDFD` with `-EINVAL` for the `CLONE_VM` path (like threads) or complete the pidfd when the VM-clone child is reaped.</violation>
<violation number="2" location="src/runtime/forkipc.c:2068">
P2: A failed `CLONE_PARENT_SETTID` copyout is ignored, so clone releases the child and reports success without writing its TID. Check the copyout and unwind with `-LINUX_EFAULT` before admitting the child.</violation>
</file>
<file name="src/syscall/signal.c">
<violation number="1" location="src/syscall/signal.c:521">
P2: In `posix_timer_discard_pending_locked`, the RT branch releases the shared claim even when only part of the per-signal queue is removed: `kept > 0` leaves `p->rt_queue[idx]` nonzero and the pending bit set, yet the code still falls through to `shared_claim_release_locked(signum)`. Releasing the claim while the signal is still pending makes the remaining queued entries (another timer's or a user's signal on the same RT number) visible to every thread, so a thread parked in a sigwait/ppoll holding the claim can lose a signal it was waiting for to another thread. The standard-signal branch only releases after the bit is cleared, so this asymmetry looks unintentional. Guard the release on `!kept` (or clear the bit before releasing).</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
| } | ||
| if (flags & LINUX_CLONE_PARENT_SETTID) { | ||
| int32_t tid = (int32_t) child_guest_pid; | ||
| (void) guest_write_small(g, ptid_gva, &tid, sizeof(tid)); |
There was a problem hiding this comment.
P2: A failed CLONE_PARENT_SETTID copyout is ignored, so clone releases the child and reports success without writing its TID. Check the copyout and unwind with -LINUX_EFAULT before admitting the child.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/runtime/forkipc.c, line 2068:
<comment>A failed `CLONE_PARENT_SETTID` copyout is ignored, so clone releases the child and reports success without writing its TID. Check the copyout and unwind with `-LINUX_EFAULT` before admitting the child.</comment>
<file context>
@@ -2021,17 +2056,16 @@ int64_t sys_clone(hv_vcpu_t vcpu,
+ }
+ if (flags & LINUX_CLONE_PARENT_SETTID) {
+ int32_t tid = (int32_t) child_guest_pid;
+ (void) guest_write_small(g, ptid_gva, &tid, sizeof(tid));
}
uint8_t admission_ready = 1;
</file context>
| (void) guest_write_small(g, ptid_gva, &tid, sizeof(tid)); | |
| if (guest_write_small(g, ptid_gva, &tid, sizeof(tid)) < 0) { | |
| fail_rc = -LINUX_EFAULT; | |
| goto fail_snapshot; | |
| } |
|
|
||
| int child_pidfd = -1; | ||
| if (flags & LINUX_CLONE_PIDFD) { | ||
| child_pidfd = clone_create_pidfd(g, child_tid, -1, pidfd_gva); |
There was a problem hiding this comment.
P2: CLONE_PIDFD with CLONE_VM now creates a pidfd that can never become readable. pidfd_create(g, child_tid, -1, ...) takes the host_pid <= 0 branch in proc-pidfd.c, which arms no exit monitor, and nothing calls proc_pidfd_notify_exit when a VM-clone child is reaped (its exit goes through thread_ptrace_wait, not any proc.c path). A guest blocking on this pidfd hangs forever, even after the child exits. Linux supports pidfds for CLONE_VM non-thread children; since the emulator cannot watch in-process children, reject CLONE_PIDFD with -EINVAL for the CLONE_VM path (like threads) or complete the pidfd when the VM-clone child is reaped.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/runtime/forkipc.c, line 1261:
<comment>`CLONE_PIDFD` with `CLONE_VM` now creates a pidfd that can never become readable. `pidfd_create(g, child_tid, -1, ...)` takes the `host_pid <= 0` branch in proc-pidfd.c, which arms no exit monitor, and nothing calls `proc_pidfd_notify_exit` when a VM-clone child is reaped (its exit goes through `thread_ptrace_wait`, not any proc.c path). A guest blocking on this pidfd hangs forever, even after the child exits. Linux supports pidfds for `CLONE_VM` non-thread children; since the emulator cannot watch in-process children, reject `CLONE_PIDFD` with `-EINVAL` for the `CLONE_VM` path (like threads) or complete the pidfd when the VM-clone child is reaped.</comment>
<file context>
@@ -1235,6 +1256,17 @@ static int64_t sys_clone_vm(hv_vcpu_t parent_vcpu,
+ int child_pidfd = -1;
+ if (flags & LINUX_CLONE_PIDFD) {
+ child_pidfd = clone_create_pidfd(g, child_tid, -1, pidfd_gva);
+ if (child_pidfd < 0) {
+ clone_rollback_tid_flags(g, &tid_rollback);
</file context>
| if (p == &sig_state.shared) | ||
| shared_claim_release_locked(signum); | ||
| } | ||
| } | ||
|
|
||
| /* The shared set as the current thread may take it. */ | ||
| static uint64_t shared_visible_locked(void) | ||
| { |
There was a problem hiding this comment.
P2: In posix_timer_discard_pending_locked, the RT branch releases the shared claim even when only part of the per-signal queue is removed: kept > 0 leaves p->rt_queue[idx] nonzero and the pending bit set, yet the code still falls through to shared_claim_release_locked(signum). Releasing the claim while the signal is still pending makes the remaining queued entries (another timer's or a user's signal on the same RT number) visible to every thread, so a thread parked in a sigwait/ppoll holding the claim can lose a signal it was waiting for to another thread. The standard-signal branch only releases after the bit is cleared, so this asymmetry looks unintentional. Guard the release on !kept (or clear the bit before releasing).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/syscall/signal.c, line 521:
<comment>In `posix_timer_discard_pending_locked`, the RT branch releases the shared claim even when only part of the per-signal queue is removed: `kept > 0` leaves `p->rt_queue[idx]` nonzero and the pending bit set, yet the code still falls through to `shared_claim_release_locked(signum)`. Releasing the claim while the signal is still pending makes the remaining queued entries (another timer's or a user's signal on the same RT number) visible to every thread, so a thread parked in a sigwait/ppoll holding the claim can lose a signal it was waiting for to another thread. The standard-signal branch only releases after the bit is cleared, so this asymmetry looks unintentional. Guard the release on `!kept` (or clear the bit before releasing).</comment>
<file context>
@@ -475,6 +483,46 @@ static void shared_claim_release_locked(int signum)
+ pending_clear(&p->pending, sig_bit(signum));
+ }
+ }
+ if (p == &sig_state.shared)
+ shared_claim_release_locked(signum);
+ }
</file context>
| if (p == &sig_state.shared) | |
| shared_claim_release_locked(signum); | |
| } | |
| } | |
| /* The shared set as the current thread may take it. */ | |
| static uint64_t shared_visible_locked(void) | |
| { | |
| if (signum < LINUX_SIGRTMIN) { | |
| int idx = signum - 1; | |
| const signal_rt_info_t *info = &p->std_info[idx]; | |
| if (!p->std_info_valid[idx] || info->si_code != LINUX_SI_TIMER || | |
| (id >= 0 && info->si_pid != id)) | |
| continue; | |
| p->std_info_valid[idx] = false; | |
| pending_clear(&p->pending, sig_bit(signum)); | |
| if (p == &sig_state.shared) | |
| shared_claim_release_locked(signum); | |
| } else { | |
| int idx = signum - LINUX_SIGRTMIN; | |
| int kept = 0; | |
| for (int n = 0; n < p->rt_queue[idx]; n++) { | |
| int slot = (p->rt_head[idx] + n) % RT_SIGQUEUE_MAX; | |
| signal_rt_info_t info = p->rt_info[idx][slot]; | |
| if (info.si_code == LINUX_SI_TIMER && | |
| (id < 0 || info.si_pid == id)) | |
| continue; | |
| int dest = (p->rt_head[idx] + kept++) % RT_SIGQUEUE_MAX; | |
| p->rt_info[idx][dest] = info; | |
| } | |
| if (kept == p->rt_queue[idx]) | |
| continue; | |
| p->rt_queue[idx] = kept; | |
| if (!kept) { | |
| p->rt_head[idx] = 0; | |
| pending_clear(&p->pending, sig_bit(signum)); | |
| if (p == &sig_state.shared) | |
| shared_claim_release_locked(signum); | |
| } | |
| } |
POSIX timer syscalls return ENOSYS. Add up to 64 realtime or monotonic
timers with signal or silent notification. BOOTTIME uses monotonic time.
Keep undelivered notifications until the queue has room, give waiting
timers a turn, and remove stale timer signals on delete, rearm and exec.
Fork does not inherit timers.
clone accepts CLONE_PIDFD without writing the output, and clone3 ignores
pidfd allocation and copyout failures. Create a close-on-exec pidfd before
the child can run and cancel creation if allocation or copyout fails.
Keep clone3's pidfd and parent-TID outputs separate. Thread pidfds remain
unsupported.
Timer expiry still requires the guest to enter the host. Deadline-driven
interruption of CPU-bound guests remains a follow-up. SIGEV_THREAD_ID
returns ENOSYS.
AI disclosure: OpenAI Codex was used to write and review this change.