Skip to content

fix(fspy): forward calls with -1 as the directory descriptor - #862

Merged
wan9chi merged 7 commits into
voidzero-dev:mainfrom
magurotuna:fix/fspy-invalid-dirfd
Oct 19, 2026
Merged

wan9chi merged 7 commits into
voidzero-dev:mainfrom
magurotuna:fix/fspy-invalid-dirfd

Conversation

@magurotuna

Copy link
Copy Markdown
Member

Motivation

In a cached task, the ps -p <pid> command aborts with the SIGABRT signal if the process <pid> has already exited. Outside of vp run, the same command exits with status 1.

Some file system calls, such as openat, take a directory descriptor: an open file descriptor of a directory, which the call uses to resolve a relative path. Starting with version 4.0.6 (5e4886ae), ps from procps-ng opens /proc/<pid> as a directory descriptor and then reads the status file relative to it. If the process has exited, the open fails and returns -1. ps does not check the result, and calls openat(-1, "status", O_RDONLY). This call is harmless by itself: the kernel fails it with the EBADF error.

On Linux and macOS, fspy injects interceptors into tracked programs. An interceptor is a function that replaces a libc function, such as openat, records the path, and then calls the original function. The interceptor converted the directory descriptor with the BorrowedFd::borrow_raw function, which panics on -1. The injected library aborts on panic, so the panic aborted ps.

Any tracked program that passes -1 as a directory descriptor aborted in the same way. The ps command is the reported case.

Fixes voidzero-dev/vite-plus#2954.

Changes

  • Add the BorrowedFd::try_borrow_raw function, which returns None for -1. The interceptors use this function, so that they forward a call with -1 to the original function instead of panicking.
  • If the path is absolute, the kernel ignores the directory descriptor, so fspy still records the path. If the path is relative, the call fails, so fspy does not record the path.
  • The changed interceptors are openat, fstatat, faccessat, statx, syscall(SYS_statx), fdopendir, getdirentries, and execveat, and on macOS also openat$NOCANCEL and __getdirentries64.
  • This change does not affect the seccomp path, which fspy uses for static programs and musl builds. The seccomp handler already ignores a failed descriptor lookup.
  • Add the crates/fspy/tests/invalid_dirfd.rs tests. The tests call statx with the syscall function, and call the glibc statx and execveat functions through the dlsym function, so that the tests build for glibc 2.17 and musl.

magurotuna and others added 5 commits October 16, 2026 21:48
A tracked program can pass -1 as a directory descriptor, such as a
failed `open` result it does not check. procps-ng's `ps -p <pid>` does
this for an exited process, and the interposers abort it while
borrowing -1 (voidzero-dev/vite-plus#2954).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The interposers borrowed every descriptor argument with
`BorrowedFd::borrow_raw`, which panics on -1, so a tracked program that
passed a failed `open` result to `openat`, `fstatat`, `faccessat`,
`statx`, `fdopendir`, or `execveat` aborted. procps-ng's `ps -p <pid>`
does this when the process has exited.

Borrow descriptor arguments as `Option`, with `None` for -1. An
absolute pathname is still tracked, since the kernel ignores the
descriptor; a relative pathname or a lone descriptor is not, since the
call fails with `EBADF`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…rget

The tests called the libc `statx` wrapper, which glibc 2.17 lacks and
the libc crate does not declare for musl, so the Linux CI builds failed.
Call `statx` through `syscall` instead, and resolve the glibc `statx`
and `execveat` wrappers with `dlsym` so that their interposers are still
covered on glibc.

Also cover `statx` with a null path and `AT_EMPTY_PATH`, and check that
a relative path at -1 is not recorded under the current directory.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@magurotuna magurotuna added the ci: full Run the slow CI jobs (macOS x64, musl) on this pull request label Oct 18, 2026
…#862

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@fengmk2
fengmk2 requested a review from wan9chi October 19, 2026 01:05
Signed-off-by: Charles Wang <me@wan9chi.com>
@wan9chi
wan9chi merged commit 1b144be into voidzero-dev:main Oct 19, 2026
20 checks passed
cursor Bot added a commit to voidzero-dev/vite-plus that referenced this pull request Oct 21, 2026
Bumps vite-task from `9ba4cc9` to `8db455e`.

Changelog:
voidzero-dev/vite-task@9ba4cc9...8db455e#diff-06572a96a58dc510037d5efa622f9bec8519bc1beab13c9f251e97e657a9d4ed

## Breaking change adapted

fspy no longer reports a bitset of access modes on each path. Each
tracked operation is its own `PathAccess` (`Read`, `Write`, `ReadWrite`,
or `ReadDir`). `vp_command` combines the operations that touch the same
workspace-relative path into the existing `AccessMode` set, so the
JavaScript `run_command` result is unchanged.

## User-facing vite-task changes

- A mistyped task setting now fails with an error that names the field,
instead of `data did not match any variant of untagged enum
UserTaskDefinition` (voidzero-dev/vite-task#815).
- `vp run` no longer panics on scripts that contain non-ASCII
characters, and no longer cuts those commands off where it displays them
(voidzero-dev/vite-task#861).
- Reinstalling with another pnpm version no longer changes the cache
fingerprint of `node_modules/.modules.yaml`; only whether the file
exists is hashed (voidzero-dev/vite-task#850).
- Cached tasks on Linux and macOS no longer abort when a program passes
`-1` as a directory descriptor, and concurrent syscalls in tracked
programs no longer overflow the stack (voidzero-dev/vite-task#862,
voidzero-dev/vite-task#854).

Docs are unchanged. These are fixes to existing behavior, and the guides
do not describe the old error text or the `.modules.yaml` fingerprint.

## Validation

- `cargo check --workspace --all-features --all-targets --locked`
- `cargo clippy -p vp_command -p vite-plus-cli --all-targets
--all-features -- --deny warnings`
- `cargo test` for `vp_command`, `vp_error`, `vp_pm_cli`,
`vp_js_runtime`, `vp_migration`, `vp_shared`, `vp_static_config`,
`vite-plus-cli`, and `vp_global_cli`


<div><a
href="https://cursor.com/agents/bc-9c9cb178-3b56-4d68-9556-3028d946a3ec?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/automations/3a586a0e-cc2b-11f1-bec7-b2318963e0b6"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/view-automation-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/view-automation-light.png"><img
alt="View Automation" width="141" height="28"
src="https://cursor.com/assets/images/view-automation-dark.png"></picture></a>&nbsp;</div>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Charles Wang <wan9chi@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci: full Run the slow CI jobs (macOS x64, musl) on this pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fspy: procps ps aborts when querying an exited process in cached tasks

2 participants