diff --git a/Cargo.lock b/Cargo.lock index c0f34b73..bf6063ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1273,6 +1273,8 @@ dependencies = [ "bstr", "bytemuck", "ctor", + "memfd", + "memmap2", "os_str_bytes", "rustc-hash", "shared_memory", @@ -1861,11 +1863,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 7269f0f9..bb4a5ce9 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..5a8aab8e 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(not(target_os = "android"), not(target_env = "musl"), feature(c_variadic))] -#[cfg(all(unix, not(target_env = "musl")))] +#[cfg(all(target_os = "linux", 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 9a4a2a08..c0733c68 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] bincode = { workspace = true } 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 756e4512..af4ea41d 100644 --- a/crates/fspy_shared/Cargo.toml +++ b/crates/fspy_shared/Cargo.toml @@ -10,11 +10,17 @@ bincode = { workspace = true } bitflags = { workspace = true } bstr = { workspace = true } bytemuck = { workspace = true, features = ["must_cast", "derive"] } -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/mod.rs b/crates/fspy_shared/src/ipc/channel/mod.rs index 3e67cea8..c3309edc 100644 --- a/crates/fspy_shared/src/ipc/channel/mod.rs +++ b/crates/fspy_shared/src/ipc/channel/mod.rs @@ -1,269 +1,53 @@ -//! Fast mpsc IPC channel implementation based on shared memory. +#[cfg(not(target_env = "musl"))] +pub mod channel; +mod native_path; +pub(crate) mod native_str; -mod shm_io; +use std::fmt::Debug; -use std::{env::temp_dir, fs::File, io, ops::Deref, path::PathBuf, sync::Arc}; +use bincode::{BorrowDecode, Encode, config::Configuration}; +use bitflags::bitflags; +pub use native_path::NativePath; +pub use native_str::NativeStr; -use bincode::{Decode, Encode}; -use shared_memory::{Shmem, ShmemConf}; -pub use shm_io::FrameMut; -use shm_io::{ShmReader, ShmWriter}; -use tracing::debug; -use uuid::Uuid; +pub const BINCODE_CONFIG: Configuration = bincode::config::standard(); -use super::NativeStr; +#[derive(Encode, BorrowDecode, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +pub struct AccessMode(u8); -/// Serializable configuration to create channel senders. -#[derive(Encode, Decode, Clone, Debug)] -pub struct ChannelConf { - lock_file_path: Box, - shm_id: Arc, - shm_size: usize, -} - -/// Creates a mpsc IPC channel with one receiver and a `ChannelConf` that can be passed around processes and used to create multiple senders -#[expect( - clippy::missing_errors_doc, - reason = "non-vite crate: cannot use vite_str/vite_path types" -)] -pub fn channel(capacity: usize) -> io::Result<(ChannelConf, Receiver)> { - // Initialize the lock file with a unique name. - let lock_file_path = temp_dir().join(format!("fspy_ipc_{}.lock", Uuid::new_v4())); - - #[cfg_attr( - not(windows), - expect(unused_mut, reason = "mut required on Windows, unused on Unix") - )] - let mut conf = ShmemConf::new().size(capacity); - // On Windows, allow opening raw shared memory (without backing file) for DLL injection scenarios - #[cfg(target_os = "windows")] - { - conf = conf.allow_raw(true); +bitflags! { + impl AccessMode: u8 { + const READ = 1; + const WRITE = 1 << 1; + const READ_DIR = 1 << 2; } - - let shm = conf.create().map_err(io::Error::other)?; - - let conf = ChannelConf { - lock_file_path: lock_file_path.as_os_str().into(), - shm_id: shm.get_os_id().into(), - shm_size: capacity, - }; - - let receiver = Receiver::new(lock_file_path, shm)?; - Ok((conf, receiver)) } -impl ChannelConf { - /// Creates a sender. - /// - /// This doesn't block on the file lock. Instead it returns immediately with error if the receiver is locked or dropped. - #[expect( - clippy::missing_errors_doc, - reason = "error conditions are self-evident from return type" - )] - pub fn sender(&self) -> io::Result { - let lock_file = File::open(self.lock_file_path.to_cow_os_str())?; - lock_file.try_lock_shared()?; - - #[cfg_attr( - not(windows), - expect(unused_mut, reason = "mut required on Windows, unused on Unix") - )] - let mut conf = ShmemConf::new().size(self.shm_size).os_id(&self.shm_id); - // On Windows, allow opening raw shared memory (without backing file) for DLL injection scenarios - #[cfg(target_os = "windows")] - { - conf = conf.allow_raw(true); +impl Debug for AccessMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + struct InternalAccessMode(AccessMode); + impl Debug for InternalAccessMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + bitflags::parser::to_writer(&self.0, f) + } } - let shm = conf.open().map_err(io::Error::other)?; - // SAFETY: `shm` is a freshly opened shared memory region with valid pointer and size. - // Exclusive write access is ensured by the shared file lock held by this sender. - let writer = unsafe { ShmWriter::new(shm) }; - Ok(Sender { writer, lock_file, lock_file_path: self.lock_file_path.clone() }) + f.debug_tuple("AccessMode").field(&InternalAccessMode(*self)).finish() } } -pub struct Sender { - writer: ShmWriter, - lock_file_path: Box, - lock_file: File, +#[derive(Encode, BorrowDecode, Debug, Clone, Copy, PartialEq, Eq)] +pub struct PathAccess<'a> { + pub mode: AccessMode, + pub path: &'a NativePath, + // TODO: add follow_symlinks (O_NOFOLLOW) } -impl Drop for Sender { - fn drop(&mut self) { - if let Err(err) = self.lock_file.unlock() { - debug!("Failed to unlock the shared IPC lock {:?}: {}", self.lock_file_path, err); - } +impl<'a> PathAccess<'a> { + pub fn read(path: impl Into<&'a NativePath>) -> Self { + Self { mode: AccessMode::READ, path: path.into() } } -} -impl Deref for Sender { - type Target = ShmWriter; - - fn deref(&self) -> &Self::Target { - &self.writer + pub fn read_dir(path: impl Into<&'a NativePath>) -> Self { + Self { mode: AccessMode::READ_DIR, path: path.into() } } -} - -#[expect( - clippy::non_send_fields_in_send_ty, - reason = "`Sender` holds a shared file lock that ensures there's no reader, so `shm` can be safely written to" -)] -/// SAFETY: `Sender` holds a shared file lock that ensures there's no reader, so `shm` can be safely written to. -unsafe impl Send for Sender {} - -/// SAFETY: `Sender` holds a shared file lock that ensures there's no reader, so `shm` can be safely written to. -unsafe impl Sync for Sender {} - -/// The unique receiver side of an IPC channel. -/// Owns the lock file and removes it on drop. -pub struct Receiver { - lock_file_path: PathBuf, - lock_file: File, - shm: Shmem, -} - -#[expect( - clippy::non_send_fields_in_send_ty, - reason = "Receiver doesn't read or write `shm`. It only pass it to `ReceiverLockGuard` under the lock" -)] -/// SAFETY: `Receiver` doesn't read or write `shm`. It only passes it to `ReceiverLockGuard` under the lock. -unsafe impl Send for Receiver {} - -/// SAFETY: `Receiver` doesn't read or write `shm`. It only passes it to `ReceiverLockGuard` under the lock. -unsafe impl Sync for Receiver {} - -impl Drop for Receiver { - fn drop(&mut self) { - if let Err(err) = std::fs::remove_file(&self.lock_file_path) { - debug!("Failed to remove IPC lock file {:?}: {}", self.lock_file_path, err); - } - } -} - -impl Receiver { - fn new(lock_file_path: PathBuf, shm: Shmem) -> io::Result { - let lock_file = File::create(&lock_file_path)?; - Ok(Self { lock_file_path, lock_file, shm }) - } - - /// Lock the shared memory for unique read access. - /// Blocks until all the senders have dropped (or processes owning them have all exited) so the shared memory can be safely read. - /// During the lifetime of returned `ReceiverReadGuard`, no new senders can be created (`ChannelConf::sender` would fail). - #[expect( - clippy::missing_errors_doc, - reason = "error conditions are self-evident from return type" - )] - pub fn lock(&self) -> io::Result> { - self.lock_file.lock()?; - // SAFETY: The exclusive file lock is held, so no writers can access the shared memory. - // The lock ensures all prior writes are visible to this thread. - let reader = ShmReader::new(unsafe { self.shm.as_slice() }); - Ok(ReceiverLockGuard { reader, lock_file: &self.lock_file }) - } -} - -pub struct ReceiverLockGuard<'a> { - reader: ShmReader<&'a [u8]>, - lock_file: &'a File, -} - -impl Drop for ReceiverLockGuard<'_> { - fn drop(&mut self) { - if let Err(err) = self.lock_file.unlock() { - debug!("Failed to unlock IPC lock file: {}", err); - } - } -} -impl<'a> Deref for ReceiverLockGuard<'a> { - type Target = ShmReader<&'a [u8]>; - - fn deref(&self) -> &Self::Target { - &self.reader - } -} - -#[cfg(test)] -mod tests { - use std::{num::NonZeroUsize, str::from_utf8}; - - use bstr::B; - use subprocess_test::command_for_fn; - - use super::*; - - #[test] - fn smoke() { - let (conf, receiver) = channel(100).unwrap(); - let cmd = command_for_fn!(conf, |conf: ChannelConf| { - let sender = conf.sender().unwrap(); - let frame_size = NonZeroUsize::new(2).unwrap(); - let mut frame = sender.claim_frame(frame_size).unwrap(); - frame.copy_from_slice(&[4, 2]); - }); - assert!(std::process::Command::from(cmd).status().unwrap().success()); - - let lock = receiver.lock().unwrap(); - let mut frames = lock.iter_frames(); - - let received_frame = frames.next().unwrap(); - assert_eq!(received_frame, &[4, 2]); - - assert!(frames.next().is_none()); - } - - #[test] - #[expect(clippy::print_stdout, reason = "test diagnostics")] - fn forbid_new_senders_after_locked() { - let (conf, receiver) = channel(42).unwrap(); - let _lock = receiver.lock().unwrap(); - - let cmd = command_for_fn!(conf, |conf: ChannelConf| { - print!("{}", conf.sender().is_ok()); - }); - let output = std::process::Command::from(cmd).output().unwrap(); - assert_eq!(B(&output.stdout), B("false")); - } - - #[test] - #[expect(clippy::print_stdout, reason = "test diagnostics")] - fn forbid_new_senders_after_receiver_dropped() { - let (conf, receiver) = channel(42).unwrap(); - drop(receiver); - - let cmd = command_for_fn!(conf, |conf: ChannelConf| { - print!("{}", conf.sender().is_ok()); - }); - let output = std::process::Command::from(cmd).output().unwrap(); - assert_eq!(B(&output.stdout), B("false")); - } - - #[test] - fn concurrent_senders() { - let (conf, receiver) = channel(8192).unwrap(); - for i in 0u16..200 { - let cmd = command_for_fn!((conf.clone(), i), |(conf, i): (ChannelConf, u16)| { - let sender = conf.sender().unwrap(); - let data_to_send = i.to_string(); - sender - .claim_frame(NonZeroUsize::new(data_to_send.len()).unwrap()) - .unwrap() - .copy_from_slice(data_to_send.as_bytes()); - }); - let output = std::process::Command::from(cmd).output().unwrap(); - assert!( - output.status.success(), - "Failed to send in iteration {}: {:?}", - i, - B(&output.stderr) - ); - } - let lock = receiver.lock().unwrap(); - let mut received_values: Vec = lock - .iter_frames() - .map(|frame| from_utf8(frame).unwrap().parse::().unwrap()) - .collect(); - received_values.sort_unstable(); - assert_eq!(received_values, (0u16..200).collect::>()); - } -} +} \ No newline at end of file diff --git a/crates/fspy_shared/src/ipc/channel/shm_io.rs b/crates/fspy_shared/src/ipc/channel/shm_io.rs index 39a490d1..95aa6704 100644 --- a/crates/fspy_shared/src/ipc/channel/shm_io.rs +++ b/crates/fspy_shared/src/ipc/channel/shm_io.rs @@ -12,6 +12,9 @@ use bincode::{ Encode, config::Config, enc::write::SizeWriter, encode_into_slice, encode_into_writer, }; use bytemuck::must_cast; +#[cfg(target_os = "android")] +use memmap2::MmapMut; +#[cfg(not(target_os = "android"))] use shared_memory::Shmem; // `ShmWriter` writes headers using atomic operations to prevent partial writes due to crashes, @@ -30,11 +33,18 @@ pub trait AsRawSlice { fn as_raw_slice(&self) -> *mut [u8]; } +#[cfg(not(target_os = "android"))] impl AsRawSlice for Shmem { fn as_raw_slice(&self) -> *mut [u8] { slice_from_raw_parts_mut(self.as_ptr(), self.len()) } } +#[cfg(target_os = "android")] +impl AsRawSlice for MmapMut { + fn as_raw_slice(&self) -> *mut [u8] { + slice_from_raw_parts_mut(self.as_ptr() as *mut u8, self.len()) + } +} /// A concurrent shared memory writer. /// diff --git a/crates/fspy_shared/src/ipc/mod.rs b/crates/fspy_shared/src/ipc/mod.rs index 17df1c2d..ddaea6a8 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; pub(crate) mod native_str; diff --git a/crates/fspy_shared_unix/Cargo.toml b/crates/fspy_shared_unix/Cargo.toml index 4afedf81..c5266c39 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 11c3e1bb..599d01bb 100644 --- a/crates/fspy_shared_unix/src/payload.rs +++ b/crates/fspy_shared_unix/src/payload.rs @@ -3,17 +3,17 @@ use std::os::unix::ffi::OsStringExt; use base64::{Engine as _, prelude::BASE64_STANDARD_NO_PAD}; use bincode::{Decode, Encode, config::standard}; 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; #[derive(Debug, Encode, Decode)] 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..9ff6be49 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()))) + Ok(None) + // 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;