fix(tracer-ptrace): track dup2/dup3 so shell redirects capture writes#83
Open
christophergeyer wants to merge 3 commits intocg/aarch64-tracerfrom
Open
fix(tracer-ptrace): track dup2/dup3 so shell redirects capture writes#83christophergeyer wants to merge 3 commits intocg/aarch64-tracerfrom
christophergeyer wants to merge 3 commits intocg/aarch64-tracerfrom
Conversation
7 tasks
christophergeyer
pushed a commit
that referenced
this pull request
May 9, 2026
The CI workflow's `pull_request: branches: [main]` filter meant only PRs whose base is `main` triggered CI. Stacked PRs (cg/foo → cg/bar) landed blind: PRs #83–#87 in the current ptrace/eBPF/preload stack all reported "no checks reported on the branch" because each one targets a feature-branch base. Drop the branch filter on `pull_request` so CI fires for every PR regardless of base. The `push: branches: [main]` constraint stays — we don't want to burn CI minutes on every feature-branch push, only on PR diffs and main. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bash's `> file` redirect emits open(file)→fd=N, dup3(N, 1, 0), write(1, …). The ptrace tracer recorded the open but never the dup, so when the write to fd 1 fired the fd_tracker had no path mapping for fd 1 and dropped the write. Result: `bash -c 'echo hi > test.txt'` captured 0/50 trials. Wire SYS_DUP2 / SYS_DUP3 into the seccomp filter and add an entry-time old_fd capture (registers are clobbered between entry and exit on aarch64 where x0 is both arg0 and ret_val). On exit, only call FdTracker::handle_dup if ret_val >= 0 — a failed dup must not replace the destination fd's existing path mapping. After this: bash -c 'echo hi > test.txt' 50/50 captured (was 0/50) bash -c 'sleep 0.2; echo hi > x' 50/50 captured aarch64 has no sys_dup2 — only sys_dup3. The arch module gives SYS_DUP2 a sentinel value there so the shared match arm compiles but never fires. Plain `dup` (single-arg) is intentionally not yet handled; bash uses dup3 specifically and we'd rather wait for a real workload to motivate it. New integration coverage in tests/integration/test_dup_tracing.py: - bash redirect is captured - failed-dup3 invariant: existing fd→path is preserved - python read+write regression check Also adds docs/proposals/preload-coverage-gaps.md capturing the parallel preload-tracer gap (shell redirects via fputs/printf-family that bypass LD_PRELOAD entirely) — recommended as a follow-up doc + warning rather than chasing libc internals. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The eval team's P0-5 finding (multi-stage shell pipelines miss output
under preload) is the same architectural gap as the bash redirect race,
just with much wider impact: any workload that pipes through `awk`,
`bash`'s redirect, or any other tool that writes via `fputs`/`printf`
silently produces empty output sets in `roar dag` and `roar lineage`.
Update the deferred-work doc to:
- Bump severity (this is the value-prop bug, not just a corner case)
- Add the empirical capture matrix across all three backends
(ptrace + PR #82 captures everything, eBPF + PR #83 captures
everything, preload still misses redirect-through-libc paths)
- Add Option E: change `--tracer auto` Linux preference to
eBPF → ptrace → preload. Makes P0-5 go away for the default
install without chasing glibc internals.
- Reorder the recommendation list — Option E is now the cheap win;
Option A (hook expansion) only matters if a workload exists that
can't fall back to ptrace.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
36a7d41 to
d71358f
Compare
CI lint caught two unused imports (`os`, `tempfile`) that snuck in when this test file was first added. ruff --fix removes them and ruff format formats the trailing whitespace.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
bash -c 'echo > test.txt'captured 0/50 trials under the ptracetracer. Bash's
>redirect emitsopenat(test.txt) → fd=N,dup3(N, 1, 0),write(1, …). The tracer recorded the open butnever the dup, so when the write to fd 1 fired the fd_tracker had no
path mapping for fd 1 and dropped the write.
Wire
SYS_DUP2/SYS_DUP3into the seccomp filter and add anentry-time
old_fdcapture (registers are clobbered between entryand exit on aarch64 where x0 is both arg0 and ret_val). On exit, only
call
FdTracker::handle_dupifret_val >= 0— a failed dup mustnot replace the destination fd's existing path mapping. After this:
aarch64 has no
sys_dup2— onlysys_dup3. The arch module givesSYS_DUP2 a sentinel value there so the shared match arm compiles but
never fires. Plain
dup(single-arg) is intentionally not yethandled; bash uses dup3 specifically and we'd rather wait for a real
workload to motivate it.
Test plan
tests/integration/test_dup_tracing.py— bash redirect captured, failed-dup3 invariant preserved, python read+write regression checkIncludes a docs update (
docs/proposals/preload-coverage-gaps.md) capturing the related P0-5 finding for the preload tracer; the actual preload fix lives on a separate PR.Stacks on #81 (aarch64 tracer port). Rebase to main once that lands.
🤖 Generated with Claude Code