Skip to content

fix(tool-sandbox): fall back to the interpreter's directory for RPATH-less ELF dependencies - #1650

Merged
SequeI merged 3 commits into
nolabs-ai:mainfrom
Ekleog:ekleog/fix-nixos
Sep 16, 2026
Merged

SequeI merged 3 commits into
nolabs-ai:mainfrom
Ekleog:ekleog/fix-nixos

Conversation

@Ekleog

@Ekleog Ekleog commented Aug 16, 2026 •

Copy link
Copy Markdown
Contributor

Linked Issue

Closes #1649
Closes #1724

Summary

resolve_shared_library's ELF dependency-closure walk only searched a
NEEDED library's own DT_RPATH/DT_RUNPATH plus a hardcoded FHS default
list (/lib, /usr/lib, etc.). On Nix/NixOS this fails whenever a shared
object has no RPATH/RUNPATH of its own — e.g. libgcc_s.so.1 (from the
gcc-*-libgcc package) needing libc.so.6 by bare soname — because none of
the FHS defaults exist as such there, so nono run refused to start any
command whose dependency closure reached that library.

The real dynamic linker resolves this case fine: a Nix-built ld.so has its
own directory compiled in as its default search path, so an RPATH-less
NEEDED entry still resolves through it. This PR threads the top-level
binary's own ELF interpreter (PT_INTERP) directory through the recursive
closure walk as an extra fallback search directory — tried after the
referencing object's own RPATH/RUNPATH and before the FHS defaults — so
resolve_shared_library mirrors that real resolution order instead of
hardcoding anything Nix-specific.

On FHS/non-Nix systems this is a no-op: the interpreter directory there is
normally /lib or /lib64, already covered by the existing default list.
The ELF_LIB_CACHE memoization key was widened to include the interpreter
directory so a resolution computed under one binary's interpreter doesn't
leak into a different binary's closure within the same launch.

Verified directly against libgcc_s.so.1 on NixOS with readelf -d (empty
RUNPATH) and ldd (resolves via the interpreter's own directory).

Agent Disclosure (if applicable)

This PR was prepared with AI assistance (Claude Code). Files/sections
consulted: crates/nono-cli/src/tool-sandbox/platform/linux.rs
(elf_dependency_closure, resolve_elf_recursive, resolve_shared_library,
parse_elf/parse_dynamic, and the surrounding ELF_*_CACHE memoization),
this repository's AGENTS.md (Coding Agent Contribution Policy, coding
standards, security considerations), and the linked issue.

Test Plan

  • cargo test -p nono-cli --bins tool_sandbox::linux — 34 passed, including
    6 new/changed tests covering the fallback, its precedence under RPATH,
    the not-found error path, and the real PT_INTERP-parsing path via
    std::env::current_exe().
  • cargo fmt --all -- --check and cargo build --release -p nono-cli — clean.
  • Manual repro: nono run --profile claude --allow-cwd -- ~/.nix-profile/bin/claude --dangerously-skip-permissions --help on NixOS no longer fails with the ELF-resolution error.

Checklist

  • An issue exists and is linked above
  • All commits are signed-off, using DCO
  • All new code follows the project's coding standards (CLAUDE.md) and is covered by tests
  • Public-facing changes are paired with documentation updates — n/a, this is internal sandbox-initialization behavior with no public API or documented contract change

Agent Compliance Check (Required for AI/Automated PRs)

  • I am not prohibited from contributing under this policy
  • An issue already exists
  • I disclosed that I am an agent in the issue discussion
  • I described my intent and approach in the issue discussion
  • I reviewed repository coding and security rules for the affected area
  • I provided required attribution for reused or adapted code — n/a, no reused/adapted code
  • I did not use forbidden patterns such as unwrap/expect
  • I used NonoError where required
  • I validated and canonicalized all relevant paths
  • This PR matches the approved or disclosed issue scope

@github-actions github-actions Bot added bug Something isn't working nono-cli size/medium labels Aug 16, 2026
@github-actions

github-actions Bot commented Aug 16, 2026 •

Copy link
Copy Markdown
Contributor

PR Review Summary

Size

Metric Value
Lines added +625
Lines removed -22
Total changed 647
Classification Large (> 300 lines)

Affected crates

  • crates/nono-cli — CLI changes. Verify argument parsing, flag documentation, and UX behaviour across supported platforms.

Blast radius — Contained

This PR touches: source code


Updated automatically on each push to this PR.

@jocelynthode

Copy link
Copy Markdown

Hey I tried to build nono from this PR. It fixed the command_policies failing, but I hit then another bug. When having a command_policies defined and trying to run for example pi. It fails. This seems related to the fact that pi is a wrapper shell script (common in nixpkgs)

any #!-script on $PATH fails ELF parsing in parse_elf(), the error propagates out of add_outer_exec_file_with_deps, and the entry is silently skipped (debug-level "outer exec gate skipped"). Neither the script nor its shebang interpreter is added to the execute allow-list, so launching it under any profile that has command policies fails with exit 127.

Not sure if fixing this should be part of this PR or a new issue.

@SequeI

SequeI commented Aug 26, 2026

Copy link
Copy Markdown
Member

Fixing both issues in one if the scope doesnt expand greatly would be very appreciated!

@jocelynthode

Copy link
Copy Markdown

@Ekleog Is that something you would be willing to look into? I'm not well versed in Rust and would not feel comfortable vibe coding and submitting a PR that I would not thoroughly understand.

@welociraptor

Copy link
Copy Markdown

