Skip to content

bug: the root allow-list is implemented twice and the audited helper has no caller #5190

Closed

Description

@EmilienM

User Story

I run OpenShell sandboxes with the Podman and Kubernetes drivers, and I read through the isolation core (openshell-sandbox, openshell-binary-identity, openshell-isolation-interface) at 00528d24a. This item comes from reading the code. I have not hit it in production. It is one line of the umbrella issue #5179.

Problem Statement

crates/openshell-sandbox/src/linux/landlock.rs:76 defines open_root_allowlist and RootEntryHandle, documented as the audited primitive in linux/mod.rs: it opens only named root entries and checks identity before and after openat. It has no production caller (only its own tests use it). The mandatory baseline instead uses capability_free_baseline_entries in sandbox/linux/landlock.rs:176, which lists / by path with std::fs::read_dir and keeps every entry except the private name. Its comment says the private root is pinned and validated, but _private_root (line 191) is a local binding that is dropped when the function returns, before the lazy iterator opens any entry.

Impact / Why This Matters

Two implementations of the same mechanism will drift. Reviewers who audit crate::linux are not auditing the code that runs, and one comment overstates what the code holds.

Acceptance Criteria

  • One implementation of the root enumeration lives in crate::linux::landlock and the baseline uses it, or the unused helper and its contract text are removed.
  • The private-root comment matches what the code does (keep the fd alive inside the iterator or reword).
  • Existing baseline tests still pass.

Reproduction Steps

Found by reading the code; not reproduced live. rg open_root_allowlist crates/ and confirm there is no non-test caller.

Environment

  • OpenShell: main at 00528d24a
  • OS: Fedora 44, Linux 7.2.14
  • Deployment: Podman driver (local gateway); code read for the Kubernetes driver too

Activity

  1. added
    state:acceptedA maintainer decided OpenShell should pursue this issue
    and removed
    state:triage-neededOpened without agent diagnostics and needs triage
    on Nov 3, 2026
  2. mrunalp commented on Nov 3, 2026

    @mrunalp
    Collaborator

    Accepted for implementation in #5221.

    Source inspection at 32dcc4a67: The audited open_root_allowlist helper has no production caller; the baseline uses a separate enumeration path and drops the private-root validation descriptor before lazy entry opening. Removing the unused helper and correcting the lifetime comment is a suitable scoped cleanup.

    This assessment is based on the source and the linked report/PR; I have not independently run deployment reproductions or the PR test suite. CI and code review still need to validate the fix.

  3. added a commit that references this issue on Nov 4, 2026
    74eacee
  4. EmilienM commented on Nov 11, 2026

    @EmilienM
    ContributorAuthor

    Confirmed against main at 00528d24a, with notes on both ACs to help triage.

    On AC1 (remove vs unify): only abi_version from crate::linux::landlock runs in production today, via probe_availability in sandbox/linux/landlock.rs. Unifying the baseline onto open_root_allowlist doesn't fit the baseline's contract:

    • open_root_allowlist collects every handle up front in a Vec<RootEntryHandle>. The baseline has to stay lazy (add the rule, drop the fd, open the next entry) because of the FD limit; capability_free_baseline_prepares_under_low_descriptor_limit pins that behavior at RLIMIT_NOFILE=64.
    • It errors on symlinks. The baseline skips them on purpose: merged-/usr images symlink /bin, and those roots still have to work.
    • It expects an explicit allow-list of names from "trusted image/driver policy". No such name source exists anywhere in the tree.

    So the removal branch seems right: delete the helper, RootEntryHandle, validate_component, and their tests; keep abi_version; rewrite the module doc, which currently describes only the removed API. The statat-before/fstat-after race check stays in history if a real caller shows up later.

    On AC2: the validation half of the comment is accurate. The private root is opened with O_DIRECTORY|O_NOFOLLOW, so a symlinked .openshell is rejected on the spot, and capability_free_baseline_rejects_private_root_redirect covers that. "Pin" is the wrong word: the fd is dropped when the function returns, before the lazy iterator opens anything, and holding it wouldn't enforce anything the name check plus O_NOFOLLOW don't already do. Rewording the comment should be enough.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    state:acceptedA maintainer decided OpenShell should pursue this issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions