diff --git a/Cargo.lock b/Cargo.lock index 9609dcde..04799af7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1866,11 +1866,20 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memfd" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" +dependencies = [ + "rustix", +] + [[package]] name = "memmap2" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" dependencies = [ "libc", ] diff --git a/crates/fspy/Cargo.toml b/crates/fspy/Cargo.toml index a9a1ffff..933fb904 100644 --- a/crates/fspy/Cargo.toml +++ b/crates/fspy/Cargo.toml @@ -29,7 +29,7 @@ fspy_seccomp_unotify = { workspace = true, features = ["supervisor"] } nix = { workspace = true, features = ["uio"] } tokio = { workspace = true, features = ["bytes"] } -[target.'cfg(all(unix, not(target_env = "musl")))'.dependencies] +[target.'cfg(all(unix, all(not(target_env = "musl"), not(target_os = "android"))))'.dependencies] fspy_preload_unix = { workspace = true } [target.'cfg(unix)'.dependencies] diff --git a/crates/fspy/src/artifact.rs b/crates/fspy/src/artifact.rs index 0c3fcba0..4b03362a 100644 --- a/crates/fspy/src/artifact.rs +++ b/crates/fspy/src/artifact.rs @@ -28,7 +28,7 @@ macro_rules! artifact { pub use artifact; impl Artifact { - #[cfg(not(target_os = "linux"))] + #[cfg(all(not(target_os = "android"), not(target_os = "linux")))] pub const fn new(name: &'static str, content: &'static [u8], hash: &'static str) -> Self { Self { name, content, hash } } diff --git a/crates/fspy/src/lib.rs b/crates/fspy/src/lib.rs index 7acabe79..acdf63e4 100644 --- a/crates/fspy/src/lib.rs +++ b/crates/fspy/src/lib.rs @@ -1,14 +1,14 @@ #![cfg_attr(target_os = "windows", feature(windows_process_extensions_main_thread_handle))] -#![feature(once_cell_try)] +#![cfg_attr(not(target_os = "android"), feature(once_cell_try))] // Persist the injected DLL/shared library somewhere in the filesystem. // Not needed on musl (seccomp-only tracking). -#[cfg(not(target_env = "musl"))] +#[cfg(all(not(target_os = "android"), not(target_env = "musl")))] mod artifact; pub mod error; -#[cfg(not(target_env = "musl"))] +#[cfg(all(not(target_os = "android"), not(target_env = "musl")))] mod ipc; #[cfg(unix)] diff --git a/crates/fspy/src/unix/mod.rs b/crates/fspy/src/unix/mod.rs index ba051630..d42e7c80 100644 --- a/crates/fspy/src/unix/mod.rs +++ b/crates/fspy/src/unix/mod.rs @@ -9,7 +9,7 @@ use std::{io, path::Path}; #[cfg(target_os = "linux")] use fspy_seccomp_unotify::supervisor::supervise; use fspy_shared::ipc::PathAccess; -#[cfg(not(target_env = "musl"))] +#[cfg(all(not(target_os = "android"), not(target_env = "musl")))] use fspy_shared::ipc::{NativeStr, channel::channel}; #[cfg(target_os = "macos")] use fspy_shared_unix::payload::Artifacts; @@ -24,7 +24,7 @@ use syscall_handler::SyscallHandler; use tokio::task::spawn_blocking; use tokio_util::sync::CancellationToken; -#[cfg(not(target_env = "musl"))] +#[cfg(all(not(target_os = "android"), not(target_env = "musl")))] use crate::ipc::{OwnedReceiverLockGuard, SHM_CAPACITY}; use crate::{ChildTermination, Command, TrackedChild, arena::PathAccessArena, error::SpawnError}; @@ -33,11 +33,11 @@ pub struct SpyImpl { #[cfg(target_os = "macos")] artifacts: Artifacts, - #[cfg(not(target_env = "musl"))] + #[cfg(all(not(target_os = "android"), not(target_env = "musl")))] preload_path: Box, } -#[cfg(not(target_env = "musl"))] +#[cfg(all(not(target_os = "android"), not(target_env = "musl")))] const PRELOAD_CDYLIB_BINARY: &[u8] = include_bytes!(env!("CARGO_CDYLIB_FILE_FSPY_PRELOAD_UNIX")); impl SpyImpl { @@ -45,8 +45,10 @@ impl SpyImpl { /// /// On musl targets, we don't build a preload library — /// only seccomp-based tracking is used. - pub fn init_in(#[cfg_attr(target_env = "musl", allow(unused))] dir: &Path) -> io::Result { - #[cfg(not(target_env = "musl"))] + pub fn init_in( + #[cfg_attr(any(target_os = "android", target_env = "musl"), allow(unused))] dir: &Path, + ) -> io::Result { + #[cfg(all(not(target_os = "android"), not(target_env = "musl")))] let preload_path = { use const_format::formatcp; use xxhash_rust::const_xxh3::xxh3_128; @@ -64,7 +66,7 @@ impl SpyImpl { }; Ok(Self { - #[cfg(not(target_env = "musl"))] + #[cfg(all(not(target_os = "android"), not(target_env = "musl")))] preload_path, #[cfg(target_os = "macos")] artifacts: { @@ -86,18 +88,18 @@ impl SpyImpl { #[cfg(target_os = "linux")] let supervisor = supervise::().map_err(SpawnError::Supervisor)?; - #[cfg(not(target_env = "musl"))] + #[cfg(all(not(target_os = "android"), not(target_env = "musl")))] let (ipc_channel_conf, ipc_receiver) = channel(SHM_CAPACITY).map_err(SpawnError::ChannelCreation)?; let payload = Payload { - #[cfg(not(target_env = "musl"))] + #[cfg(all(not(target_os = "android"), not(target_env = "musl")))] ipc_channel_conf, #[cfg(target_os = "macos")] artifacts: self.artifacts.clone(), - #[cfg(not(target_env = "musl"))] + #[cfg(all(not(target_os = "android"), not(target_env = "musl")))] preload_path: self.preload_path.clone(), #[cfg(target_os = "linux")] @@ -169,12 +171,12 @@ impl SpyImpl { // Lock the ipc channel after the child has exited. // We are not interested in path accesses from descendants after the main child has exited. - #[cfg(not(target_env = "musl"))] + #[cfg(all(not(target_os = "android"), not(target_env = "musl")))] let ipc_receiver_lock_guard = OwnedReceiverLockGuard::lock_async(ipc_receiver).await?; let path_accesses = PathAccessIterable { arenas, - #[cfg(not(target_env = "musl"))] + #[cfg(all(not(target_os = "android"), not(target_env = "musl")))] ipc_receiver_lock_guard, }; @@ -188,7 +190,7 @@ impl SpyImpl { pub struct PathAccessIterable { arenas: Vec, - #[cfg(not(target_env = "musl"))] + #[cfg(all(not(target_os = "android"), not(target_env = "musl")))] ipc_receiver_lock_guard: OwnedReceiverLockGuard, } @@ -197,12 +199,12 @@ impl PathAccessIterable { let accesses_in_arena = self.arenas.iter().flat_map(|arena| arena.borrow_accesses().iter()).copied(); - #[cfg(not(target_env = "musl"))] + #[cfg(all(not(target_os = "android"), not(target_env = "musl")))] { let accesses_in_shm = self.ipc_receiver_lock_guard.iter_path_accesses(); accesses_in_shm.chain(accesses_in_arena) } - #[cfg(target_env = "musl")] + #[cfg(any(target_os = "android", target_env = "musl"))] { accesses_in_arena } diff --git a/crates/fspy_preload_unix/src/lib.rs b/crates/fspy_preload_unix/src/lib.rs index 9728cd98..aea7e2ed 100644 --- a/crates/fspy_preload_unix/src/lib.rs +++ b/crates/fspy_preload_unix/src/lib.rs @@ -1,12 +1,15 @@ // On musl targets, fspy_preload_unix is not needed since we can track accesses via seccomp-only. // Compile as an empty crate to avoid build failures from missing libc symbols. -#![cfg_attr(not(target_env = "musl"), feature(c_variadic))] +#![cfg_attr(all(not(target_os = "android"), not(target_env = "musl")), feature(c_variadic))] -#[cfg(all(unix, not(target_env = "musl")))] +#[cfg(all(unix, not(target_os = "android"), not(target_env = "musl")))] mod client; -#[cfg(all(unix, not(target_env = "musl")))] + +#[cfg(all(target_os = "linux", not(target_env = "musl")))] mod interceptions; -#[cfg(all(unix, not(target_env = "musl")))] + +#[cfg(all(target_os = "linux", not(target_env = "musl")))] mod libc; -#[cfg(all(unix, not(target_env = "musl")))] + +#[cfg(all(target_os = "linux", not(target_env = "musl")))] mod macros; diff --git a/crates/fspy_seccomp_unotify/Cargo.toml b/crates/fspy_seccomp_unotify/Cargo.toml index 0870032b..3041dbe2 100644 --- a/crates/fspy_seccomp_unotify/Cargo.toml +++ b/crates/fspy_seccomp_unotify/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" publish = false -[target.'cfg(target_os = "linux")'.dependencies] +[target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies] wincode = { workspace = true, features = ["derive"] } libc = { workspace = true } nix = { workspace = true, features = ["process", "fs", "poll", "socket", "uio"] } diff --git a/crates/fspy_seccomp_unotify/src/lib.rs b/crates/fspy_seccomp_unotify/src/lib.rs index a70b0a81..b530f3ba 100644 --- a/crates/fspy_seccomp_unotify/src/lib.rs +++ b/crates/fspy_seccomp_unotify/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg(target_os = "linux")] +#![cfg(any(target_os = "android", target_os = "linux"))] #[cfg(any(feature = "supervisor", feature = "target"))] mod bindings; diff --git a/crates/fspy_seccomp_unotify/src/target.rs b/crates/fspy_seccomp_unotify/src/target.rs index 5406f796..8e424451 100644 --- a/crates/fspy_seccomp_unotify/src/target.rs +++ b/crates/fspy_seccomp_unotify/src/target.rs @@ -7,6 +7,9 @@ use std::{ }; use libc::sock_filter; +#[cfg(target_os = "android")] +use libc::{PR_SET_NO_NEW_PRIVS, prctl}; +#[cfg(not(target_os = "android"))] use nix::sys::prctl::set_no_new_privs; use passfd::FdPassingExt; @@ -19,7 +22,17 @@ use crate::{bindings::install_unotify_filter, payload::SeccompPayload}; /// Returns an error if setting no-new-privs fails, the filter cannot be installed, /// or the IPC socket communication fails. pub fn install_target(payload: &SeccompPayload) -> nix::Result<()> { + #[cfg(not(target_os = "android"))] set_no_new_privs()?; + + #[cfg(target_os = "android")] + { + let ret = unsafe { prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) }; + if ret != 0 { + return Err(nix::Error::last()); + } + } + let sock_filters = payload.filter.0.iter().copied().map(sock_filter::from).collect::>(); let notify_fd = install_unotify_filter(&sock_filters)?; diff --git a/crates/fspy_shared/Cargo.toml b/crates/fspy_shared/Cargo.toml index 07f43b6f..3d174f2f 100644 --- a/crates/fspy_shared/Cargo.toml +++ b/crates/fspy_shared/Cargo.toml @@ -11,11 +11,17 @@ bitflags = { workspace = true } bstr = { workspace = true } bytemuck = { workspace = true, features = ["must_cast", "derive"] } native_str = { workspace = true } -shared_memory = { workspace = true, features = ["logging"] } thiserror = { workspace = true } tracing = { workspace = true } uuid = { workspace = true, features = ["v4"] } +[target.'cfg(not(target_os = "android"))'.dependencies] +shared_memory = { workspace = true, features = ["logging"] } + +[target.'cfg(target_os = "android")'.dependencies] +memmap2 = "0.9.10" +memfd = "0.6" + [target.'cfg(target_os = "windows")'.dependencies] bytemuck = { workspace = true } os_str_bytes = { workspace = true } diff --git a/crates/fspy_shared/src/ipc/channel/shm_io.rs b/crates/fspy_shared/src/ipc/channel/shm_io.rs index 7890043d..9f7cb9eb 100644 --- a/crates/fspy_shared/src/ipc/channel/shm_io.rs +++ b/crates/fspy_shared/src/ipc/channel/shm_io.rs @@ -712,4 +712,4 @@ mod tests { } } } -} +} \ No newline at end of file diff --git a/crates/fspy_shared/src/ipc/mod.rs b/crates/fspy_shared/src/ipc/mod.rs index c7236e5d..7b74554b 100644 --- a/crates/fspy_shared/src/ipc/mod.rs +++ b/crates/fspy_shared/src/ipc/mod.rs @@ -1,4 +1,4 @@ -#[cfg(not(target_env = "musl"))] +#[cfg(all(not(target_os = "android"), not(target_env = "musl")))] pub mod channel; mod native_path; use std::fmt::Debug; diff --git a/crates/fspy_shared_unix/Cargo.toml b/crates/fspy_shared_unix/Cargo.toml index fecabd52..3a8b4e74 100644 --- a/crates/fspy_shared_unix/Cargo.toml +++ b/crates/fspy_shared_unix/Cargo.toml @@ -15,7 +15,7 @@ stackalloc = { workspace = true } [dev-dependencies] -[target.'cfg(target_os = "linux")'.dependencies] +[target.'cfg(any(target_os = "android",target_os = "linux"))'.dependencies] elf = { workspace = true } fspy_seccomp_unotify = { workspace = true, features = ["target"] } memmap2 = { workspace = true } diff --git a/crates/fspy_shared_unix/src/lib.rs b/crates/fspy_shared_unix/src/lib.rs index 5437a3ef..d39490f9 100644 --- a/crates/fspy_shared_unix/src/lib.rs +++ b/crates/fspy_shared_unix/src/lib.rs @@ -5,8 +5,9 @@ pub(crate) mod open_exec; pub mod payload; pub mod spawn; -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "android", target_os = "linux"))] mod elf; -#[cfg(target_os = "linux")] // exposed for verifying static executables in fspy tests +#[cfg(any(target_os = "android", target_os = "linux"))] +// exposed for verifying static executables in fspy tests pub use elf::is_dynamically_linked_to_libc; diff --git a/crates/fspy_shared_unix/src/payload.rs b/crates/fspy_shared_unix/src/payload.rs index 5267bd42..bbfbaa11 100644 --- a/crates/fspy_shared_unix/src/payload.rs +++ b/crates/fspy_shared_unix/src/payload.rs @@ -2,18 +2,18 @@ use std::os::unix::ffi::OsStringExt; use base64::{Engine as _, prelude::BASE64_STANDARD_NO_PAD}; use bstr::BString; -#[cfg(not(target_env = "musl"))] +#[cfg(all(not(target_os = "android"), not(target_env = "musl")))] use fspy_shared::ipc::NativeStr; -#[cfg(not(target_env = "musl"))] +#[cfg(all(not(target_os = "android"), not(target_env = "musl")))] use fspy_shared::ipc::channel::ChannelConf; use wincode::{SchemaRead, SchemaWrite}; #[derive(Debug, SchemaWrite, SchemaRead)] pub struct Payload { - #[cfg(not(target_env = "musl"))] + #[cfg(all(not(target_os = "android"), not(target_env = "musl")))] pub ipc_channel_conf: ChannelConf, - #[cfg(not(target_env = "musl"))] + #[cfg(all(not(target_os = "android"), not(target_env = "musl")))] pub preload_path: Box, #[cfg(target_os = "macos")] diff --git a/crates/fspy_shared_unix/src/spawn/linux/mod.rs b/crates/fspy_shared_unix/src/spawn/linux/mod.rs index 0632999d..31670eb1 100644 --- a/crates/fspy_shared_unix/src/spawn/linux/mod.rs +++ b/crates/fspy_shared_unix/src/spawn/linux/mod.rs @@ -1,11 +1,11 @@ -#[cfg(not(target_env = "musl"))] +#[cfg(all(not(target_os = "android"), not(target_env = "musl")))] use std::{ffi::OsStr, os::unix::ffi::OsStrExt as _, path::Path}; use fspy_seccomp_unotify::{payload::SeccompPayload, target::install_target}; -#[cfg(not(target_env = "musl"))] +#[cfg(all(not(target_os = "android"), not(target_env = "musl")))] use memmap2::Mmap; -#[cfg(not(target_env = "musl"))] +#[cfg(all(not(target_os = "android"), not(target_env = "musl")))] use crate::{elf, exec::ensure_env, open_exec::open_executable}; use crate::{ exec::Exec, @@ -28,11 +28,11 @@ impl PreExec { pub fn handle_exec( command: &mut Exec, - encoded_payload: &EncodedPayload, + _encoded_payload: &EncodedPayload, ) -> nix::Result> { // On musl targets, LD_PRELOAD is not available (cdylib not supported). // Always use seccomp-based tracking instead. - #[cfg(not(target_env = "musl"))] + #[cfg(all(not(target_os = "android"), not(target_env = "musl")))] { let executable_fd = open_executable(Path::new(OsStr::from_bytes(&command.program)))?; // SAFETY: The file descriptor is valid and we only read from the mapping. @@ -51,5 +51,6 @@ pub fn handle_exec( } command.envs.retain(|(name, _)| name != LD_PRELOAD && name != PAYLOAD_ENV_NAME); + Ok(Some(PreExec(encoded_payload.payload.seccomp_payload.clone()))) } diff --git a/crates/fspy_shared_unix/src/spawn/mod.rs b/crates/fspy_shared_unix/src/spawn/mod.rs index e4812577..7273030b 100644 --- a/crates/fspy_shared_unix/src/spawn/mod.rs +++ b/crates/fspy_shared_unix/src/spawn/mod.rs @@ -1,4 +1,4 @@ -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "android", target_os = "linux"))] #[path = "./linux/mod.rs"] mod os_specific;