Skip to content

fix(containment): restrict additional Linux terminal controls - #240

Open
kevin-orellana wants to merge 2 commits into
mainfrom
feature/linux-terminal-controls
Open

kevin-orellana wants to merge 2 commits into
mainfrom
feature/linux-terminal-controls

Conversation

@kevin-orellana

@kevin-orellana kevin-orellana commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Human readable section

Problem. An untrusted program can share the operator's terminal and interfere with its input. Box already blocks one way to inject input. It still permits Linux console paste requests and changes to the terminal's input processing.

Solution. Block those two additional requests. In the disposable-terminal test, the original code lets the program disable input processing. With this change, input continues to work. Tests also retain input/output, terminal settings, window sizing, and Ctrl+C behavior.

Trade-off. Programs that need these requests receive a permission error. Other Linux text-console controls remain available, including keyboard and display mode changes. This change does not fully isolate a shared terminal. Tests confirm request denial through a software terminal, but do not reproduce selection or paste on a real Linux text console.

Description

Extend the request restrictions added in #126 with TIOCLINUX and TIOCSETD. Keep TIOCSTI restricted. Compare each request's low 32 bits, matching Linux argument handling.

TIOCLINUX includes console selection and paste operations. The filter checks argument values, but cannot read the subcommand through its pointer. It therefore blocks every TIOCLINUX subcommand. A line discipline is the kernel component that processes terminal input. TIOCSETD replaces that component. Neither request is needed by the ordinary interactive controls exercised here.

Upstream Linux 6.12 permits KDSKBMODE, KDSETMODE, VT_SETMODE, and VT_ACTIVATE when the descriptor is the caller's controlling terminal. KDSKBMETA has no equivalent permission check in its dispatch branch. These requests remain outside this PR's restricted set. This is a source-confirmed residual risk; its effects were not reproduced on a real console here. See the kernel dispatch and keyboard mode handling.

The disposable-terminal test checks effects outside the untrusted program. The original code lets the program change the shared input processor to N_NULL, which breaks input. With the fix, the outside observer sees the original input processor and working input.

Five terminal tests cover requests with nonzero upper 32 bits, unchanged input processing and queued input, normal terminal operations, and foreground interrupts. They consolidate the two older terminal tests, preserve their pixel-size and filtered-settings assertions, and share one fork harness. Removing either added restriction makes the corresponding denial tests fail. The successful-operation controls also pass on the baseline.

Authored and reviewed with Codex assistance.

Related Issues

Related to #51. Extends #126. This does not close the broader issue.
Follow-up #253 tracks the remaining virtual-console restrictions and their required validation.

Type of Change

Bug fix

Testing

Current hosted checks. The continuous integration gate passed for c47b6bd2c1dcb498b5a675e129cb237df9892667 on November 1, 2026. This status is separate from the local and historical test results below. The optional Verdict job skipped and provides no coverage.

Validation covers the source committed as c47b6bd2c1dcb498b5a675e129cb237df9892667, with EXPERIMENTAL_LINUX_SUPPORT=TRUE on native ARM64 Amazon Linux 2023, kernel 6.12.113-138.209.amzn2023.aarch64.

  • Workspace: 4,578 passed, zero failed, 17 ignored across 139 test groups. Four additional skip messages cover an absent installed-alias fixture, removed cardinality scaffolding, an unsupported Linux execute-only assertion, and an unavailable credential daemon. These skips do not count as coverage.
  • Containment: 551 passed, zero failed, zero ignored, with all features and no runtime skip messages. The count fell by two because the duplicate terminal tests were consolidated.
  • Deterministic integration: 124 passed, zero failed, zero errors, nine declared macOS-only skips across all 133 expected records; verdict GREEN. Its older console characterization note is not terminal-denial evidence.
  • Independent adversarial review found no blocking defect. Deliberately corrupting only the child's pixel-size result or refusing only its terminal-settings query failed the retained assertions. A deliberately hung query failed after five seconds. The clean control passed.
  • Containment builds passed with all features and with no default features. Workspace formatting and lint passed. Workspace lint used the exact vendored-code allowances in just clippy; strict containment lint needed none. just itself is unavailable on the host.
  • Builds used two jobs on a disk-backed filesystem. Scoped and workspace tests used two threads; deterministic integration used eight.

At the earlier 16ea76d revision, the disposable full-CLI terminal harness passed 17 scenarios, including 12 request denials, input/output, raw-mode restoration, window queries/resizing, and Ctrl+C with foreground restoration. Baseline and separate restriction-removal controls detected both added denials, including requests with nonzero upper 32 bits. Four moved test bodies remain byte-identical; the current follow-up changes only the normal-operation assertions and shared harness, plus a private production-helper name. These earlier removal runs were not repeated or presented as new runs. The first historical workspace attempt omitted the Linux opt-in and failed at startup; corrected runs passed.

