fix(memory): source forwarded env below memtrack's capability boundary#438
Closed
not-matthias wants to merge 1 commit into
Closed
fix(memory): source forwarded env below memtrack's capability boundary#438not-matthias wants to merge 1 commit into
not-matthias wants to merge 1 commit into
Conversation
codspeed-memtrack holds file capabilities, so a non-root user executing it enters glibc secure-execution mode (AT_SECURE=1), which strips LD_* variables from the environment before main(). The memory executor sourced the forwarded environment around memtrack, so LD_LIBRARY_PATH was restored and then stripped again before the spawned benchmark could inherit it. Benchmarks relying on LD_LIBRARY_PATH (e.g. neqo loading a locally built NSS) fell back to the system library and panicked with UnsupportedVersion. Source the env inside the command memtrack runs via `bash -c`, matching how walltime already layers privilege escalation outside the env re-source. Add prefix_command_with_env for the raw `source <file> && <cmd>` snippet and reuse it from wrap_with_env.
| // LD_* variables. Sourcing inside the benchmark shell restores them below | ||
| // that boundary. | ||
| let bench_command = get_bench_command(&execution_context.config)?; | ||
| let (bench_command, env_file) = prefix_command_with_env(&bench_command, &extra_env)?; |
There was a problem hiding this comment.
When the runner starts memtrack as root and memtrack drops its child bash -c to SUDO_UID/SUDO_GID, the child now has to source a NamedTempFile created by the runner. That temp file is owner-only by default, so the benchmark shell can fail with Permission denied before restoring the forwarded environment; the old outer wrapper read the file before crossing into memtrack's dropped child process.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/executor/memory/executor.rs
Line: 62
Comment:
**Privilege Drop Hides Env File**
When the runner starts memtrack as root and memtrack drops its child `bash -c` to `SUDO_UID`/`SUDO_GID`, the child now has to `source` a `NamedTempFile` created by the runner. That temp file is owner-only by default, so the benchmark shell can fail with `Permission denied` before restoring the forwarded environment; the old outer wrapper read the file before crossing into memtrack's dropped child process.
How can I resolve this? If you propose a fix, please make it concise.
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.
Context
Reported via Mozilla Slack thread (COD-3076): a neqo benchmark fails on the memory runner but succeeds on simulation. The benchmark panics in NSS init with:
The workflow builds NSS 3.121 from source (the runner image ships 3.98) and points the benchmark at it via
LD_LIBRARY_PATH. On the memory runner that variable never reached the benchmark, so it loaded the too-old system NSS.Root cause
codspeed-memtrackholds file capabilities (cap_bpf,cap_perfmon,cap_sys_admin). A non-root user executing a capability binary enters glibc secure-execution mode (AT_SECURE=1), which stripsLD_*variables from the environment beforemain().The memory executor sourced the forwarded environment around memtrack:
So
LD_LIBRARY_PATHwas restored and then stripped a second time, before the benchmark memtrack spawns could inherit it.Walltime is unaffected because it layers privilege escalation (
sudo/perf/systemd scope) outside the env re-source, keepingsourceat the innermost layer.Fix
Source the env inside the command memtrack runs via its own
bash -c, below the capability boundary:prefix_command_with_envfor the rawsource <file> && <cmd>snippet (no outerbash -c) and reuse it fromwrap_with_env.MemoryExecutor::build_memtrack_commandnow prefixes the bench command and passes it as memtrack's argument; the outer bash wrapper is gone.Test
test_memory_executor_forwards_ld_library_path(Linux +GITHUB_ACTIONSgated) exercises this end-to-end. The existing..._forwards_pathtest never caught it becausePATHis not anLD_*variable.Note
If the memory test suite is ever run as root, memtrack's caps add nothing, secure-exec isn't triggered, and the test would pass even with the bug present, same limitation as the real-world scenario, which only affects the non-root
runneruser.Verification
Reproduced the mechanism with a minimal capability binary that runs its argument via
bash -c(memtrack's exact structure), as a non-root user:LD_LIBRARY_PATH/custom/lib✅