Skip to content

Support pre-existing LD_PRELOAD/DYLD_INSERT_LIBRARIES#349

Merged
branchseer merged 4 commits intomainfrom
claude/fix-issue-340-e2e-6QRM5
Apr 22, 2026
Merged

Support pre-existing LD_PRELOAD/DYLD_INSERT_LIBRARIES#349
branchseer merged 4 commits intomainfrom
claude/fix-issue-340-e2e-6QRM5

Conversation

@branchseer
Copy link
Copy Markdown
Member

Summary

Fixes issue #340 by allowing fspy to coexist with user-supplied LD_PRELOAD (Linux) or DYLD_INSERT_LIBRARIES (macOS) environment variables. Previously, fspy would reject spawns when these variables were already set. Now it appends its tracer shim to the end of the preload list, preserving both the user's preload and correct symbol interposition order.

Key Changes

  • New append_path_env() function in fspy_shared_unix/src/exec/mod.rs: Appends a value to colon-separated path environment variables (like LD_PRELOAD) instead of overwriting them. The function is idempotent and avoids leading colons. Includes comprehensive unit tests covering edge cases.

  • Updated Linux spawn handler (fspy_shared_unix/src/spawn/linux/mod.rs): Changed from ensure_env() (which would fail if the variable existed) to append_path_env() to append fspy's shim to any existing LD_PRELOAD.

  • Updated macOS spawn handler (fspy_shared_unix/src/spawn/macos.rs): Similarly updated to use append_path_env() for DYLD_INSERT_LIBRARIES.

  • New test library (crates/preload_test_lib/src/lib.rs): A Linux-only LD_PRELOAD library that intercepts open/openat syscalls. It short-circuits paths containing the marker preload_test_short_circuit (returning ENOENT without forwarding), while forwarding all other calls via RTLD_NEXT. This allows testing that fspy correctly handles user preloads that short-circuit syscalls.

  • New e2e test fixture (preexisting_ld_preload): Comprehensive end-to-end test that verifies:

    • Short-circuited file accesses are not tracked by fspy (cache hit when modified)
    • Real file accesses are tracked (cache miss when modified)
    • The preload chain works correctly with both user and fspy preloads
  • E2e test harness improvements (vite_task_bin/tests/e2e_snapshots/main.rs): Added support for Linux platform filter and environment variable placeholder substitution (<PRELOAD_TEST_LIB_PATH>) to inject the test library path at runtime.

Implementation Details

The append strategy is critical: by placing fspy's shim last in the preload list, user preloads that short-circuit syscalls (returning without forwarding to libc) remain invisible to fspy—accurately reflecting what the OS actually executed. This preserves cache correctness when user preloads intercept file operations.

https://claude.ai/code/session_018oB9YLpLUKWprpr1RFJq4k

claude added 3 commits April 22, 2026 20:07
…#340)

When the user's shell had already exported LD_PRELOAD (Linux) or
DYLD_INSERT_LIBRARIES (macOS), fspy's ensure_env saw the existing value
and returned EINVAL, surfacing as "failed to prepare the command for
injection: Invalid argument (os error 22)" and aborting the run.

Replace the overwrite-only ensure_env call on the preload variables with
a new append_path_env that colon-appends fspy's shim. Appending (rather
than prepending) keeps the user's preload ahead of fspy in interposition
order, so libc calls short-circuited by the user layer remain invisible
to fspy — the tracer must record what actually executed, not what was
suppressed.

The existing ensure_env is kept for PAYLOAD_ENV_NAME, which is
single-valued.

Adds a Linux-only e2e fixture that demonstrates both properties:
a cdylib test preload (crates/preload_test_lib) short-circuits any path
containing "preload_test_short_circuit" and forwards everything else via
RTLD_NEXT. The fixture runs with LD_PRELOAD pointing at the test preload
and verifies that fspy tracks real.txt (cache miss on modification) but
never sees the short-circuited file (cache hit despite modification).
The path to the test cdylib is discovered at test-runtime from
CARGO_CDYLIB_FILE_PRELOAD_TEST_LIB via a <PRELOAD_TEST_LIB_PATH>
placeholder in snapshots.toml, so the test binary is cross-compile and
transfer-friendly.
On musl, fspy falls back to seccomp-unotify for access tracking and
strips LD_PRELOAD from spawned children (spawn/linux/mod.rs). That
breaks the fixture's interposer-chain assumption: without
preload_test_lib loaded, the "short-circuited" file is actually opened
and seccomp records it, so modifying it becomes a cache miss instead
of a hit.

Add a "linux-gnu" platform filter that matches target_os = linux and
target_env != musl, and switch the fixture to use it.
@branchseer branchseer force-pushed the claude/fix-issue-340-e2e-6QRM5 branch from 18962a4 to 2351cc4 Compare April 22, 2026 12:07
@branchseer branchseer requested a review from Copilot April 22, 2026 12:15
@branchseer
Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Enables fspy to coexist with pre-existing LD_PRELOAD (Linux) and DYLD_INSERT_LIBRARIES (macOS) by appending fspy’s shim to the existing preload list instead of rejecting the spawn.

Changes:

  • Added append_path_env() in fspy_shared_unix to append to colon-separated preload env vars (idempotently) and updated Linux/macOS spawn handlers to use it.
  • Introduced a Linux-only test LD_PRELOAD interposer library (preload_test_lib) plus a new e2e fixture verifying correct preload chaining behavior.
  • Enhanced the e2e harness with additional platform filters and runtime placeholder substitution for the artifact-built preload library path.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
crates/fspy_shared_unix/src/exec/mod.rs Adds append_path_env() + unit tests to append (not overwrite) preload env vars.
crates/fspy_shared_unix/src/spawn/linux/mod.rs Switches Linux injection from ensure_env() to append_path_env() for LD_PRELOAD.
crates/fspy_shared_unix/src/spawn/macos.rs Switches macOS injection to append_path_env() for DYLD_INSERT_LIBRARIES.
crates/preload_test_lib/Cargo.toml New Linux-only cdylib crate metadata/deps for the test preload library.
crates/preload_test_lib/src/lib.rs New test interposer implementing short-circuit + forwarding behavior for open/openat.
crates/vite_task_bin/Cargo.toml Adds Linux-only dev artifact dependency on preload_test_lib for e2e runs.
crates/vite_task_bin/tests/e2e_snapshots/main.rs Adds platform filters + <PRELOAD_TEST_LIB_PATH> env placeholder resolution.
crates/vite_task_bin/tests/e2e_snapshots/fixtures/preexisting_ld_preload/** New fixture + snapshots covering pre-existing LD_PRELOAD behavior.
Cargo.lock Records the new preload_test_lib package and dependency edge.
CHANGELOG.md Documents the fix for pre-existing preload env vars (Issue #340).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Addresses Copilot review feedback: the trailing \n in the write-file
step argument made display_command_line() render the ## snapshot
header across multiple lines. The file contents change regardless
of the trailing newline, so cache invalidation is still exercised.
@branchseer branchseer merged commit 753ccd9 into main Apr 22, 2026
11 of 12 checks passed
@branchseer branchseer deleted the claude/fix-issue-340-e2e-6QRM5 branch April 22, 2026 12:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants