Skip to content

fix: allow tty ioctls on Linux v5+ - #310

Merged
lukehinds merged 2 commits into
nolabs-ai:mainfrom
josephgimenez:codex/issue-306-tty-ioctldev
Mar 10, 2026
Merged

fix: allow tty ioctls on Linux v5+#310
lukehinds merged 2 commits into
nolabs-ai:mainfrom
josephgimenez:codex/issue-306-tty-ioctldev

Conversation

@josephgimenez

@josephgimenez josephgimenez commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Fixes #306.

Summary

  • grant AccessFs::IoctlDev only for TTY device capabilities on Linux when the handled Landlock ABI supports it
  • keep IoctlDev scoped to /dev/tty and /dev/pts so non-device read-write paths do not gain ioctl permission
  • add Linux unit coverage for TTY path detection and the IoctlDev access mapping

Why

Landlock ABI v5+ enforces IoctlDev when it is part of the handled filesystem access set. nono already handled ABI v5 rights in the ruleset, but never granted IoctlDev to the TTY device paths that TUI programs use for tcsetattr and raw mode. That caused setRawMode to fail with EACCES.

Validation

Host

$ cargo test -p nono
Finished `test` profile [unoptimized + debuginfo] target(s) in 19.10s
running 470 tests
...
test result: ok

Linux harness

$ bash scripts/test-linux-container.sh cargo test -p nono sandbox::linux::tests:: -- --nocapture
running 27 tests
...
test sandbox::linux::tests::test_tty_paths_gain_ioctl_dev_when_supported ... ok
test sandbox::linux::tests::test_read_only_tty_path_does_not_gain_ioctl_dev ... ok
test sandbox::linux::tests::test_non_tty_paths_do_not_gain_ioctl_dev ... ok
test result: ok. 27 passed; 0 failed; 0 ignored; 0 measured; 434 filtered out
$ bash scripts/test-linux-container.sh cargo check -p nono-cli
Finished `dev` profile [unoptimized + debuginfo] target(s) in 8.44s
$ bash scripts/test-linux-container.sh bash -c \
    'command -v script >/dev/null && \
     command -v stty >/dev/null && \
     /usr/local/cargo/bin/cargo run -q -p nono-cli -- run --allow-cwd -- \
       script -q -c "stty raw -echo; stty sane; printf ok" /dev/null'
Applying Kernel sandbox protections.
Sandbox active. Restrictions are now in effect.

ok

Before/after Node repro

These runs use separate CARGO_TARGET_DIR values inside the Linux harness to avoid cross-worktree cache contamination.

Unpatched origin/main:

$ bash scripts/test-linux-container.sh bash -lc 'export CARGO_TARGET_DIR=/cache/target-unpatched-306-verify; \
    apt-get update >/dev/null && \
    apt-get install -y --no-install-recommends nodejs >/dev/null && \
    cat > /tmp/run-node-repro.sh <<'\''EOF'\''
#!/usr/bin/env bash
node -e '\''process.stdin.setRawMode(true); console.log("OK"); process.stdin.setRawMode(false);'\''
EOF
chmod +x /tmp/run-node-repro.sh
script -q -c '\''/usr/local/cargo/bin/cargo run -q -p nono-cli -- run --allow-cwd -- /tmp/run-node-repro.sh'\'' /dev/null
status=$?
rm -f /tmp/run-node-repro.sh
exit $status'
Applying Kernel sandbox protections.
Sandbox active. Restrictions are now in effect.

Error: setRawMode EACCES
...
code: 'EACCES',
syscall: 'setRawMode'

Patched branch:

$ bash scripts/test-linux-container.sh bash -lc 'export CARGO_TARGET_DIR=/cache/target-patched-306-verify; \
    apt-get update >/dev/null && \
    apt-get install -y --no-install-recommends nodejs >/dev/null && \
    cat > /tmp/run-node-repro.sh <<'\''EOF'\''
#!/usr/bin/env bash
node -e '\''process.stdin.setRawMode(true); console.log("OK"); process.stdin.setRawMode(false);'\''
EOF
chmod +x /tmp/run-node-repro.sh
script -q -c '\''/usr/local/cargo/bin/cargo run -q -p nono-cli -- run --allow-cwd -- /tmp/run-node-repro.sh'\'' /dev/null
status=$?
rm -f /tmp/run-node-repro.sh
exit $status'
Applying Kernel sandbox protections.
Sandbox active. Restrictions are now in effect.

OK

Linux harness environment

$ bash scripts/test-linux-container.sh bash -c 'uname -r'
6.12.68-linuxkit

The runtime nono run path still logs the hardcoded target ABI, so I probed the highest supported ABI separately by creating hard-requirement Landlock rulesets for descending ABI candidates.

$ # temporary one-off Landlock ruleset probe in the Linux harness
$ /usr/local/cargo/bin/cargo run --manifest-path tmp/landlock-probe/Cargo.toml
detected=V6

Notes

In this Docker environment, nono setup --check-only cannot use /sys/kernel/security/lsm because that path is not present in the container, so the ABI claim above comes from direct Landlock ruleset probing rather than the setup command.

@github-actions github-actions Bot added the bug Something isn't working label Mar 10, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses an issue where TUI programs failed to operate correctly on Linux systems using Landlock ABI v5+ due to missing IoctlDev permissions for TTY devices. The changes introduce a mechanism to conditionally grant IoctlDev access exclusively to TTY paths, ensuring that terminal-based applications can perform necessary operations like setting raw mode, while maintaining a secure and minimal privilege model for other file system interactions.

Highlights

  • TTY Ioctl Access: Granted AccessFs::IoctlDev specifically for TTY device capabilities on Linux when the Landlock ABI supports it, resolving EACCES errors for TUI programs.
  • Scoped Access: Ensured IoctlDev access is strictly limited to /dev/tty and /dev/pts paths, preventing unintended widening of ioctl permissions for other read-write paths.
  • Unit Test Coverage: Added new Linux unit tests to cover TTY path detection and the conditional IoctlDev access mapping logic.
Changelog
  • crates/nono/src/sandbox/linux.rs
    • Imported FsCapability and Path for new functionality.
    • Modified access_to_landlock to conditionally include AccessFs::Truncate based on ABI support.
    • Introduced access_to_landlock_for_capability function to manage IoctlDev access for file system capabilities.
    • Added helper functions should_grant_tty_ioctl and is_tty_device_path to determine when TTY ioctl access is appropriate.
    • Updated the apply function to utilize the new access_to_landlock_for_capability for setting Landlock rules.
    • Expanded unit tests to cover scenarios for TTY device path detection and the conditional granting of IoctlDev access.
Activity
  • Validated changes using cargo test -p nono.
  • Validated changes using bash scripts/test-linux-container.sh cargo test -p nono sandbox::linux::tests:: -- --nocapture.
  • Validated changes using bash scripts/test-linux-container.sh cargo check -p nono-cli.
  • Validated changes using bash scripts/test-linux-container.sh bash -c 'command -v script >/dev/null && command -v stty >/dev/null && /usr/local/cargo/bin/cargo run -q -p nono-cli -- run --allow-cwd -- script -q -c "stty raw -echo; stty sane; printf ok" /dev/null'.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request correctly implements support for TTY ioctls on Linux with Landlock ABI v5+. The changes are well-structured, introducing new helper functions to encapsulate the logic for granting IoctlDev access. The logic correctly restricts this permission to TTY device paths that have write access, and only when the running kernel's Landlock ABI supports it. The addition of comprehensive unit tests ensures the new functionality is well-covered. The single minor suggestion to improve code conciseness has been retained.

Comment thread crates/nono/src/sandbox/linux.rs
Signed-off-by: Joseph Gimenez <joseph.gimenez@joingotu.com>
@josephgimenez
josephgimenez force-pushed the codex/issue-306-tty-ioctldev branch from b00b1bb to c0157ae Compare March 10, 2026 01:59
Signed-off-by: Joseph Gimenez <joseph.gimenez@joingotu.com>
@lukehinds
lukehinds merged commit 6106967 into nolabs-ai:main Mar 10, 2026
11 checks passed
lukehinds added a commit that referenced this pull request Mar 10, 2026
Replace the hardcoded TARGET_ABI V5 constant with runtime ABI detection
that probes V6 down to V1 using HardRequirement. The detected ABI is
threaded through sandbox application so access flags are intersected with
what the kernel actually supports, with tracing warnings when flags are
dropped. This makes enforcement degradation explicit instead of relying
on the landlock crate's silent BestEffort flag masking.

apply_with_abi() uses HardRequirement for filesystem handle_access(),
so passing a forged or stale ABI higher than the kernel supports will
fail rather than silently dropping flags.

