Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hook execve and execv to allow shared sockets. #2610

Merged
merged 76 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
dc61c6d
trying to send the sockets in execve, once again
meowjesty Jul 15, 2024
ce09680
envp for the (almost) win
meowjesty Jul 16, 2024
e1e65dd
bad file descriptor
meowjesty Jul 16, 2024
f7db5b9
still bad
meowjesty Jul 16, 2024
872a142
new logs
meowjesty Jul 16, 2024
1700630
filtering
meowjesty Jul 16, 2024
1a1a840
little bit cleaner
meowjesty Jul 16, 2024
b00a1d3
should work on macos
meowjesty Jul 16, 2024
6ece064
changes
meowjesty Jul 17, 2024
c04c81f
cleaning up
meowjesty Jul 17, 2024
aff349d
works, but can we do better than set_var
meowjesty Jul 17, 2024
1aa48fd
Does this compile on macos
meowjesty Jul 18, 2024
0add75d
add test file // less broken macos
meowjesty Jul 18, 2024
3694550
import exec
meowjesty Jul 18, 2024
5fa579a
fix imports
meowjesty Jul 18, 2024
ac0d260
disambiguate exec
meowjesty Jul 18, 2024
74d2a75
patch binaries is unused (question mark)
meowjesty Jul 18, 2024
f434b5a
remove unused
meowjesty Jul 18, 2024
05031a8
remove unused fn
meowjesty Jul 18, 2024
4ec1b29
trace logs
meowjesty Jul 18, 2024
97493c1
different name for test
meowjesty Jul 18, 2024
e77576a
Merge branch 'main' of github.com:metalbear-co/mirrord into issue/864…
meowjesty Jul 18, 2024
111d3c8
uvicorn with reload
meowjesty Jul 18, 2024
29e117d
more logs
meowjesty Jul 19, 2024
53f9676
better log
meowjesty Jul 19, 2024
35c3caa
should work bash test
meowjesty Jul 19, 2024
5a8176e
works on all (question mark)
meowjesty Jul 19, 2024
6f566c3
clippy
meowjesty Jul 19, 2024
b926ac6
clippy
meowjesty Jul 19, 2024
38080b3
exit from python
meowjesty Jul 19, 2024
120e69b
leak on macos
meowjesty Jul 22, 2024
f1e960f
it was already leaking
meowjesty Jul 22, 2024
4364275
send sigterm
meowjesty Jul 22, 2024
1621acc
wait for a bit
meowjesty Jul 22, 2024
4e44244
actual message
meowjesty Jul 22, 2024
76835ca
wait for longer
meowjesty Jul 22, 2024
0268e4c
add patch_binaries back
meowjesty Jul 22, 2024
578a3a3
readd function and fix macos
meowjesty Jul 22, 2024
3d718e8
wait for 10 secs before sigkil
meowjesty Jul 22, 2024
74d43ad
macos execve on posix_spawn
meowjesty Jul 22, 2024
1526287
compile macos
meowjesty Jul 22, 2024
5fee622
remove dumb code
meowjesty Jul 22, 2024
cc9bbc6
please compile
meowjesty Jul 22, 2024
e756425
clippy macos
meowjesty Jul 22, 2024
42f9767
no color in logs
meowjesty Jul 22, 2024
b1ed876
lgs
meowjesty Jul 23, 2024
241e27f
cfg not macos
meowjesty Jul 23, 2024
7fffe00
dont hook execv for mac
meowjesty Jul 23, 2024
5f3fc47
test typo
meowjesty Jul 23, 2024
77d1182
macOS
aviramha Jul 23, 2024
6e3bbf8
Merge branch 'issue/864/copy-sockets' of github.com:meowjesty/mirrord…
aviramha Jul 23, 2024
c47a48a
clippy
meowjesty Jul 23, 2024
b4b2fba
coment
meowjesty Jul 23, 2024
fde122d
docs
meowjesty Jul 23, 2024
638f829
remove dead code
meowjesty Jul 23, 2024
c604a85
docs
meowjesty Jul 23, 2024
8319b38
docs
meowjesty Jul 23, 2024
a9743e2
logs and docs
meowjesty Jul 23, 2024
028d31a
Merge branch 'main' into issue/864/copy-sockets
meowjesty Jul 23, 2024
fcb8367
fix docs
meowjesty Jul 23, 2024
738aacb
Merge branch 'issue/864/copy-sockets' of github.com:meowjesty/mirrord…
meowjesty Jul 23, 2024
fdef7ab
fix url
meowjesty Jul 23, 2024
ccf2232
changelog
meowjesty Jul 23, 2024
6d7adc0
insert sockets even if envp is empty
meowjesty Jul 23, 2024
4074a97
Merge branch 'main' into issue/864/copy-sockets
meowjesty Jul 24, 2024
f58f043
clippy
meowjesty Jul 25, 2024
521b2ae
fix test
meowjesty Jul 25, 2024
b05b005
dont test macos
meowjesty Jul 26, 2024
a14221f
always patch sip
meowjesty Jul 29, 2024
1257428
Merge branch 'main' of github.com:metalbear-co/mirrord into issue/864…
meowjesty Jul 29, 2024
7066a65
fix macos
meowjesty Jul 29, 2024
7e7c7b8
clippy macos
meowjesty Jul 29, 2024
860ffaf
shadow variables
meowjesty Jul 29, 2024
510e325
trace
meowjesty Jul 29, 2024
a8fd556
chcked_into macos
meowjesty Jul 29, 2024
f18c6da
macos doesnt need checkedinto ...
meowjesty Jul 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions mirrord/layer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mirrord-intproxy-protocol = { path = "../intproxy/protocol", features = ["codec"

ctor = "0.2"
libc.workspace = true
bincode.workspace = true
nix = { workspace = true, features = ["net", "process", "signal"]}
tracing.workspace = true
tracing-subscriber.workspace = true
Expand Down Expand Up @@ -56,16 +57,16 @@ dashmap = "5.4"
hashbrown = "0.14"
exec.workspace = true
syscalls = { version = "0.6", features = ["full"] }
null-terminated = "0.3"
base64.workspace = true

[target.'cfg(target_os = "macos")'.dependencies]
mirrord-sip = { path = "../sip" }
null-terminated = "0.3"

[dev-dependencies]
mirrord-intproxy = { path = "../intproxy" }
k8s-openapi.workspace = true
chrono = { version = "0.4", features = ["clock"] }
base64.workspace = true
http-body = { workspace = true }
hyper = { workspace = true }
rstest = "*"
Expand Down
22 changes: 21 additions & 1 deletion mirrord/layer/src/common.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
//! Shared place for a few types and functions that are used everywhere by the layer.
use std::{ffi::CStr, fmt::Debug, path::PathBuf};
use std::{ffi::CStr, fmt::Debug, ops::Not, path::PathBuf};

use libc::c_char;
use mirrord_intproxy_protocol::{IsLayerRequest, IsLayerRequestWithResponse, MessageId};
use mirrord_protocol::file::OpenOptionsInternal;
use null_terminated::Nul;
use tracing::warn;

use crate::{
detour::{Bypass, Detour},
error::{HookError, HookResult},
exec_hooks::Argv,
file::OpenOptionsInternalExt,
PROXY_CONNECTION,
};
Expand Down Expand Up @@ -116,6 +118,24 @@ impl CheckedInto<PathBuf> for *const c_char {
}
}

impl CheckedInto<Argv> for *const *const c_char {
fn checked_into(self) -> Detour<Argv> {
let c_list = self
.is_null()
.not()
.then(|| unsafe { Nul::new_unchecked(self) })?;

let list = c_list
.iter()
// Remove the last `null` pointer.
.filter(|value| !value.is_null())
.map(|value| unsafe { CStr::from_ptr(*value) }.to_owned())
.collect::<Argv>();

Detour::Success(list)
}
}

impl CheckedInto<OpenOptionsInternal> for *const c_char {
fn checked_into(self) -> Detour<OpenOptionsInternal> {
CheckedInto::<String>::checked_into(self).map(OpenOptionsInternal::from_mode)
Expand Down
4 changes: 4 additions & 0 deletions mirrord/layer/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ pub(crate) enum HookError {

#[error("mirrord-layer: address passed to `bind` is not valid for the socket domain")]
InvalidBindAddressForDomain,

#[error("mirrord-layer: Failed encoding value with `{0}`!")]
BincodeEncode(#[from] bincode::error::EncodeError),
}

/// Errors internal to mirrord-layer.
Expand Down Expand Up @@ -237,6 +240,7 @@ impl From<HookError> for i64 {
HookError::ProxyError(_) => libc::EINVAL,
HookError::IO(io_fail) => io_fail.raw_os_error().unwrap_or(libc::EIO),
HookError::LockError => libc::EINVAL,
HookError::BincodeEncode(_) => libc::EINVAL,
HookError::ResponseError(response_fail) => match response_fail {
ResponseError::AllocationFailure(_) => libc::ENOMEM,
ResponseError::NotFound(_) => libc::ENOENT,
Expand Down
69 changes: 69 additions & 0 deletions mirrord/layer/src/exec_hooks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#[cfg(target_os = "macos")]
use std::marker::PhantomData;
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)]
pub(crate) struct Argv(Vec<CString>);

/// This must be memory-same as just a `*const c_char`.
#[cfg(target_os = "macos")]
#[repr(C)]
pub(crate) struct StringPtr<'a> {
ptr: *const c_char,
_phantom: PhantomData<&'a ()>,
}

impl Argv {
/// Get a null-pointer [`StringPtr`].
#[cfg(target_os = "macos")]
pub(crate) fn null_string_ptr() -> StringPtr<'static> {
StringPtr {
ptr: ptr::null(),
_phantom: Default::default(),
}
}

/// Get a vector of pointers of which the data buffer is memory-same as a null-terminated array
/// of pointers to null-terminated strings.
#[cfg(target_os = "macos")]
pub(crate) fn null_vec(&mut self) -> Vec<StringPtr> {
let mut vec: Vec<StringPtr> = self
.0
.iter()
.map(|c_string| StringPtr {
ptr: c_string.as_ptr(),
_phantom: Default::default(),
})
.collect();
vec.push(Self::null_string_ptr());
vec
}

pub(crate) fn leak(self) -> *const *const c_char {
let mut list = self
.0
.into_iter()
.map(|value| value.into_raw().cast_const())
.collect::<Vec<_>>();

list.push(ptr::null());

list.into_raw_parts().0.cast_const()
}

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))
}
}
114 changes: 114 additions & 0 deletions mirrord/layer/src/exec_hooks/hooks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use std::{ffi::CString, os::unix::process::parent_id};

use base64::prelude::*;
use libc::{c_char, c_int, FD_CLOEXEC};
use mirrord_layer_macro::hook_guard_fn;
use tracing::Level;

use super::*;
#[cfg(target_os = "macos")]
use crate::exec_utils::*;
use crate::{
common::CheckedInto,
detour::Detour,
hooks::HookManager,
replace,
socket::{hooks::FN_FCNTL, UserSocket, SHARED_SOCKETS_ENV_VAR},
SOCKETS,
};

#[tracing::instrument(level = Level::TRACE, ret)]
fn shared_sockets() -> Vec<(i32, UserSocket)> {
SOCKETS
.iter()
.filter_map(|inner| {
// We only want to share the sockets that are not `FD_CLOEXEC`.
let is_shared = unsafe { FN_FCNTL(*inner.key(), libc::F_GETFD) } & FD_CLOEXEC > 0;
is_shared.then(|| (*inner.key(), UserSocket::clone(inner.value())))
})
.collect::<Vec<_>>()
}

#[mirrord_layer_macro::instrument(
level = Level::DEBUG,
ret,
fields(
pid = std::process::id(),
parent_pid = parent_id(),
)
)]
fn execve(env_vars: Detour<Argv>) -> Detour<*const *const c_char> {
let mut env_vars = env_vars?;
let encoded = bincode::encode_to_vec(shared_sockets(), bincode::config::standard())
.map(|bytes| BASE64_URL_SAFE.encode(bytes))?;

if !encoded.is_empty() {
env_vars.push(CString::new(format!("{SHARED_SOCKETS_ENV_VAR}={encoded}"))?);
}

Detour::Success(env_vars.leak())
}

