-
Notifications
You must be signed in to change notification settings - Fork 109
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* trying to send the sockets in execve, once again * envp for the (almost) win * bad file descriptor * still bad * new logs * filtering * little bit cleaner * should work on macos * changes * cleaning up * works, but can we do better than set_var * Does this compile on macos * add test file // less broken macos * import exec * fix imports * disambiguate exec * patch binaries is unused (question mark) * remove unused * remove unused fn * trace logs * different name for test * uvicorn with reload * more logs * better log * should work bash test * works on all (question mark) * clippy * clippy * exit from python * leak on macos * it was already leaking * send sigterm * wait for a bit * actual message * wait for longer * add patch_binaries back * readd function and fix macos * wait for 10 secs before sigkil * macos execve on posix_spawn * compile macos * remove dumb code * please compile * clippy macos * no color in logs * lgs * cfg not macos * dont hook execv for mac * test typo * macOS * clippy * coment * docs * remove dead code * docs * docs * logs and docs * fix docs * fix url * changelog * insert sockets even if envp is empty * clippy * fix test * dont test macos * always patch sip * fix macos * clippy macos * shadow variables * trace * chcked_into macos * macos doesnt need checkedinto ... --------- Co-authored-by: Aviram Hassan <[email protected]>
- Loading branch information
Showing
16 changed files
with
455 additions
and
125 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Pass the list of UserSocket to child processes when exec is called through an env var MIRRORD_SHARED_SOCKETS. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
use std::{ | ||
ffi::{c_char, CString}, | ||
ptr, | ||
}; | ||
|
||
pub(crate) mod hooks; | ||
|
||
/// Hold a vector of new CStrings to use instead of the original argv. | ||
#[derive(Default, Debug, Clone)] | ||
pub(crate) struct Argv(Vec<CString>); | ||
|
||
impl Argv { | ||
/// Turns this list of [`CString`] into a C list of pointers (null-terminated). | ||
/// | ||
/// We leak the [`CString`]s, so that they may live in C-land. | ||
pub(crate) fn leak(self) -> *const *const c_char { | ||
// Leaks the strings. | ||
let mut list = self | ||
.0 | ||
.into_iter() | ||
.map(|value| value.into_raw().cast_const()) | ||
.collect::<Vec<_>>(); | ||
|
||
// Null-terminated. | ||
list.push(ptr::null()); | ||
|
||
// Leaks the list itself. | ||
list.into_raw_parts().0.cast_const() | ||
} | ||
|
||
/// Convenience to [`Vec::push`] a new [`CString`]. | ||
pub(crate) fn push(&mut self, item: CString) { | ||
self.0.push(item); | ||
} | ||
} | ||
|
||
impl FromIterator<CString> for Argv { | ||
fn from_iter<T: IntoIterator<Item = CString>>(iter: T) -> Self { | ||
Argv(Vec::from_iter(iter)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
use std::{ffi::CString, os::unix::process::parent_id}; | ||
|
||
use base64::prelude::*; | ||
use libc::{c_char, c_int}; | ||
use mirrord_layer_macro::hook_guard_fn; | ||
use tracing::Level; | ||
|
||
use super::*; | ||
#[cfg(not(target_os = "macos"))] | ||
use crate::common::CheckedInto; | ||
#[cfg(target_os = "macos")] | ||
use crate::exec_utils::*; | ||
use crate::{ | ||
detour::{Bypass, Detour}, | ||
hooks::HookManager, | ||
replace, | ||
socket::{UserSocket, SHARED_SOCKETS_ENV_VAR}, | ||
SOCKETS, | ||
}; | ||
|
||
/// Converts the [`SOCKETS`] map into a vector of pairs `(Fd, UserSocket)`, so we can rebuild | ||
/// it as a map. | ||
#[mirrord_layer_macro::instrument(level = Level::TRACE, ret)] | ||
fn shared_sockets() -> Vec<(i32, UserSocket)> { | ||
SOCKETS | ||
.iter() | ||
.map(|inner| (*inner.key(), UserSocket::clone(inner.value()))) | ||
.collect::<Vec<_>>() | ||
} | ||
|
||
/// Takes an [`Argv`] with the enviroment variables from an `exec` call, extending it with | ||
/// an encoded version of our [`SOCKETS`]. | ||
/// | ||
/// The check for [`libc::FD_CLOEXEC`] is performed during the [`SOCKETS`] initialization | ||
/// by the child process. | ||
#[mirrord_layer_macro::instrument( | ||
level = Level::TRACE, | ||
ret, | ||
fields( | ||
pid = std::process::id(), | ||
parent_pid = parent_id(), | ||
) | ||
)] | ||
pub(crate) fn prepare_execve_envp(env_vars: Detour<Argv>) -> Detour<Argv> { | ||
let mut env_vars = env_vars.or_bypass(|reason| match reason { | ||
Bypass::EmptyOption => Detour::Success(Argv(Vec::new())), | ||
other => Detour::Bypass(other), | ||
})?; | ||
|
||
let encoded = bincode::encode_to_vec(shared_sockets(), bincode::config::standard()) | ||
.map(|bytes| BASE64_URL_SAFE.encode(bytes))?; | ||
|
||
env_vars.push(CString::new(format!("{SHARED_SOCKETS_ENV_VAR}={encoded}"))?); | ||
|
||
Detour::Success(env_vars) | ||
} | ||
|
||
/// Hook for `libc::execv` for linux only. | ||
/// | ||
/// On macos this just calls `execve(path, argv, _environ)`, so we'll be handling it in our | ||
/// [`execve_detour`]. | ||
#[cfg(not(target_os = "macos"))] | ||
#[hook_guard_fn] | ||
unsafe extern "C" fn execv_detour(path: *const c_char, argv: *const *const c_char) -> c_int { | ||
let encoded = bincode::encode_to_vec(shared_sockets(), bincode::config::standard()) | ||
.map(|bytes| BASE64_URL_SAFE.encode(bytes)) | ||
.unwrap_or_default(); | ||
|
||
// `encoded` is emtpy if the encoding failed, so we don't set the env var. | ||
if !encoded.is_empty() { | ||
std::env::set_var("MIRRORD_SHARED_SOCKETS", encoded); | ||
} | ||
|
||
FN_EXECV(path, argv) | ||
} | ||
|
||
/// Hook for `libc::execve`. | ||
/// | ||
/// We can't change the pointers, to get around that we create our own and **leak** them. | ||
#[cfg(not(target_os = "macos"))] | ||
#[hook_guard_fn] | ||
pub(crate) unsafe extern "C" fn execve_detour( | ||
path: *const c_char, | ||
argv: *const *const c_char, | ||
envp: *const *const c_char, | ||
) -> c_int { | ||
// Hopefully `envp` is a properly null-terminated list. | ||
if let Detour::Success(envp) = prepare_execve_envp(envp.checked_into()) { | ||
FN_EXECVE(path, argv, envp.leak()) | ||
} else { | ||
FN_EXECVE(path, argv, envp) | ||
} | ||
} | ||
|
||
/// Hook for `libc::execve`. | ||
/// | ||
/// We can't change the pointers, to get around that we create our own and **leak** them. | ||
/// | ||
/// - #[cfg(target_os = "macos")] | ||
/// | ||
/// We change 3 arguments and then call the original functions: | ||
/// | ||
/// 1. The executable path - we check it for SIP, create a patched binary and use the path to the | ||
/// new path instead of the original path. If there is no SIP, we use a new string with the same | ||
/// path. | ||
/// 2. argv - we strip mirrord's temporary directory from the start of arguments. | ||
/// So if `argv[1]` is "/var/folders/1337/mirrord-bin/opt/homebrew/bin/npx", switch it | ||
/// to "/opt/homebrew/bin/npx". Also here we create a new array with pointers | ||
/// to new strings, even if there are no changes needed (except for the case of an error). | ||
/// 3. envp - We found out that Turbopack (Vercel) spawns a clean "Node" instance without env, | ||
/// basically stripping all of the important mirrord env. | ||
/// [#2500](https://github.com/metalbear-co/mirrord/issues/2500) | ||
/// We restore the `DYLD_INSERT_LIBRARIES` environment variable and all env vars | ||
/// starting with `MIRRORD_` if the dyld var can't be found in `envp`. | ||
/// | ||
/// If there is an error in the detour, we don't exit or anything, we just call the original libc | ||
/// function with the original passed arguments. | ||
#[cfg(target_os = "macos")] | ||
#[hook_guard_fn] | ||
pub(crate) unsafe extern "C" fn execve_detour( | ||
path: *const c_char, | ||
argv: *const *const c_char, | ||
envp: *const *const c_char, | ||
) -> c_int { | ||
match patch_sip_for_new_process(path, argv, envp) { | ||
Detour::Success((path, argv, envp)) => { | ||
match prepare_execve_envp(Detour::Success(envp.clone())) { | ||
Detour::Success(envp) => { | ||
FN_EXECVE(path.into_raw().cast_const(), argv.leak(), envp.leak()) | ||
} | ||
_ => FN_EXECVE(path.into_raw().cast_const(), argv.leak(), envp.leak()), | ||
} | ||
} | ||
_ => FN_EXECVE(path, argv, envp), | ||
} | ||
} | ||
|
||
/// Enables `exec` hooks. | ||
pub(crate) unsafe fn enable_exec_hooks(hook_manager: &mut HookManager) { | ||
#[cfg(not(target_os = "macos"))] | ||
replace!(hook_manager, "execv", execv_detour, FnExecv, FN_EXECV); | ||
|
||
replace!(hook_manager, "execve", execve_detour, FnExecve, FN_EXECVE); | ||
} |
Oops, something went wrong.