mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
d0a693e541
# Summary This PR introduces the Windows sandbox runner IPC foundation that later unified_exec work will build on. The key point is that this is intentionally infrastructure-only. The new IPC transport, runner plumbing, and ConPTY helpers are added here, but the active elevated Windows sandbox path still uses the existing request-file bootstrap. In other words, this change prepares the transport and module layout we need for unified_exec without switching production behavior over yet. Part of this PR is also a source-layout cleanup: some Windows sandbox files are moved into more explicit `elevated/`, `conpty/`, and shared locations so it is clearer which code is for the elevated sandbox flow, which code is legacy/direct-spawn behavior, and which helpers are shared between them. That reorganization is intentional in this first PR so later behavioral changes do not also have to carry a large amount of file-move churn. # Why This Is Needed For unified_exec Windows elevated sandboxed unified_exec needs a long-lived, bidirectional control channel between the CLI and a helper process running under the sandbox user. That channel has to support: - starting a process and reporting structured spawn success/failure - streaming stdout/stderr back incrementally - forwarding stdin over time - terminating or polling a long-lived process - supporting both pipe-backed and PTY-backed sessions The existing elevated one-shot path is built around a request-file bootstrap and does not provide those primitives cleanly. Before we can turn on Windows sandbox unified_exec, we need the underlying runner protocol and transport layer that can carry those lifecycle events and streams. # Why Windows Needs More Machinery Than Linux Or macOS Linux and macOS can generally build unified_exec on top of the existing sandbox/process model: the parent can spawn the child directly, retain normal ownership of stdio or PTY handles, and manage the lifetime of the sandboxed process without introducing a second control process. Windows elevated sandboxing is different. To run inside the sandbox boundary, we cross into a different user/security context and then need to manage a long-lived process from outside that boundary. That means we need an explicit helper process plus an IPC transport to carry spawn, stdin, output, and exit events back and forth. The extra code here is mostly that missing Windows sandbox infrastructure, not a conceptual difference in unified_exec itself. # What This PR Adds - the framed IPC message types and transport helpers for parent <-> runner communication - the renamed Windows command runner with both the existing request-file bootstrap and the dormant IPC bootstrap - named-pipe helpers for the elevated runner path - ConPTY helpers and process-thread attribute plumbing needed for PTY-backed sessions - shared sandbox/process helpers that later PRs will reuse when switching live execution paths over - early file/module moves so later PRs can focus on behavior rather than layout churn # What This PR Does Not Yet Do - it does not switch the active elevated one-shot path over to IPC yet - it does not enable Windows sandbox unified_exec yet - it does not remove the existing request-file bootstrap yet So while this code compiles and the new path has basic validation, it is not yet the exercised production path. That is intentional for this first PR: the goal here is to land the transport and runner foundation cleanly before later PRs start routing real command execution through it. # Follow-Ups Planned follow-up PRs will: 1. switch elevated one-shot Windows sandbox execution to the new runner IPC path 2. layer Windows sandbox unified_exec sessions on top of the same transport 3. remove the legacy request-file path once the IPC-based path is live # Validation - `cargo build -p codex-windows-sandbox`
191 lines
5.3 KiB
Rust
191 lines
5.3 KiB
Rust
#![allow(clippy::unwrap_used)]
|
|
|
|
// This file is copied from https://github.com/wezterm/wezterm (MIT license).
|
|
// Copyright (c) 2018-Present Wez Furlong
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
|
|
use crate::win::psuedocon::PsuedoCon;
|
|
use anyhow::Error;
|
|
use filedescriptor::FileDescriptor;
|
|
use filedescriptor::Pipe;
|
|
use portable_pty::cmdbuilder::CommandBuilder;
|
|
use portable_pty::Child;
|
|
use portable_pty::MasterPty;
|
|
use portable_pty::PtyPair;
|
|
use portable_pty::PtySize;
|
|
use portable_pty::PtySystem;
|
|
use portable_pty::SlavePty;
|
|
use std::mem::ManuallyDrop;
|
|
use std::os::windows::io::AsRawHandle;
|
|
use std::os::windows::io::RawHandle;
|
|
use std::sync::Arc;
|
|
use std::sync::Mutex;
|
|
use winapi::um::wincon::COORD;
|
|
|
|
#[derive(Default)]
|
|
pub struct ConPtySystem {}
|
|
|
|
fn create_conpty_handles(
|
|
size: PtySize,
|
|
) -> anyhow::Result<(PsuedoCon, FileDescriptor, FileDescriptor)> {
|
|
let stdin = Pipe::new()?;
|
|
let stdout = Pipe::new()?;
|
|
|
|
let con = PsuedoCon::new(
|
|
COORD {
|
|
X: size.cols as i16,
|
|
Y: size.rows as i16,
|
|
},
|
|
stdin.read,
|
|
stdout.write,
|
|
)?;
|
|
|
|
Ok((con, stdin.write, stdout.read))
|
|
}
|
|
|
|
pub struct RawConPty {
|
|
con: PsuedoCon,
|
|
input_write: FileDescriptor,
|
|
output_read: FileDescriptor,
|
|
}
|
|
|
|
impl RawConPty {
|
|
pub fn new(cols: i16, rows: i16) -> anyhow::Result<Self> {
|
|
let (con, input_write, output_read) = create_conpty_handles(PtySize {
|
|
rows: rows as u16,
|
|
cols: cols as u16,
|
|
pixel_width: 0,
|
|
pixel_height: 0,
|
|
})?;
|
|
Ok(Self {
|
|
con,
|
|
input_write,
|
|
output_read,
|
|
})
|
|
}
|
|
|
|
pub fn pseudoconsole_handle(&self) -> RawHandle {
|
|
self.con.raw_handle()
|
|
}
|
|
|
|
pub fn into_raw_handles(self) -> (RawHandle, RawHandle, RawHandle) {
|
|
let me = ManuallyDrop::new(self);
|
|
(
|
|
me.con.raw_handle(),
|
|
me.input_write.as_raw_handle(),
|
|
me.output_read.as_raw_handle(),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl PtySystem for ConPtySystem {
|
|
fn openpty(&self, size: PtySize) -> anyhow::Result<PtyPair> {
|
|
let (con, writable, readable) = create_conpty_handles(size)?;
|
|
|
|
let master = ConPtyMasterPty {
|
|
inner: Arc::new(Mutex::new(Inner {
|
|
con,
|
|
readable,
|
|
writable: Some(writable),
|
|
size,
|
|
})),
|
|
};
|
|
|
|
let slave = ConPtySlavePty {
|
|
inner: master.inner.clone(),
|
|
};
|
|
|
|
Ok(PtyPair {
|
|
master: Box::new(master),
|
|
slave: Box::new(slave),
|
|
})
|
|
}
|
|
}
|
|
|
|
struct Inner {
|
|
con: PsuedoCon,
|
|
readable: FileDescriptor,
|
|
writable: Option<FileDescriptor>,
|
|
size: PtySize,
|
|
}
|
|
|
|
impl Inner {
|
|
pub fn resize(
|
|
&mut self,
|
|
num_rows: u16,
|
|
num_cols: u16,
|
|
pixel_width: u16,
|
|
pixel_height: u16,
|
|
) -> Result<(), Error> {
|
|
self.con.resize(COORD {
|
|
X: num_cols as i16,
|
|
Y: num_rows as i16,
|
|
})?;
|
|
self.size = PtySize {
|
|
rows: num_rows,
|
|
cols: num_cols,
|
|
pixel_width,
|
|
pixel_height,
|
|
};
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct ConPtyMasterPty {
|
|
inner: Arc<Mutex<Inner>>,
|
|
}
|
|
|
|
pub struct ConPtySlavePty {
|
|
inner: Arc<Mutex<Inner>>,
|
|
}
|
|
|
|
impl MasterPty for ConPtyMasterPty {
|
|
fn resize(&self, size: PtySize) -> anyhow::Result<()> {
|
|
let mut inner = self.inner.lock().unwrap();
|
|
inner.resize(size.rows, size.cols, size.pixel_width, size.pixel_height)
|
|
}
|
|
|
|
fn get_size(&self) -> Result<PtySize, Error> {
|
|
let inner = self.inner.lock().unwrap();
|
|
Ok(inner.size)
|
|
}
|
|
|
|
fn try_clone_reader(&self) -> anyhow::Result<Box<dyn std::io::Read + Send>> {
|
|
Ok(Box::new(self.inner.lock().unwrap().readable.try_clone()?))
|
|
}
|
|
|
|
fn take_writer(&self) -> anyhow::Result<Box<dyn std::io::Write + Send>> {
|
|
Ok(Box::new(
|
|
self.inner
|
|
.lock()
|
|
.unwrap()
|
|
.writable
|
|
.take()
|
|
.ok_or_else(|| anyhow::anyhow!("writer already taken"))?,
|
|
))
|
|
}
|
|
}
|
|
|
|
impl SlavePty for ConPtySlavePty {
|
|
fn spawn_command(&self, cmd: CommandBuilder) -> anyhow::Result<Box<dyn Child + Send + Sync>> {
|
|
let inner = self.inner.lock().unwrap();
|
|
let child = inner.con.spawn_command(cmd)?;
|
|
Ok(Box::new(child))
|
|
}
|
|
}
|