#[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();

if !encoded.is_empty() {
std::env::set_var("MIRRORD_SHARED_SOCKETS", encoded);
}

FN_EXECV(path, argv)
}

/// Hook for `libc::execve`.
///
/// - #[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.
/// 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.
#[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 {
let checked_envp = envp.checked_into();

if let Detour::Success(modified_envp) = execve(checked_envp) {
#[cfg(target_os = "macos")]
match patch_sip_for_new_process(path, argv, modified_envp) {
DmitryDodzin marked this conversation as resolved.
Show resolved Hide resolved
Detour::Success((new_path, new_argv, new_envp)) => FN_EXECVE(
new_path.into_raw().cast_const(),
new_argv.leak(),
new_envp.leak(),
),
_ => FN_EXECVE(path, argv, envp),
aviramha marked this conversation as resolved.
Show resolved Hide resolved
}

// tracing::info!("Success execve!");
#[cfg(target_os = "linux")]
FN_EXECVE(path, argv, modified_envp)
} else {
tracing::info!("Not success execve!");
FN_EXECVE(path, argv, envp)
}
}

pub(crate) unsafe fn enable_exec_hooks(hook_manager: &mut HookManager) {
replace!(hook_manager, "execv", execv_detour, FnExecv, FN_EXECV);
replace!(hook_manager, "execve", execve_detour, FnExecve, FN_EXECVE);
}
Loading
Loading