Skip to content

Decode the agent desktop's video and images in a sandboxed process (decoder phases 2–4) - #73

Merged
GrischaE1 merged 1 commit into
mainfrom
decoder-process
Oct 24, 2026
Merged

GrischaE1 merged 1 commit into
mainfrom
decoder-process

Conversation

@GrischaE1

Copy link
Copy Markdown
Owner

Phases 2–4 of docs/decoder-isolation-design.md: a device's video and clipboard images are now decoded in a separate process, RemoteHubDecoder.exe, that can reach nothing of the administrator's PC (threats T9.1 and T9.2). A device that has been taken over can no longer use a flaw in Windows' H.264 or image decoders to run code inside RemoteHub, where the unlocked credentials are.

How it works

  • src/RemoteHub.Decoder: only the H.264 decoder (VideoCodec.cs), DeviceImage.Decode and the protocol. No settings, database, network or files.
  • The sandbox (DecoderSandbox), §3.2:
    • an AppContainer with no capabilities
    • if that can't be made, a restricted token at low integrity
    • if neither works, the tab says the picture can't be shown; there is never a fallback to decoding in RemoteHub
    • a job: ended with RemoteHub, no child processes, 1 GB, all UI limits, DIE_ON_UNHANDLED_EXCEPTION
    • an explicit handle list: two pipe ends and the video section, nothing else
  • The protocol (DecoderProtocol, DecoderHost), §3.3:
    • length-prefixed messages on anonymous pipes
    • video pixels in a shared section with two buffers
    • each clipboard image gets a section of exactly its header's size, handed over with DuplicateHandle
    • RemoteHub checks every answer before it touches the pixels: kind, length, frame or image id, buffer, and a size no larger than asked for
    • time limits: 10 s to start, 2 s per frame, 10 s per image
  • Restarts (SessionDecoder), §3.4:
    • a decoder that ends, doesn't answer in time, or answers something it can't mean is ended and restarted from a key frame
    • more than 3 failures in a minute end the session: "The picture can't be shown – the device keeps sending data RemoteHub can't decode. Disconnect and check the device."
    • each stop goes to the diagnostic log without device names, and to the audit log as desktop.decoder_stopped ("Desktop decoder stopped")
  • Clipboard images: decoded once, when they arrive, in the session's decoder. ClipboardOffer only re-encodes the pixels; nothing is decoded in RemoteHub any more.
  • Closing a tab: a frame still waiting for the decoder is cut short by ending the job first, and the section is unmapped only after that call has returned.
  • RemoteHub.exe --decoder-check <file>: starts the decoder as a session does, decodes a test stream and images, runs the sandbox probes and writes the results as JSON. The lab uses it, and so can support.
  • Install: build-installer.ps1 publishes the decoder self-contained into RemoteHub's folder, on the same runtime, and signs it. A release now uses 5 eSigner signings (6 with the credential provider), one more than before. Development builds copy it next to RemoteHub.exe.

Measured in the spike and in a self-contained publish: 1080p frames cost the same as decoding in-process (9.4 ms against 9.9 ms), the decoder starts in about 0.3 s, and each one uses 21 MB private memory at 640 × 360, 42 MB at 1080p and 112 MB at 4K.

Tests

  • DecoderProcessTests (14):
    • the answer checks against garbage answers
    • the restart limit with a clock
    • the real decoder in its AppContainer: decoding works and all ten probes are blocked (network, loopback, profile, database folder, writing, HKCU, clipboard, a new process, RemoteHub's process, other windows)
    • at low integrity (forced): no writes, no clipboard, no new process, RemoteHub's process and other windows out of reach; reading files and the network stay open, which the design records
    • a decoder killed from outside is reported within the frame timeout, and a new one starts
    • closing a session while a call waits returns at once and isn't reported as a failure
    • fuzzing: 120 truncated, bit-flipped, noisy and doubled H.264 access units and 120 broken PNG, BMP and JPEG files, each ending within the time limit, with a good stream decoding afterwards
    • the tab after a restart and after the session gives up
  • DeviceImageTests and ClipboardOfferTests: updated for decoding through the process.
  • SecretMemoryTests (from Passwords stay encrypted in memory; editors no longer show stored ones #70): now runs alone, after all other tests. It dumps its own process, and with the decoder tests running beside it a suspended thread held a lock the dump needed, which deadlocked the whole run. Dumping from outside isn't possible with .NET's createdump since .NET 8.
  • Lab SEC-DEC-06 (SEC-DEC.Tests.ps1, device VMs: Windows 11 and Server): normaluser runs --decoder-check on the installed build. It needs an AppContainer, the pictures shown, the images decoded and every probe blocked. Not run in the lab yet (marked ◐).
  • Results:
    • Full unit suite: 1,138 passed, 6 skipped, 2 failed. The 2 failures are RecordingTests.FrameGrabber_CapturesHostedChildWindowsAndCoveredContent; they fail the same way on main's build on this PC right now (the desktop session's state) and passed in the run before.
    • Release and debug builds have no warnings.
    • A self-contained publish was checked with --decoder-check.

Not in this PR

SEC-DEC-01 … 05 need a rogue agent in the lab harness: a fake hostile device that RemoteHub has pinned, which sends malformed video and images, hostile file names, floods and a hostile Hello. That comes next, in its own PR.

Docs

  • decoder-isolation-design.md: status, §2, §3.3, §6, §10 and a new §12 "As built".
  • security-test-lab-design.md: T9.1 and T9.2 are now mitigated, and a SEC-DEC-06 row.
  • The lab case sheet and test document: SEC-DEC-06.
  • README: the agent desktop decodes in a sandbox.
  • credential-provider-design.md and the release.yml comment: the signing count.

…process (phases 2-4)

RemoteHubDecoder.exe decodes the device's H.264 video and clipboard images.
RemoteHub starts it in an AppContainer with no capabilities (low integrity
if that can't be made, refusing the picture if neither works), inside a job:
ended with RemoteHub, no child processes, 1 GB, all UI limits, no error
dialog, and only its two pipe ends and the video section inherited. Pixels
come back through shared memory; RemoteHub checks every answer (kind,
length, frame or image id, buffer, size) before it touches them.

A decoder that ends, doesn't answer in time or answers something it can't
mean is ended and restarted from a key frame; more than 3 failures in a
minute end the session. Each stop is logged (no device names) and audited
(desktop.decoder_stopped). Clipboard images are decoded once, when they
arrive, in the session's decoder; the clipboard data is built from those
pixels.

RemoteHub.exe --decoder-check <file> runs the decoder as a session does
and writes decoding and sandbox probe results as JSON (lab case
SEC-DEC-06). The installer publishes and signs the decoder next to
RemoteHub.exe.

Tests: answer checks, the restart limit, the real decoder in its
AppContainer and at low integrity (decoding and probes), a killed decoder,
closing a session while a call waits, fuzzed video and images, and the tab
after the session gives up. The memory-dump test now runs alone: dumping
its own process beside the decoder tests deadlocked the run.
@github-actions
github-actions Bot marked this pull request as draft October 23, 2026 19:54
@github-actions

Copy link
Copy Markdown

Pull requests start as drafts here. Mark this one Ready for review when it's done and all checks have passed.

@GrischaE1
GrischaE1 marked this pull request as ready for review October 24, 2026 03:41
@GrischaE1
GrischaE1 merged commit 9a70e7b into main Oct 24, 2026
14 checks passed
@GrischaE1
GrischaE1 deleted the decoder-process branch October 24, 2026 03:42
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 24, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant