You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
supervisor: Landlock hard_requirement aborts with "incompatible directory-only access-rights: ReadDir" for real directories on RHEL 9.6 (kernel 5.14) ABI #2218
On a Kubernetes (OpenShift) deployment backed by RHCOS = RHEL 9.6, kernel 5.14.0, openshell sandbox create with a policy whose landlock.compatibility: hard_requirement fails at sandbox preparation:
Failed to prepare sandbox: incompatible directory-only access-rights: BitFlags<AccessFs>(0b1000, ReadDir)
This happens even when the policy's filesystem_policy lists only real directories (no device files, no symlinks). ReadDir (0b1000 = LANDLOCK_ACCESS_FS_READ_DIR) is a standard directory-only access right that has existed since Landlock ABI v1 (kernel 5.13), yet the supervisor rejects it as incompatible on this kernel's (RHEL-backported) Landlock ABI instead of applying it — or gracefully restricting to the supported subset.
Setting landlock.compatibility: best_effort brings the workspace up cleanly. This confirms the issue is specific to the hard_requirement ABI/access-right compatibility check.
Loaded the create-github-issue and debug-openshell-cluster skills and traced this error through current OpenShell main and the pinned landlock crate (0.4.4).
Finding
This is a real OpenShell bug, but the primary cause is path-type handling rather than the RHEL kernel rejecting ReadDir as an ABI feature.
OpenShell currently builds one access mask per policy class:
read-write paths receive AccessFs::from_all(ABI::V2), which also contains directory-only rights.
Those masks are passed unchanged to PathBeneath::new() for every path in crates/openshell-supervisor-process/src/sandbox/linux/landlock.rs, regardless of whether the opened path is a directory or a regular/device file.
The effective policy contains non-directory paths even when the user-authored filesystem policy lists only directories:
proxy baseline enrichment adds /dev/urandom as read-only;
the restrictive default also includes /dev/urandom and /dev/null;
GPU enrichment may add /dev/nvidia* or /dev/dxg device nodes.
The landlock crate calls fstat() on each PathFd. When the target is not a directory, ReadDir and other directory-only bits are incompatible. In BestEffort mode the crate removes those superfluous bits; in HardRequirement mode it returns exactly:
That explains why changing only landlock.compatibility makes the sandbox start. It does not indicate that RHEL's Landlock ABI lacks ReadDir; it indicates that a directory-only permission was attached to a non-directory PathFd.
Recommended fix
OpenShell should determine each opened path's file type before constructing the rule and tailor the allowed mask explicitly:
directories retain AccessFs::from_read() or AccessFs::from_all();
non-directories intersect the intended mask with AccessFs::from_file(abi).
This preserves hard_requirement semantics for kernel/ABI support while removing rights that are nonsensical for the target file type. Relying on per-rule best-effort compatibility would be less precise because it could also hide genuinely unsupported access rights.
Regression coverage should include, in hard-requirement mode:
a read-only regular/device file such as /dev/urandom;
a read-write file such as /dev/null;
ordinary read-only and read-write directories;
mixed directory and device-node baseline paths;
verification that truly unsupported ABI rights still fail closed.
The current best_effort setting is a functional workaround, but it weakens the operator's requested fail-closed guarantee and should not be treated as the resolution. No duplicate issue or active implementation PR was found.
The failure is reproducible from the current implementation and is not a RHEL Landlock ABI deficiency. OpenShell applies directory-only access rights to non-directory baseline paths, which the landlock crate correctly rejects under hard_requirement.
Investigation
openshell-supervisor-process constructs one read-only and one read-write AccessFs mask, then applies each mask unchanged to every PathBeneath. Effective policies include non-directories such as /dev/urandom, /dev/null, and optional GPU device nodes. landlock 0.4.4 uses fstat() and returns DirectoryAccess { incompatible: ReadDir } for these rules in hard-requirement mode; best-effort mode silently removes the nonsensical bits, explaining the reported workaround.
Recommendation
Keep the issue open as a confirmed supervisor compatibility bug. Tailor each rule from the already-opened file descriptor: retain directory masks for directories and intersect non-directory masks with AccessFs::from_file(abi). Add hard-requirement regression coverage for read-only/read-write files, directories, mixed baseline paths, and genuinely unsupported ABI rights. The GitHub issue is already typed as Bug; implementation still requires the human state:agent-ready decision.
Issue type:fix Complexity: Medium Confidence: High — clear path
Summary
The supervisor currently applies class-wide Landlock access masks to every opened policy path. OpenShell's baseline policy enrichment adds non-directory paths such as /dev/urandom and /dev/null; in hard_requirement mode, rust-landlock correctly rejects directory-only rights such as ReadDir when those masks are attached to a file or device. The fix will classify the inode through the already-open PathFd and remove directory-only rights for non-directories while preserving the requested access level and the existing fail-closed compatibility behavior.
Scope
crates/openshell-supervisor-process/src/sandbox/linux/landlock.rs: derive each rule's access mask from the opened path type, using fstat on the same descriptor to avoid a path-classification race; add focused unit coverage.
e2e/python/test_sandbox_landlock.py: add a hard_requirement regression covering a policy with both directories and baseline file/device paths, while retaining allow/deny enforcement assertions.
architecture/security-policy.md: document that Landlock rule rights are tailored to the opened inode type and that hard_requirement still fails on genuine preparation or ABI errors.
Implementation Steps
Add a small helper that inspects the already-open PathFd with fstat. Keep the intended access mask unchanged for directories and intersect non-directory masks with AccessFs::from_file(ABI::V2).
Use the tailored mask when adding both read-only and read-write PathBeneath rules. Propagate classification failures and preserve the existing global handled-access mask and compatibility levels so unsupported Landlock capabilities still fail closed.
Add unit tests for directory masks, regular files, character devices, and symlink targets, covering read-only and read-write rules and ensuring no rule receives an empty or broader mask.
Add a Linux E2E regression using landlock.compatibility: hard_requirement with ordinary directories plus baseline device paths. Verify expected reads/writes succeed and writes to read-only or outside-policy paths remain denied.
Update the security-policy architecture note with the per-inode rule behavior and hard-requirement invariant.
Test Plan
Unit tests: Extend the landlock.rs test module to verify directories retain directory rights, non-directories receive only file-compatible rights, symlinks are classified through their opened target, and fstat errors propagate.
Integration tests: N/A — rule preparation is covered directly by unit tests and the full sandbox boundary is exercised by E2E.
E2E tests: Extend e2e/python/test_sandbox_landlock.py with a hard_requirement mixed-path case that reproduces the baseline device-path failure and rechecks filesystem allow/deny enforcement. Run the relevant Linux sandbox E2E lane in addition to mise run pre-commit.
Risks & Open Questions
This code is part of the filesystem security boundary. The proposed transformation is permission-subtractive for non-directories and mirrors rust-landlock's path-type handling; it must not downgrade per-rule or global compatibility to best_effort.
Classification must use the same open descriptor passed to Landlock. A separate path metadata lookup would introduce a TOCTOU window and is out of scope.
Regular files, device nodes, FIFOs, and sockets should all use the non-directory mask. Newer-ABI device IOCTL controls and unrelated compatibility-value parsing are not part of this fix.
The affected E2E requires a Linux host with Landlock support; CI or a maintainer-run E2E lane may be needed if the local development host cannot execute it.
Documentation Impact
Update architecture/security-policy.md to describe per-inode Landlock rule masks and confirm that hard_requirement continues to reject genuine unsupported or failed setup. No gateway configuration keys, defaults, Helm rendering, or compute-driver setup change.
Current main still passes AccessFs::from_read(ABI::V2) or AccessFs::from_all(ABI::V2) unchanged to every PathBeneath rule in crates/openshell-supervisor-process/src/sandbox/linux/landlock.rs.
Failed to prepare sandbox: incompatible directory-only access-rights:
BitFlags<AccessFs>(0b1000, ReadDir)
Removing /dev/urandom and /dev/null allows an offline sandbox to reach Ready, but that is not a valid downstream workaround. A non-empty network policy activates effective-policy enrichment in crates/openshell-sandbox/src/lib.rs, which adds /dev/urandom back before preparation. The same failure then occurs without making any external network request.
This affects AI-Q's shipped network-enabled policy, and the same generic path can affect GPU device nodes such as /dev/nvidia* and /dev/dxg.
Proposed generic solution
After PathFd::new() succeeds, inspect metadata through that exact opened descriptor (for example, fstat) to avoid a pathname TOCTOU race.
Directories retain the full requested mask.
Every non-directory inode receives requested_access & AccessFs::from_file(abi).
Keep ABI::V2, hard_requirement, best_effort, existing path-open/error handling, OCSF behavior, privilege ordering, and the global handled-access mask unchanged.
Do not special-case /dev/null, /dev/urandom, or device types.
This is permission-subtractive for non-directories and preserves fail-closed behavior for genuine ABI/setup failures.
Validation plan
Focused Rust tests should prove:
directories retain their requested directory and file-content rights;
file masks contain no rights outside AccessFs::from_file(abi).
For the Linux Docker E2E, prefer the existing Rust SandboxGuard pattern in the mise run e2e:docker lane. Use hard_requirement, /dev/urandom read-only, /dev/null read-write, and a non-empty network policy. Verify the sandbox reaches Ready, reads 16 random bytes, writes to /dev/null, denies a write outside the allowlist, and is deleted even on assertion failure. No successful external egress is required.
Downstream AI-Q validation should keep its shipped policy unchanged, network policies enabled, and Landlock at hard_requirement; all three Linux live OpenShell integration tests should pass against locally built fixed images, effective-policy attestation should succeed, and cleanup should leave no sandboxes. The AI-Q version bump should remain a separate PR after a fixed OpenShell release is published.
Activity
TaylorMutch commentedon Jul 10, 2026
Agent Diagnostic
Loaded the
create-github-issueanddebug-openshell-clusterskills and traced this error through current OpenShellmainand the pinnedlandlockcrate (0.4.4).Finding
This is a real OpenShell bug, but the primary cause is path-type handling rather than the RHEL kernel rejecting
ReadDiras an ABI feature.OpenShell currently builds one access mask per policy class:
AccessFs::from_read(ABI::V2), which containsExecute | ReadFile | ReadDir;AccessFs::from_all(ABI::V2), which also contains directory-only rights.Those masks are passed unchanged to
PathBeneath::new()for every path incrates/openshell-supervisor-process/src/sandbox/linux/landlock.rs, regardless of whether the opened path is a directory or a regular/device file.The effective policy contains non-directory paths even when the user-authored filesystem policy lists only directories:
/dev/urandomas read-only;/dev/urandomand/dev/null;/dev/nvidia*or/dev/dxgdevice nodes.The
landlockcrate callsfstat()on eachPathFd. When the target is not a directory,ReadDirand other directory-only bits are incompatible. InBestEffortmode the crate removes those superfluous bits; inHardRequirementmode it returns exactly:That explains why changing only
landlock.compatibilitymakes the sandbox start. It does not indicate that RHEL's Landlock ABI lacksReadDir; it indicates that a directory-only permission was attached to a non-directoryPathFd.Recommended fix
OpenShell should determine each opened path's file type before constructing the rule and tailor the allowed mask explicitly:
AccessFs::from_read()orAccessFs::from_all();AccessFs::from_file(abi).This preserves
hard_requirementsemantics for kernel/ABI support while removing rights that are nonsensical for the target file type. Relying on per-rule best-effort compatibility would be less precise because it could also hide genuinely unsupported access rights.Regression coverage should include, in hard-requirement mode:
/dev/urandom;/dev/null;The current
best_effortsetting is a functional workaround, but it weakens the operator's requested fail-closed guarantee and should not be treated as the resolution. No duplicate issue or active implementation PR was found.TaylorMutch commentedon Jul 10, 2026
jyaunches commentedon Jul 20, 2026
Implementation Plan
Issue type:
fixComplexity: Medium
Confidence: High — clear path
Summary
The supervisor currently applies class-wide Landlock access masks to every opened policy path. OpenShell's baseline policy enrichment adds non-directory paths such as
/dev/urandomand/dev/null; inhard_requirementmode, rust-landlock correctly rejects directory-only rights such asReadDirwhen those masks are attached to a file or device. The fix will classify the inode through the already-openPathFdand remove directory-only rights for non-directories while preserving the requested access level and the existing fail-closed compatibility behavior.Scope
crates/openshell-supervisor-process/src/sandbox/linux/landlock.rs: derive each rule's access mask from the opened path type, usingfstaton the same descriptor to avoid a path-classification race; add focused unit coverage.e2e/python/test_sandbox_landlock.py: add ahard_requirementregression covering a policy with both directories and baseline file/device paths, while retaining allow/deny enforcement assertions.architecture/security-policy.md: document that Landlock rule rights are tailored to the opened inode type and thathard_requirementstill fails on genuine preparation or ABI errors.Implementation Steps
PathFdwithfstat. Keep the intended access mask unchanged for directories and intersect non-directory masks withAccessFs::from_file(ABI::V2).PathBeneathrules. Propagate classification failures and preserve the existing global handled-access mask and compatibility levels so unsupported Landlock capabilities still fail closed.landlock.compatibility: hard_requirementwith ordinary directories plus baseline device paths. Verify expected reads/writes succeed and writes to read-only or outside-policy paths remain denied.Test Plan
landlock.rstest module to verify directories retain directory rights, non-directories receive only file-compatible rights, symlinks are classified through their opened target, andfstaterrors propagate.e2e/python/test_sandbox_landlock.pywith ahard_requirementmixed-path case that reproduces the baseline device-path failure and rechecks filesystem allow/deny enforcement. Run the relevant Linux sandbox E2E lane in addition tomise run pre-commit.Risks & Open Questions
best_effort.Documentation Impact
Update
architecture/security-policy.mdto describe per-inode Landlock rule masks and confirm thathard_requirementcontinues to reject genuine unsupported or failed setup. No gateway configuration keys, defaults, Helm rendering, or compute-driver setup change.Revision 1 — initial plan
KyleZheng1284 commentedon Jul 20, 2026
Additional Ubuntu 24.04 reproduction and downstream validation context
This adds a second, independent reproduction for the path-type diagnosis already captured above.
Agent diagnostic
0.0.80on Ubuntu 24.04.2 LTS, kernel6.8.0-57-generic, x86_64, Docker client/server28.3.3.v0.0.87.maininspected:9377e0d5fefb3ae13e4b46aa06c8801af1f9fcf5.Current
mainstill passesAccessFs::from_read(ABI::V2)orAccessFs::from_all(ABI::V2)unchanged to everyPathBeneathrule incrates/openshell-supervisor-process/src/sandbox/linux/landlock.rs.Ubuntu reproduction
The policy used:
Sandbox preparation fails with:
Removing
/dev/urandomand/dev/nullallows an offline sandbox to reachReady, but that is not a valid downstream workaround. A non-empty network policy activates effective-policy enrichment incrates/openshell-sandbox/src/lib.rs, which adds/dev/urandomback before preparation. The same failure then occurs without making any external network request.This affects AI-Q's shipped network-enabled policy, and the same generic path can affect GPU device nodes such as
/dev/nvidia*and/dev/dxg.Proposed generic solution
After
PathFd::new()succeeds, inspect metadata through that exact opened descriptor (for example,fstat) to avoid a pathname TOCTOU race.requested_access & AccessFs::from_file(abi).ABI::V2,hard_requirement,best_effort, existing path-open/error handling, OCSF behavior, privilege ordering, and the global handled-access mask unchanged./dev/null,/dev/urandom, or device types.This is permission-subtractive for non-directories and preserves fail-closed behavior for genuine ABI/setup failures.
Validation plan
Focused Rust tests should prove:
AccessFs::from_read(abi) & AccessFs::from_file(abi);AccessFs::from_all(abi) & AccessFs::from_file(abi);AccessFs::from_file(abi).For the Linux Docker E2E, prefer the existing Rust
SandboxGuardpattern in themise run e2e:dockerlane. Usehard_requirement,/dev/urandomread-only,/dev/nullread-write, and a non-empty network policy. Verify the sandbox reachesReady, reads 16 random bytes, writes to/dev/null, denies a write outside the allowlist, and is deleted even on assertion failure. No successful external egress is required.Downstream AI-Q validation should keep its shipped policy unchanged, network policies enabled, and Landlock at
hard_requirement; all three Linux live OpenShell integration tests should pass against locally built fixed images, effective-policy attestation should succeed, and cleanup should leave no sandboxes. The AI-Q version bump should remain a separate PR after a fixed OpenShell release is published.KyleZheng1284 commentedon Jul 20, 2026
@jyaunches
fix(supervisor): tailor Landlock rights by inode type (NVIDIA#2380)