Environment limits. A real virtual console is unavailable to the test user. Pseudoterminal tests prove that console requests are denied, but cannot reproduce actual console selection/paste. The host kernel already disables legacy TIOCSTI; invalid-descriptor comparisons separately attribute refusal to the filter. The example runner found no scripted examples. No live SDK tutorial, minimum-kernel, root, macOS, or cloud-qualification run was performed locally. Hosted platform checks remain separate evidence.

  • I ran the relevant suites (just check, or cargo test --workspace --all-features)
  • If I touched Rust, I ran cargo fmt --all and cargo clippy --workspace --all-targets --all-features -- -D warnings

The workspace lint command included the vendored-code allowances from the documented just clippy recipe. The host lacks just, so the equivalent commands ran directly. Strict containment lint used no allowances.

Checklist

  • I have read the CONTRIBUTING document
  • I have reviewed and understand every line of code in this PR, including any generated by AI tools, and I can explain why it works
  • My change is focused and reasonably small; I have split unrelated work into separate PRs
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • My changes generate no new warnings

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

## Problem

The inherited terminal remains a host resource. The existing TIOCSTI refusal leaves console controls and line-discipline changes reachable from an untrusted workload.
The baseline terminal_discipline_cannot_change_or_break_input test demonstrates that TIOCSETD can replace terminal input processing.

## Solution

Refuse TIOCLINUX and TIOCSETD beside TIOCSTI in the existing deny-only filter. Compare the low 32 request bits, as the kernel does.
Keep ordinary terminal operations permitted. Document the restricted requests and remaining terminal limits. No public API or policy grant changes.

## Tests

The containment suite passes 553 tests with all features. The five added tests cover truncated request arguments, unchanged terminal discipline and input queue, normal terminal operations, and foreground interrupts.
Baseline and separate TIOCLINUX/TIOCSETD removal controls fail the relevant denial tests. Successful-operation controls pass on both baseline and repaired builds.
A disposable full-CLI harness passes 17 scenarios, including input/output, resizing, interrupts, and restoration.
Native validation uses ARM64 Amazon Linux 2023 and kernel 6.12.111. A real virtual console is unavailable; this does not claim console effect coverage, full terminal isolation, or cloud qualification.
@kevin-orellana
kevin-orellana marked this pull request as ready for review October 30, 2026 18:09
@github-actions github-actions Bot added Area - Agent Containment bug Something isn't working labels Oct 30, 2026
@kevin-orellana
kevin-orellana enabled auto-merge (squash) October 30, 2026 18:15
@github-actions

Copy link
Copy Markdown

Assessment: Comment

The filter change is correct for the PTY case, which is the common one, and I found no blocking defect. My one substantive finding is about scope: on a Linux virtual console, some console controls are still reachable, and the agent guidance does not record this residual.

Review Categories
  • Scope: The VT perm-only ioctls (KDSKBMODE, KDSETMODE, VT_*) still change console input and display. libc has no names for them, so I suggest documenting the residual now and adding the filter in a follow-up.
  • Tests: The new module and namespace/mod.rs both own the request-truncation fact and the filtered-child harness.

Checked and held:

  • Low-32-bit Dword matching denies requests with nonzero upper bits. I ran the 5 new tests locally on x86_64 with kernel 7.0, and all passed.
  • On a PTY, TIOCSTI/TIOCLINUX/TIOCSETD are refused, and termios, window-size, read/write, and SIGINT still work.
  • Function-key and keymap injection (KDSKBENT/KDSKBSENT) and TIOCSCTTY steal are already closed by kernel capability checks.
  • Constants come from libc per arch, as containment/AGENTS.md requires.
  • No frozen surface or pub use change. The restriction stays a deny-only floor beneath policy.
  • Inputs that were valid before (for example, TIOCLINUX probes on a PTY) change only from ENOTTY to EPERM.

The disposable-terminal tests that compare against an unfiltered baseline are strong evidence for this fix.

@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown

Assessment: Approve

I re-reviewed at c47b6bd. Both earlier findings are fixed, and the new commit introduces no new defect.

Re-verified at this commit
  • VT residual: crates/containment/AGENTS.md no longer contains the stale "nothing filters TIOCSTI" paragraph. That file and docs/design/linux-enforcement.md now name the filtered set and the permitted VT controls, and Linux: restrict inherited virtual-console keyboard and display controls #253 tracks the follow-up. I checked the KDSKBMETA claim against the kernel source. The v6.12 vt_k_ioctl has no perm test for it. Current master adds if (!perm), so the version-pinned wording is accurate.
  • Test ownership: The old terminal_input_injection_is_refused_before_descriptor_lookup and terminal_queries_still_work_under_the_filters tests are gone, and no references remain. tests/terminal.rs reuses mod.rs::in_child. Its modes test keeps the four-field window-size check and the filtered tcgetattr check. The helper is now terminal_control_rules.
  • Local run (x86_64, kernel 7.0): all 5 tests in tests::terminal pass, and cargo clippy --all-targets --all-features -D warnings is clean for containment.

Checked and held:

  • Denial uses only the low 32 bits of the request.
  • PTY refusal works with ordinary terminal operations still permitted.
  • Request numbers come only from libc.
  • No frozen-surface change.
  • The rules add only deny-only floors beneath the policy.

This branch has not been deployed

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

Labels

Area - Agent Containment bug Something isn't working

2 participants