Supersede the IoctlDev fix from #310 (hardcoded TTY path list) with
stat()-based device detection: IoctlDev is granted only when the path
is an actual char/block device or a directory under /dev, checked at
rule-addition time. Remove /dev/pts from system_write_linux in
policy.json — /dev/tty (the process's controlling terminal) is
sufficient for the base policy. Programs needing PTY allocation can
add /dev/pts in their specific profiles.

Library changes:
- Add DetectedAbi struct with feature query methods (has_refer,
  has_truncate, has_network, has_ioctl_dev, has_scoping)
- Add detect_abi() probing V6..V1 with HardRequirement
- Add apply_with_abi() with kernel-validated ABI
- Add is_device_path() and is_device_directory() for selective
  IoctlDev grants
- Remove IoctlDev from generic Write flags and #310's
  is_tty_device_path / access_to_landlock_for_capability
- Update is_supported() and support_info() to use detect_abi()

CLI changes:
- Replace local probe_landlock_abi / probe_landlock_abi_candidate /
  select_highest_supported_landlock_abi / landlock_feature_lines with
  library detect_abi() and DetectedAbi
- Add ABI info to banner output on Linux (version + features + degraded)
- Direct mode uses apply_with_abi() with pre-detected ABI
- Remove /dev/pts from system_write_linux base policy

Closes #256 #306

Signed-off-by: Luke Hinds <lukehinds@gmail.com>
lukehinds added a commit that referenced this pull request Mar 10, 2026
* feat: ABI-aware Landlock capability system (#256, #306)

Replace the hardcoded TARGET_ABI V5 constant with runtime ABI detection
that probes V6 down to V1 using HardRequirement. The detected ABI is
threaded through sandbox application so access flags are intersected with
what the kernel actually supports, with tracing warnings when flags are
dropped. This makes enforcement degradation explicit instead of relying
on the landlock crate's silent BestEffort flag masking.

apply_with_abi() uses HardRequirement for filesystem handle_access(),
so passing a forged or stale ABI higher than the kernel supports will
fail rather than silently dropping flags.

Supersede the IoctlDev fix from #310 (hardcoded TTY path list) with
stat()-based device detection: IoctlDev is granted only when the path
is an actual char/block device or a directory under /dev, checked at
rule-addition time. Remove /dev/pts from system_write_linux in
policy.json — /dev/tty (the process's controlling terminal) is
sufficient for the base policy. Programs needing PTY allocation can
add /dev/pts in their specific profiles.

Library changes:
- Add DetectedAbi struct with feature query methods (has_refer,
  has_truncate, has_network, has_ioctl_dev, has_scoping)
- Add detect_abi() probing V6..V1 with HardRequirement
- Add apply_with_abi() with kernel-validated ABI
- Add is_device_path() and is_device_directory() for selective
  IoctlDev grants
- Remove IoctlDev from generic Write flags and #310's
  is_tty_device_path / access_to_landlock_for_capability
- Update is_supported() and support_info() to use detect_abi()

CLI changes:
- Replace local probe_landlock_abi / probe_landlock_abi_candidate /
  select_highest_supported_landlock_abi / landlock_feature_lines with
  library detect_abi() and DetectedAbi
- Add ABI info to banner output on Linux (version + features + degraded)
- Direct mode uses apply_with_abi() with pre-detected ABI
- Remove /dev/pts from system_write_linux base policy

Closes #256 #306

Signed-off-by: Luke Hinds <lukehinds@gmail.com>

* fix:rebased Landlock ABI follow-up

resolving the remaining sandbox/linux merge state and keeping the
ioctl narrowing intact.

This preserves the ABI-aware enforcement changes, removes the broad /dev/pts
write grant, and restores the test/build state expected by CI.

Signed-off-by: Luke Hinds <lukehinds@gmail.com>

* fix: remove stale imports

Signed-off-by: Luke Hinds <lukehinds@gmail.com>

---------

Signed-off-by: Luke Hinds <lukehinds@gmail.com>
lukehinds added a commit that referenced this pull request Mar 10, 2026
* feat: ABI-aware Landlock capability system (#256, #306)

Replace the hardcoded TARGET_ABI V5 constant with runtime ABI detection
that probes V6 down to V1 using HardRequirement. The detected ABI is
threaded through sandbox application so access flags are intersected with
what the kernel actually supports, with tracing warnings when flags are
dropped. This makes enforcement degradation explicit instead of relying
on the landlock crate's silent BestEffort flag masking.

apply_with_abi() uses HardRequirement for filesystem handle_access(),
so passing a forged or stale ABI higher than the kernel supports will
fail rather than silently dropping flags.

Supersede the IoctlDev fix from #310 (hardcoded TTY path list) with
stat()-based device detection: IoctlDev is granted only when the path
is an actual char/block device or a directory under /dev, checked at
rule-addition time. Remove /dev/pts from system_write_linux in
policy.json — /dev/tty (the process's controlling terminal) is
sufficient for the base policy. Programs needing PTY allocation can
add /dev/pts in their specific profiles.

Library changes:
- Add DetectedAbi struct with feature query methods (has_refer,
  has_truncate, has_network, has_ioctl_dev, has_scoping)
- Add detect_abi() probing V6..V1 with HardRequirement
- Add apply_with_abi() with kernel-validated ABI
- Add is_device_path() and is_device_directory() for selective
  IoctlDev grants
- Remove IoctlDev from generic Write flags and #310's
  is_tty_device_path / access_to_landlock_for_capability
- Update is_supported() and support_info() to use detect_abi()

CLI changes:
- Replace local probe_landlock_abi / probe_landlock_abi_candidate /
  select_highest_supported_landlock_abi / landlock_feature_lines with
  library detect_abi() and DetectedAbi
- Add ABI info to banner output on Linux (version + features + degraded)
- Direct mode uses apply_with_abi() with pre-detected ABI
- Remove /dev/pts from system_write_linux base policy

Closes #256 #306

Signed-off-by: Luke Hinds <lukehinds@gmail.com>

* fix:rebased Landlock ABI follow-up

resolving the remaining sandbox/linux merge state and keeping the
ioctl narrowing intact.

This preserves the ABI-aware enforcement changes, removes the broad /dev/pts
write grant, and restores the test/build state expected by CI.

Signed-off-by: Luke Hinds <lukehinds@gmail.com>

* fix: remove stale imports

Signed-off-by: Luke Hinds <lukehinds@gmail.com>

---------

Signed-off-by: Luke Hinds <lukehinds@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Landlock ABI V5+ IoctlDev breaks setRawMode for TUI programs

2 participants