Confirming this fixes #1649 on NixOS with nono 0.74.0 — the issue was filed against 0.73.0, so it's still current. The patch applies to tag v0.74.0 with offsets only and leaves Cargo.lock alone, so carrying it in nixpkgs is a one-line overrideAttrs.

Would you consider un-drafting it?

@nogent-nolabs-ai nogent-nolabs-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nogent code review

No blocking issues found.

Findings: none flagged in scope.

Automated code + security review. CI already covers clippy, rustfmt, tests, cargo-audit and commit-lint.

@Ekleog

Ekleog commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

I just undrafted, but unfortunately it looks like CI is failing and I'll probably not be able to get back to this for the next few weeks (maybe until early next year depending on how things evolve on my personal side).

If someone could take the PR over and push it to completion, it'd be awesome. Sorry for being unable to pass the finish line!

@SequeI

SequeI commented Sep 15, 2026

Copy link
Copy Markdown
Member

If I have free time tmwr, I will take care of this; will write it down on my todo's!

…-less ELF dependencies

Closes nolabs-ai#1649

`resolve_shared_library`'s ELF dependency-closure walk only searched a
`NEEDED` library's own `DT_RPATH`/`DT_RUNPATH` plus a hardcoded FHS default
list (`/lib`, `/usr/lib`, etc.). On Nix/NixOS this fails whenever a shared
object has no RPATH/RUNPATH of its own — e.g. `libgcc_s.so.1` (from the
`gcc-*-libgcc` package) needing `libc.so.6` by bare soname — because none of
the FHS defaults exist as such there, so `nono run` refused to start any
command whose dependency closure reached that library.

The real dynamic linker resolves this case fine: a Nix-built `ld.so` has its
own directory compiled in as its default search path, so an RPATH-less
`NEEDED` entry still resolves through it. This PR threads the *top-level*
binary's own ELF interpreter (`PT_INTERP`) directory through the recursive
closure walk as an extra fallback search directory — tried after the
referencing object's own RPATH/RUNPATH and before the FHS defaults — so
`resolve_shared_library` mirrors that real resolution order instead of
hardcoding anything Nix-specific.

On FHS/non-Nix systems this is a no-op: the interpreter directory there is
normally `/lib` or `/lib64`, already covered by the existing default list.
The `ELF_LIB_CACHE` memoization key was widened to include the interpreter
directory so a resolution computed under one binary's interpreter doesn't
leak into a different binary's closure within the same launch.

Verified directly against `libgcc_s.so.1` on NixOS with `readelf -d` (empty
`RUNPATH`) and `ldd` (resolves via the interpreter's own directory).

- `cargo test -p nono-cli --bins tool_sandbox::linux` — 34 passed, including
  6 new/changed tests covering the fallback, its precedence under RPATH,
  the not-found error path, and the real `PT_INTERP`-parsing path via
  `std::env::current_exe()`.
- `cargo fmt --all -- --check` and `cargo build --release -p nono-cli` — clean.
- Manual repro: `nono run --profile claude --allow-cwd -- ~/.nix-profile/bin/claude --dangerously-skip-permissions --help` on NixOS no longer fails with the ELF-resolution error.

- [x] An issue exists and is linked above
- [x] All commits are signed-off, using [DCO](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin)
- [x] All new code follows the project's coding standards ([CLAUDE.md](CLAUDE.md)) and is covered by tests
- [x] Public-facing changes are paired with documentation updates — n/a, this is internal sandbox-initialization behavior with no public API or documented contract change

This PR was prepared with AI assistance (Claude Code). Files/sections
consulted: `crates/nono-cli/src/tool-sandbox/platform/linux.rs`
(`elf_dependency_closure`, `resolve_elf_recursive`, `resolve_shared_library`,
`parse_elf`/`parse_dynamic`, and the surrounding `ELF_*_CACHE` memoization),
this repository's `AGENTS.md` (Coding Agent Contribution Policy, coding
standards, security considerations), and the linked issue.

- [x] I am not prohibited from contributing under this policy
- [x] An issue already exists
- [x] I described my intent and approach in the issue discussion
- [x] I reviewed repository coding and security rules for the affected area
- [x] I provided required attribution for reused or adapted code — n/a, no reused/adapted code
- [x] I did not use forbidden patterns such as unwrap/expect
- [x] I used NonoError where required
- [x] I validated and canonicalized all relevant paths
- [x] This PR matches the approved or disclosed issue scope

Signed-off-by: Léo Gaspard <leo@gaspard.io>
@jocelynthode

Copy link
Copy Markdown

@SequeI Thanks for taking over! will you fix #1724 in the same PR as well?

@SequeI

SequeI commented Sep 16, 2026

Copy link
Copy Markdown
Member

@jocelynthode Yup, just orienting myself around this right now. Will tag you once again to give it a whirl when the fix is up

  Grant and validate shebang interpreter closures for trusted executable directories, including /usr/bin/env targets. Add Nix-style wrapper and writable-interpreter regressions.

Signed-off-by: Aleksy Siek <aleksy@nolabs.ai>
Signed-off-by: Aleksy Siek <aleksy@nolabs.ai>
@SequeI

SequeI commented Sep 16, 2026

Copy link
Copy Markdown
Member

@jocelynthode Let me know if both issues are resolved now off of this PR please

@jocelynthode

Copy link
Copy Markdown

@SequeI I did a quick test running on this branch with

./target/debug/nono run --profile pi-cmd -- pi

pi worked fine. I also have command policies in this profile (for gh and kubectl) and both worked fine, so from my cursory test it appears to be fixed as these 2 tests where failing before! Thanks!

@SequeI
SequeI merged commit 0766749 into nolabs-ai:main Sep 16, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working nono-cli size/large

Projects

None yet

4 participants