sandbox_runtime/sandbox/linux/
bwrap.rs1use std::path::Path;
4
5use crate::config::SandboxRuntimeConfig;
6use crate::error::SandboxError;
7use crate::sandbox::linux::bridge::SocatBridge;
8use crate::sandbox::linux::filesystem::{generate_bind_mounts, BindMount};
9use crate::sandbox::linux::seccomp::{get_apply_seccomp_path, get_bpf_path};
10use crate::utils::quote;
11
12pub fn check_bwrap() -> bool {
14 std::process::Command::new("bwrap")
15 .arg("--version")
16 .output()
17 .map(|o| o.status.success())
18 .unwrap_or(false)
19}
20
21pub fn generate_bwrap_command(
23 command: &str,
24 config: &SandboxRuntimeConfig,
25 cwd: &Path,
26 http_socket_path: Option<&str>,
27 socks_socket_path: Option<&str>,
28 http_proxy_port: u16,
29 socks_proxy_port: u16,
30 shell: Option<&str>,
31) -> Result<(String, Vec<String>), SandboxError> {
32 let shell = shell.unwrap_or("/bin/bash");
33
34 let (mounts, warnings) = generate_bind_mounts(
36 &config.filesystem,
37 cwd,
38 config.ripgrep.as_ref(),
39 config.mandatory_deny_search_depth,
40 )?;
41
42 let mut bwrap_args = vec![
44 "bwrap".to_string(),
45 "--unshare-net".to_string(), "--dev".to_string(),
47 "/dev".to_string(),
48 "--proc".to_string(),
49 "/proc".to_string(),
50 "--tmpfs".to_string(),
51 "/tmp".to_string(),
52 "--tmpfs".to_string(),
53 "/run".to_string(),
54 ];
55
56 bwrap_args.push("--ro-bind".to_string());
58 bwrap_args.push("/".to_string());
59 bwrap_args.push("/".to_string());
60
61 for mount in &mounts {
63 if !mount.readonly {
64 bwrap_args.extend(mount.to_bwrap_args());
65 }
66 }
67
68 for mount in &mounts {
70 if mount.readonly {
71 bwrap_args.extend(mount.to_bwrap_args());
72 }
73 }
74
75 bwrap_args.push("--chdir".to_string());
77 bwrap_args.push(cwd.display().to_string());
78
79 let inner_command = build_inner_command(
81 command,
82 config,
83 http_socket_path,
84 socks_socket_path,
85 http_proxy_port,
86 socks_proxy_port,
87 shell,
88 )?;
89
90 bwrap_args.push("--".to_string());
92 bwrap_args.push(shell.to_string());
93 bwrap_args.push("-c".to_string());
94 bwrap_args.push(inner_command);
95
96 let wrapped = bwrap_args
98 .iter()
99 .map(|s| quote(s))
100 .collect::<Vec<_>>()
101 .join(" ");
102
103 Ok((wrapped, warnings))
104}
105
106fn build_inner_command(
109 command: &str,
110 config: &SandboxRuntimeConfig,
111 http_socket_path: Option<&str>,
112 socks_socket_path: Option<&str>,
113 http_proxy_port: u16,
114 socks_proxy_port: u16,
115 shell: &str,
116) -> Result<String, SandboxError> {
117 let mut parts = Vec::new();
118
119 if let Some(http_sock) = http_socket_path {
121 let bridge_cmd = SocatBridge::tcp_to_unix_command(http_proxy_port, http_sock);
122 parts.push(format!("{} &", bridge_cmd));
123 }
124
125 if let Some(socks_sock) = socks_socket_path {
126 let bridge_cmd = SocatBridge::tcp_to_unix_command(socks_proxy_port, socks_sock);
127 parts.push(format!("{} &", bridge_cmd));
128 }
129
130 if http_socket_path.is_some() || socks_socket_path.is_some() {
132 parts.push("sleep 0.1".to_string());
133 }
134
135 if !config.network.allow_all_unix_sockets.unwrap_or(false) {
137 if let (Ok(bpf_path), Ok(apply_path)) = (
139 get_bpf_path(config.seccomp.as_ref()),
140 get_apply_seccomp_path(config.seccomp.as_ref()),
141 ) {
142 let env_vars = generate_proxy_env_string(http_proxy_port, socks_proxy_port);
144 parts.push(env_vars);
145
146 parts.push(format!(
148 "{} {} {} -c {}",
149 apply_path.display(),
150 bpf_path.display(),
151 shell,
152 quote(command)
153 ));
154 } else {
155 tracing::warn!(
157 "Seccomp not available - Unix socket creation will not be blocked"
158 );
159 let env_vars = generate_proxy_env_string(http_proxy_port, socks_proxy_port);
160 parts.push(format!("{} {} -c {}", env_vars, shell, quote(command)));
161 }
162 } else {
163 let env_vars = generate_proxy_env_string(http_proxy_port, socks_proxy_port);
165 parts.push(format!("{} {} -c {}", env_vars, shell, quote(command)));
166 }
167
168 Ok(parts.join(" ; "))
169}
170
171fn generate_proxy_env_string(http_port: u16, socks_port: u16) -> String {
173 format!(
174 "export http_proxy='http://localhost:{}' https_proxy='http://localhost:{}' \
175 HTTP_PROXY='http://localhost:{}' HTTPS_PROXY='http://localhost:{}' \
176 ALL_PROXY='socks5://localhost:{}' all_proxy='socks5://localhost:{}' ;",
177 http_port, http_port, http_port, http_port, socks_port, socks_port
178 )
179}
180
181pub fn generate_proxy_env(http_port: u16, socks_port: u16) -> Vec<(String, String)> {
183 let http_proxy = format!("http://localhost:{}", http_port);
184 let socks_proxy = format!("socks5://localhost:{}", socks_port);
185
186 vec![
187 ("http_proxy".to_string(), http_proxy.clone()),
188 ("HTTP_PROXY".to_string(), http_proxy.clone()),
189 ("https_proxy".to_string(), http_proxy.clone()),
190 ("HTTPS_PROXY".to_string(), http_proxy),
191 ("ALL_PROXY".to_string(), socks_proxy.clone()),
192 ("all_proxy".to_string(), socks_proxy),
193 ]
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn test_generate_proxy_env_string() {
202 let env = generate_proxy_env_string(3128, 1080);
203 assert!(env.contains("http_proxy='http://localhost:3128'"));
204 assert!(env.contains("ALL_PROXY='socks5://localhost:1080'"));
205 }
206
207 #[test]
208 fn test_check_bwrap() {
209 let available = check_bwrap();
211 println!("Bubblewrap available: {}", available);
212 }
213}