Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 13 additions & 2 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion crates/fspy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion crates/fspy/src/artifact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}
Expand Down
6 changes: 3 additions & 3 deletions crates/fspy/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down
32 changes: 17 additions & 15 deletions crates/fspy/src/unix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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};

Expand All @@ -33,20 +33,22 @@ 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<NativeStr>,
}

#[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 {
/// Initialize the fs access spy by writing the preload library on disk.
///
/// 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<Self> {
#[cfg(not(target_env = "musl"))]
pub fn init_in(
#[cfg_attr(any(target_os = "android", target_env = "musl"), allow(unused))] dir: &Path,
) -> io::Result<Self> {
#[cfg(all(not(target_os = "android"), not(target_env = "musl")))]
let preload_path = {
use const_format::formatcp;
use xxhash_rust::const_xxh3::xxh3_128;
Expand All @@ -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: {
Expand All @@ -86,18 +88,18 @@ impl SpyImpl {
#[cfg(target_os = "linux")]
let supervisor = supervise::<SyscallHandler>().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")]
Expand Down Expand Up @@ -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,
};

Expand All @@ -188,7 +190,7 @@ impl SpyImpl {

pub struct PathAccessIterable {
arenas: Vec<PathAccessArena>,
#[cfg(not(target_env = "musl"))]
#[cfg(all(not(target_os = "android"), not(target_env = "musl")))]
ipc_receiver_lock_guard: OwnedReceiverLockGuard,
}

Expand All @@ -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
}
Expand Down
13 changes: 8 additions & 5 deletions crates/fspy_preload_unix/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 1 addition & 1 deletion crates/fspy_seccomp_unotify/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
2 changes: 1 addition & 1 deletion crates/fspy_seccomp_unotify/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![cfg(target_os = "linux")]
#![cfg(any(target_os = "android", target_os = "linux"))]

#[cfg(any(feature = "supervisor", feature = "target"))]
mod bindings;
Expand Down
13 changes: 13 additions & 0 deletions crates/fspy_seccomp_unotify/src/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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::<Vec<sock_filter>>();
let notify_fd = install_unotify_filter(&sock_filters)?;
Expand Down
8 changes: 7 additions & 1 deletion crates/fspy_shared/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
Loading