Skip to content

fix(policy): survive atomic replacement of resolv.conf; report stale … - #1448

Merged
lukehinds merged 5 commits into
nolabs-ai:mainfrom
maufl:felix/unbreak-resolv-conf-on-linux
Jul 24, 2026
Merged

fix(policy): survive atomic replacement of resolv.conf; report stale …#1448
lukehinds merged 5 commits into
nolabs-ai:mainfrom
maufl:felix/unbreak-resolv-conf-on-linux

Conversation

@maufl

@maufl maufl commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Linked Issue

References #986 — a partial fix by design (diagnostics plus one built-in policy instance, per the disclosure comment on the issue), so intentionally not Closes.

Summary

Landlock rules bind to the inode open at ruleset build, so a file-level grant goes stale when the file is atomically replaced (write-temp + rename(2)). This PR:

  1. Policy (data/policy.json): system_read_linux_core additionally grants the directory /run/systemd/resolve (read). Its /etc/resolv.conf file grant canonicalizes to stub-resolv.conf, which systemd-resolved replaces on any resolver-config change, breaking DNS for the rest of the session. Directory rules cover children created later, so they survive; non-systemd systems skip the missing path.
  2. Diagnostics: the capability state file records (dev, ino) per file grant at sandbox start (Linux only; optional fields, compatible both ways). nono why re-stats the granted path and reports DENIED / stale_file_grant with grant-time vs. current inode and a restart hint — or ALLOWED with a warning when a directory grant still covers the path. Non---self queries load the state leniently (unreadable state ⇒ no hints, never an error); --self stays strict.
  3. Docs: troubleshooting entry + Landlock-internals note.
DENIED
  Reason: stale_file_grant
  Details: The sandbox granted this file, but the file at ... was replaced
  after sandbox start (inode 39:175234 when the sandbox started, 39:175245
  now). Landlock rules bind to the inode, not the path, ... Restart the
  sandbox to re-apply the grant to the current file.

Deliberately not done: Landlock has no path-based rules, so the only way a file grant can survive replacement is granting its parent directory. We did not apply that automatically to all file grants — it silently widens each to its whole directory, a posture change maintainers should make explicitly, per grant (as done here). The issue's repro still reproduces for user/profile file grants, now diagnosable. Other rename-prone builtin file grants (/etc/ld.so.cache, /etc/hosts, …) remain — their parent /etc is too broad. /run/systemd/resolve is systemd-specific in-tree policy; per NOGENT.md a nono package may be the longer-term home — guidance welcome.

Agent Disclosure

  • Agent-assisted contribution (Claude Fable 5 via Claude Code), directed and reviewed by @maufl; intent disclosed on filesystem.read_file grants become stale when target is replaced via atomic rename (Linux/Landlock) #986 before opening this PR.
  • Consulted: AGENTS.md, NOGENT.md, sandbox/linux.rs (PathFd inode binding), capability.rs (grant-time canonicalization), policy.rs, sandbox_state.rs, why_runtime.rs, query_ext.rs, the profile authoring guide, and this template. All code newly written; nothing reused or adapted.
  • Confirmed compliant with repository coding and security requirements: NonoError propagation, no unwrap/expect in production code, paths canonicalized, fail-closed behavior preserved (the lenient loader affects diagnostics only, never enforcement).

Test Plan

  • New unit tests: staleness detection (fresh / in-place / replaced / removed), both why verdicts, pass-through, inode recording, legacy-state compatibility, lenient-loader failure modes.
  • make check, make test, cargo audit, make lint-aliases, make lint-docs pass locally (remaining failures are pre-existing PTY/AF_UNIX tests needing permissions the sandboxed dev env lacks, all in untouched files).
  • Verified end-to-end on Arch/zen (Landlock ABI v6): resolv.conf reads survive stub replacement under the new grant; the output above is from a live session; non---self why still answers with an unreadable state file.

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
  • Release note has been added to CHANGELOG.md if needed (generated via git-cliff from conventional commits)

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
  • 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

maufl and others added 4 commits July 20, 2026 09:47
…file grants in nono why

Landlock rules bind to the inode open at ruleset build time, not to the
path. The system_read_linux_core grant for /etc/resolv.conf canonicalizes
to /run/systemd/resolve/stub-resolv.conf, which systemd-resolved rewrites
via write-temp-then-rename whenever resolver config changes — leaving the
rule pointing at the old inode and every subsequent read failing EACCES
for the rest of the session. Grant the /run/systemd/resolve directory as
well: directory rules cover children created after ruleset application,
so they survive the replacement.

The failure was also undiagnosable: nono why reasons from the path spec
and reported ALLOWED while the kernel denied. Record each file grant's
(dev, ino) in the capability state file at sandbox start, and have path
queries re-stat the granted path: a stale file grant now reports a
stale_file_grant denial naming the inode change (or, when a directory
grant still covers the path, stays ALLOWED with a warning about the
stale grant).

Co-Authored-By: Fable 5 (nono) <noreply@anthropic.com>
Signed-off-by: Felix Konstantin Maurer <felix@wave.com>
… unreadable

The stale-file-grant detection made every `nono why` query load
NONO_CAP_FILE through the strict loader, which exits on any failure.
Inside a sandbox the state file is frequently not covered by a read
grant, so plain `nono why --path ...` — which never touched the state
before — started failing with "Error reading capability state file"
instead of answering. Load the state leniently for non-self queries:
any failure degrades to no staleness hints, restoring the previous
behavior. `--self` queries, whose whole answer comes from the state
file, keep the strict fail-loud loader.

Co-Authored-By: Fable 5 (nono) <noreply@anthropic.com>
Signed-off-by: Felix Konstantin Maurer <felix@wave.com>
…agnostic

Co-Authored-By: Fable 5 (nono) <noreply@anthropic.com>
Signed-off-by: Felix Konstantin Maurer <felix@wave.com>
A device number without an inode (or vice versa) identifies nothing, so
the previous (Option<u64>, Option<u64>) return implied states that don't
exist. Keep the split into two scalar keys at the serialization boundary
only.

Co-Authored-By: Fable 5 (nono) <noreply@anthropic.com>
Signed-off-by: Felix Konstantin Maurer <felix@wave.com>
@github-actions github-actions Bot added bug Something isn't working nono-cli size/large labels Jul 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

PR Review Summary

Size

Metric Value
Lines added +602
Lines removed -12
Total changed 614
Classification Large (> 300 lines)

Affected crates

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

Blast radius — Moderate

This PR touches: source code,configuration / policy files


Updated automatically on each push to this PR.

@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; 0 findings.

Findings: none flagged in scope.

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

@lukehinds
lukehinds self-requested a review July 24, 2026 13:06
@lukehinds

Copy link
Copy Markdown
Contributor

Thanks @maufl , I am good with the /run/systemd/resolve grant, there is nothing private there , the alternative (broken DNS on every resolver-config change) is much worse. approved

@lukehinds
lukehinds merged commit 1d3db0e into nolabs-ai:main Jul 24, 2026
16 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

Development

Successfully merging this pull request may close these issues.

2 participants