site serve, site view and patch: show a VM's output without letting it control the terminal #254

Open
opened 2026-09-29 02:47:53 +01:00 by allod-agent · 0 comments
Member

Show what a VM prints on the hypervisor's terminal as text only, so a compromised VM cannot use its output to control or rewrite the owner's terminal.

Primary goals:

  • Control bytes from a VM never reach the terminal — anything a VM writes is shown with its control characters made visible, not acted on.
  • Ordinary diagnostics read as they do today — a refusal, a log tail or an ssh error from the VM is still shown, line for line.
  • One rule for every command that relays a VM's output — allod site view, allod site serve --vm and allod patch all pass through the same filter.

Current state

Commands run on the hypervisor start ssh to a VM and connect its output straight to the terminal. Whatever the VM writes arrives byte for byte, terminal control sequences included. A terminal acts on those: they can move the cursor, clear or overwrite what is on screen, and change the window title, so a VM can make the terminal show text the hypervisor never printed. The VM is the less trusted side: a VM never gains authority over the host.

This has not been run against a real hypervisor and VM. It is read from the code, at ef380c8:

Command What is relayed Receipt
allod site view the VM's standard error cmd/allod/site_serve.go:14, reached from cmd/allod/site_view.go:48
allod site serve --vm the VM's standard error and standard output cmd/allod/site_serve.go:14 and cmd/allod/site_serve.go:91
allod patch, reading from a VM the VM's standard error cmd/allod/patch.go:204
allod patch, transferring from a VM the VM's standard error cmd/allod/patch.go:212

runCommand (cmd/allod/exec.go:46) assigns the writer it is given to the child's output, and each caller above gives it the process's own standard error or standard output.

What a VM writes includes text it does not author itself: allod site serve prints the last lines of the preview unit's journal on failure, and those lines come from whatever the site's app logged.

allod site view already discards the VM's standard output (cmd/allod/site_view.go:48), so nothing on the hypervisor reads a value from it. That is unchanged by this issue.

Default

The output of an ssh to a VM goes through one writer before it reaches the terminal. The writer passes printable text, newline and tab, and replaces every other control byte, the escape character among them, with a visible form. It works on bytes as they arrive, holds no more than it needs to finish a multi-byte character, and never fails the write, so a VM that prints without end costs no memory and does not stall ssh.

Alternative, only if the default proves awkward: capture the VM's output and show it only when the remote command fails. It hides progress messages from a long start and needs a bound on what is kept.

Validation

  • A remote that writes control sequences on standard error and on standard output, through the test seam: the terminal side receives visible text and no control byte other than newline and tab.
  • A remote that writes an ordinary refusal and a log tail: shown unchanged.
  • A remote that writes several megabytes: memory does not grow with it.
  • A multi-byte character split across two writes arrives whole.

Scope

In: the three commands in the table, the shared writer, its tests. Out: what allod patch does with the bytes it captures from a VM; the pages a preview serves to the browser; the final ssh -L of allod site view. view becomes that ssh (cmd/allod/site_view.go:53), so nothing of allod is left to filter it; it runs no command in the VM, but anything the VM's ssh server sends the client, such as a banner, still reaches the terminal. Whether view should keep a process of its own around that ssh is a separate decision.

Show what a VM prints on the hypervisor's terminal as text only, so a compromised VM cannot use its output to control or rewrite the owner's terminal. Primary goals: - **Control bytes from a VM never reach the terminal** — anything a VM writes is shown with its control characters made visible, not acted on. - **Ordinary diagnostics read as they do today** — a refusal, a log tail or an `ssh` error from the VM is still shown, line for line. - **One rule for every command that relays a VM's output** — `allod site view`, `allod site serve --vm` and `allod patch` all pass through the same filter. ### Current state Commands run on the hypervisor start `ssh` to a VM and connect its output straight to the terminal. Whatever the VM writes arrives byte for byte, terminal control sequences included. A terminal acts on those: they can move the cursor, clear or overwrite what is on screen, and change the window title, so a VM can make the terminal show text the hypervisor never printed. The VM is the less trusted side: a VM never gains authority over the host. This has not been run against a real hypervisor and VM. It is read from the code, at `ef380c8`: | Command | What is relayed | Receipt | | --- | --- | --- | | `allod site view` | the VM's standard error | `cmd/allod/site_serve.go:14`, reached from `cmd/allod/site_view.go:48` | | `allod site serve --vm` | the VM's standard error and standard output | `cmd/allod/site_serve.go:14` and `cmd/allod/site_serve.go:91` | | `allod patch`, reading from a VM | the VM's standard error | `cmd/allod/patch.go:204` | | `allod patch`, transferring from a VM | the VM's standard error | `cmd/allod/patch.go:212` | `runCommand` (`cmd/allod/exec.go:46`) assigns the writer it is given to the child's output, and each caller above gives it the process's own standard error or standard output. What a VM writes includes text it does not author itself: `allod site serve` prints the last lines of the preview unit's journal on failure, and those lines come from whatever the site's app logged. `allod site view` already discards the VM's standard output (`cmd/allod/site_view.go:48`), so nothing on the hypervisor reads a value from it. That is unchanged by this issue. ### Default The output of an `ssh` to a VM goes through one writer before it reaches the terminal. The writer passes printable text, newline and tab, and replaces every other control byte, the escape character among them, with a visible form. It works on bytes as they arrive, holds no more than it needs to finish a multi-byte character, and never fails the write, so a VM that prints without end costs no memory and does not stall `ssh`. Alternative, only if the default proves awkward: capture the VM's output and show it only when the remote command fails. It hides progress messages from a long start and needs a bound on what is kept. ### Validation - A remote that writes control sequences on standard error and on standard output, through the test seam: the terminal side receives visible text and no control byte other than newline and tab. - A remote that writes an ordinary refusal and a log tail: shown unchanged. - A remote that writes several megabytes: memory does not grow with it. - A multi-byte character split across two writes arrives whole. ### Scope In: the three commands in the table, the shared writer, its tests. Out: what `allod patch` does with the bytes it captures from a VM; the pages a preview serves to the browser; the final `ssh -L` of `allod site view`. `view` becomes that `ssh` (`cmd/allod/site_view.go:53`), so nothing of `allod` is left to filter it; it runs no command in the VM, but anything the VM's ssh server sends the client, such as a banner, still reaches the terminal. Whether `view` should keep a process of its own around that `ssh` is a separate decision.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
allod/tools#254
No description provided.