|
1 | 1 | use std::io; |
| 2 | +use std::os::fd::AsFd; |
2 | 3 | use std::os::fd::AsRawFd; |
3 | | -use std::os::fd::FromRawFd as _; |
4 | 4 | use std::os::fd::OwnedFd; |
5 | 5 |
|
6 | 6 | use anyhow::Context as _; |
@@ -28,6 +28,12 @@ fn get_escalate_client() -> anyhow::Result<AsyncDatagramSocket> { |
28 | 28 | Ok(unsafe { AsyncDatagramSocket::from_raw_fd(client_fd) }?) |
29 | 29 | } |
30 | 30 |
|
| 31 | +fn duplicate_fd_for_transfer(fd: impl AsFd, name: &str) -> anyhow::Result<OwnedFd> { |
| 32 | + fd.as_fd() |
| 33 | + .try_clone_to_owned() |
| 34 | + .with_context(|| format!("failed to duplicate {name} for escalation transfer")) |
| 35 | +} |
| 36 | + |
31 | 37 | pub async fn run_shell_escalation_execve_wrapper( |
32 | 38 | file: String, |
33 | 39 | argv: Vec<String>, |
@@ -62,19 +68,26 @@ pub async fn run_shell_escalation_execve_wrapper( |
62 | 68 | .context("failed to receive EscalateResponse")?; |
63 | 69 | match message.action { |
64 | 70 | EscalateAction::Escalate => { |
65 | | - // TODO: maybe we should send ALL open FDs (except the escalate client)? |
| 71 | + // Duplicate stdio before transferring ownership to the server. The |
| 72 | + // wrapper must keep using its own stdin/stdout/stderr until the |
| 73 | + // escalated child takes over. |
| 74 | + let destination_fds = [ |
| 75 | + io::stdin().as_raw_fd(), |
| 76 | + io::stdout().as_raw_fd(), |
| 77 | + io::stderr().as_raw_fd(), |
| 78 | + ]; |
66 | 79 | let fds_to_send = [ |
67 | | - unsafe { OwnedFd::from_raw_fd(io::stdin().as_raw_fd()) }, |
68 | | - unsafe { OwnedFd::from_raw_fd(io::stdout().as_raw_fd()) }, |
69 | | - unsafe { OwnedFd::from_raw_fd(io::stderr().as_raw_fd()) }, |
| 80 | + duplicate_fd_for_transfer(io::stdin(), "stdin")?, |
| 81 | + duplicate_fd_for_transfer(io::stdout(), "stdout")?, |
| 82 | + duplicate_fd_for_transfer(io::stderr(), "stderr")?, |
70 | 83 | ]; |
71 | 84 |
|
72 | 85 | // TODO: also forward signals over the super-exec socket |
73 | 86 |
|
74 | 87 | client |
75 | 88 | .send_with_fds( |
76 | 89 | SuperExecMessage { |
77 | | - fds: fds_to_send.iter().map(AsRawFd::as_raw_fd).collect(), |
| 90 | + fds: destination_fds.into_iter().collect(), |
78 | 91 | }, |
79 | 92 | &fds_to_send, |
80 | 93 | ) |
@@ -115,3 +128,23 @@ pub async fn run_shell_escalation_execve_wrapper( |
115 | 128 | } |
116 | 129 | } |
117 | 130 | } |
| 131 | + |
| 132 | +#[cfg(test)] |
| 133 | +mod tests { |
| 134 | + use super::*; |
| 135 | + use std::os::fd::AsRawFd; |
| 136 | + use std::os::unix::net::UnixStream; |
| 137 | + |
| 138 | + #[test] |
| 139 | + fn duplicate_fd_for_transfer_does_not_close_original() { |
| 140 | + let (left, _right) = UnixStream::pair().expect("socket pair"); |
| 141 | + let original_fd = left.as_raw_fd(); |
| 142 | + |
| 143 | + let duplicate = duplicate_fd_for_transfer(&left, "test fd").expect("duplicate fd"); |
| 144 | + assert_ne!(duplicate.as_raw_fd(), original_fd); |
| 145 | + |
| 146 | + drop(duplicate); |
| 147 | + |
| 148 | + assert_ne!(unsafe { libc::fcntl(original_fd, libc::F_GETFD) }, -1); |
| 149 | + } |
| 150 | +} |
0 commit comments