Skip to content

Fix tui resize on linux#12

Merged
thomas-endres-tng merged 1 commit into
mainfrom
bugfix/linux-tui-resize
Jun 21, 2026
Merged

Fix tui resize on linux#12
thomas-endres-tng merged 1 commit into
mainfrom
bugfix/linux-tui-resize

Conversation

@GevatterGaul

@GevatterGaul GevatterGaul commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Summary

On Linux, an inner TUI launched through the built-in sandbox (e.g. opencode under
omac start) renders once at the correct size but never reflows when the surrounding
terminal is resized. This PR removes --new-session from the bwrap invocation, which
restores SIGWINCH resize delivery to the inner process.

The underlying problem is already partially solved in bubblewrap but was never merged: containers/bubblewrap#586

Problem

A terminal program learns its window size two ways:

  1. At startup, via a TIOCGWINSZ ioctl on its tty fd. This works regardless of
    controlling-terminal status — which is why the initial render is correctly sized.
  2. On every resize, by receiving SIGWINCH. The kernel only delivers SIGWINCH to
    the foreground process group of the controlling terminal.

We invoked bwrap with --new-session, which calls setsid(2). That places the entire
inner process tree (bwrap → omac sandbox stage2 → opencode) in a new session with no
controlling terminal
. With no controlling terminal, the kernel never delivers SIGWINCH
on resize, so the TUI never learns it should reflow. omac forwards only
SIGINT/SIGTERM/SIGHUP/SIGQUIT to the child group — not SIGWINCH — so nothing bridged
the gap either.

Fix

Remove --new-session from BuildBwrapArgv. The inner tree keeps the real controlling
terminal, the kernel delivers SIGWINCH on resize, and the TUI reflows correctly.

What --new-session was for

Its sole purpose is to block TIOCSTI terminal-input injection: a confined process
sharing the real terminal fd can call ioctl(fd, TIOCSTI, …) to push characters into the
terminal's input queue, which the outer shell then executes after omac exits — a
deferred sandbox escape into the host shell. This is the only capability we give up, and on
modern Linux it is already closed at the kernel level (see risk assessment).

Risk assessment

Scope: narrow. --new-session affects only session/controlling-terminal semantics. It
has no bearing on the mechanisms that confine the sandbox from the outside world, all
of which are unchanged:

Isolation property Provided by Affected?
Filesystem confinement --ro-bind/--bind mounts + Landlock path rules No
Network confinement filtering CONNECT proxy + Landlock net rules (stage2) No
Process/IPC isolation --unshare-pid / --unshare-ipc / --unshare-uts No
Lifetime coupling --die-with-parent No
Terminal-input injection --new-session Removed

Residual risk — TIOCSTI injection:

  • Linux ≥ 6.2 (default config): TIOCSTI is gated behind the dev.tty.legacy_tiocsti
    sysctl, which defaults to 0 (disabled). With it disabled, ioctl(TIOCSTI) returns
    EIO regardless of --new-session. On these systems dropping the flag loses nothing
    — the kernel already closes the vector. (Verified on the dev machine: kernel 7.0.11,
    dev.tty.legacy_tiocsti = 0.)
  • Kernels < 6.2, or any system with dev.tty.legacy_tiocsti = 1: the vector reopens. A
    confined process could queue input into the user's shell that executes unconfined after
    omac exits
    . Requires an interactive shell that reads from the shared terminal after the
    session ends.

Severity: Low for the project's threat model (confining the inner process from reaching
host filesystem/network/processes), which is fully preserved. The regression applies only
to the terminal-injection vector, only on pre-6.2 kernels or non-default sysctl configs,
and only against an interactive parent shell.

Alternatives considered:

  • Gate the removal on the sysctl — drop --new-session only when
    dev.tty.legacy_tiocsti == 0, keep it otherwise. Portable and safe on old kernels, at
    the cost of no resize in that rare case.
  • Allocate a private PTY (preferred long-term) — keep --new-session and give the inner
    process a PTY as its controlling terminal, forwarding window-size changes to the PTY
    master. Restores resize and closes TIOCSTI more completely (injection can only reach
    omac's own PTY master, never the real shell). Heavier (a PTY-forwarding loop); deferred
    as a follow-up.

This PR takes the direct removal: simplest change, full resize support, and zero isolation
loss on modern default-configured Linux.

Testing

  • Added a negative assertion in bwrap_test.go that fails if --new-session ever
    reappears in the argv.
  • go test ./internal/sandboxrun/ passes; gofmt clean.
  • Manually verified: resizing the terminal now reflows the opencode TUI under omac start.

Note

The historical spec docs under openspec/changes/native-sandbox/ still list
--new-session as required. Left untouched as a record of a completed change — if the
project treats them as living spec, I can update those three files to match.

@thomas-endres-tng thomas-endres-tng merged commit b869a08 into main Jun 21, 2026
3 of 4 checks passed
@thomas-endres-tng thomas-endres-tng deleted the bugfix/linux-tui-resize branch June 21, 2026 11:45
@thomas-endres-tng

Copy link
Copy Markdown
Contributor

Thanks for the fix

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants