diff --git a/Cargo.lock b/Cargo.lock index 483b43547a9a0d..ed2ad67f22aa07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -446,6 +446,7 @@ dependencies = [ "anyhow", "futures 0.3.31", "gpui", + "shlex", "smol", "tempfile", "util", diff --git a/crates/askpass/Cargo.toml b/crates/askpass/Cargo.toml index d64ee9f7c35d4d..a2df3b8495acce 100644 --- a/crates/askpass/Cargo.toml +++ b/crates/askpass/Cargo.toml @@ -15,6 +15,7 @@ path = "src/askpass.rs" anyhow.workspace = true futures.workspace = true gpui.workspace = true +shlex.workspace = true smol.workspace = true tempfile.workspace = true util.workspace = true diff --git a/crates/askpass/src/askpass.rs b/crates/askpass/src/askpass.rs index a4bdb5edb8e179..b5435ac98e3ecf 100644 --- a/crates/askpass/src/askpass.rs +++ b/crates/askpass/src/askpass.rs @@ -72,8 +72,7 @@ impl AskPassSession { let (askpass_opened_tx, askpass_opened_rx) = oneshot::channel::<()>(); let listener = UnixListener::bind(&askpass_socket).context("failed to create askpass socket")?; - let zed_path = std::env::current_exe() - .context("Failed to figure out current executable path for use in askpass")?; + let zed_path = get_shell_safe_zed_path()?; let (askpass_kill_master_tx, askpass_kill_master_rx) = oneshot::channel::<()>(); let mut kill_tx = Some(askpass_kill_master_tx); @@ -115,7 +114,7 @@ impl AskPassSession { // Create an askpass script that communicates back to this process. let askpass_script = format!( "{shebang}\n{print_args} | {zed_exe} --askpass={askpass_socket} 2> /dev/null \n", - zed_exe = zed_path.display(), + zed_exe = zed_path, askpass_socket = askpass_socket.display(), print_args = "printf '%s\\0' \"$@\"", shebang = "#!/bin/sh", @@ -161,6 +160,32 @@ impl AskPassSession { } } +#[cfg(unix)] +fn get_shell_safe_zed_path() -> anyhow::Result { + let zed_path = std::env::current_exe() + .context("Failed to figure out current executable path for use in askpass")? + .to_string_lossy() + .to_string(); + + // sanity check on unix systems that the path exists and is executable + // todo(windows): implement this check for windows (or just use `is-executable` crate) + use std::os::unix::fs::MetadataExt; + let metadata = std::fs::metadata(&zed_path) + .context("Failed to check metadata of Zed executable path for use in askpass")?; + let is_executable = metadata.is_file() && metadata.mode() & 0o111 != 0; + anyhow::ensure!( + is_executable, + "Failed to verify Zed executable path for use in askpass" + ); + // As of writing, this can only be fail if the path contains a null byte, which shouldn't be possible + // but shlex has annotated the error as #[non_exhaustive] so we can't make it a compile error if other + // errors are introduced in the future :( + let zed_path_escaped = shlex::try_quote(&zed_path) + .context("Failed to shell-escape Zed executable path for use in askpass")?; + + return Ok(zed_path_escaped.to_string()); +} + /// The main function for when Zed is running in netcat mode for use in askpass. /// Called from both the remote server binary and the zed binary in their respective main functions. #[cfg(unix)]