diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index a5e92eb..8f886e7 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -24,17 +24,20 @@ jobs: strategy: fail-fast: false matrix: - target-triple: [x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl] + target-triple: + - x86_64-unknown-linux-gnu + - x86_64-unknown-linux-musl + - aarch64-unknown-linux-gnu + - aarch64-unknown-linux-musl steps: - - name: Install musl lib - if: ${{ contains(matrix.target-triple, 'musl') }} - run: sudo apt-get install musl-tools - - name: Install target triple - run: rustup target install ${{ matrix.target-triple }} + - name: Install cross tools + run: cargo install cross --git https://github.com/cross-rs/cross - name: Checkout uses: actions/checkout@v4 - name: Run tests - run: make test-ci TARGET_TRIPLE=${{ matrix.target-triple }} + run: | + cross test --target ${{ matrix.target-triple }} --tests --examples --all-features + cross test --target ${{ matrix.target-triple }} --tests --examples --no-default-features doc: name: Documentation Check diff --git a/Cargo.toml b/Cargo.toml index cfd349b..246ec7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,5 +33,8 @@ rusqlite = "^0.26" [target.'cfg(target_env = "musl")'.dev-dependencies] reqwest = { version = "^0.11", default-features = false, features = ["rustls-tls"] } -[target.'cfg(not(target_env = "musl"))'.dev-dependencies] +[target.'cfg(all(not(target_env = "musl"), target_arch = "aarch64"))'.dev-dependencies] +reqwest = { version = "^0.11", features = ["native-tls-vendored"] } + +[target.'cfg(not(any(target_env = "musl", target_arch = "aarch64")))'.dev-dependencies] reqwest = { version = "^0.11" } diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 0000000..2886fff --- /dev/null +++ b/Cross.toml @@ -0,0 +1,23 @@ +[target.aarch64-unknown-linux-gnu] +pre-build = [ + "dpkg --add-architecture $CROSS_DEB_ARCH", + "apt-get update && apt-get install --assume-yes libssl-dev:$CROSS_DEB_ARCH libsqlite3-dev:$CROSS_DEB_ARCH" +] + +[target.x86_64-unknown-linux-gnu] +pre-build = [ + "dpkg --add-architecture $CROSS_DEB_ARCH", + "apt-get update && apt-get install --assume-yes libssl-dev:$CROSS_DEB_ARCH libsqlite3-dev:$CROSS_DEB_ARCH" +] + +[target.x86_64-unknown-linux-musl] +pre-build = [ + "dpkg --add-architecture $CROSS_DEB_ARCH", + "apt-get update && apt-get install --assume-yes libsqlite3-dev:$CROSS_DEB_ARCH" +] + +[target.aarch64-unknown-linux-musl] +pre-build = [ + "dpkg --add-architecture $CROSS_DEB_ARCH", + "apt-get update && apt-get install --assume-yes libsqlite3-dev:$CROSS_DEB_ARCH" +] diff --git a/src/builtins/basic.rs b/src/builtins/basic.rs index 5d33789..2b462f4 100644 --- a/src/builtins/basic.rs +++ b/src/builtins/basic.rs @@ -42,7 +42,9 @@ impl RuleSet for BasicCapabilities { // Readlink isn't dangerous because you still need to be able to open the file to do // anything with the resolved name. + #[cfg(target_arch = "x86_64")] Sysno::readlink, + Sysno::readlinkat, // Getpid/tid is fine. Sysno::getpid, diff --git a/src/builtins/danger_zone.rs b/src/builtins/danger_zone.rs index 8e18bca..c2f83f4 100644 --- a/src/builtins/danger_zone.rs +++ b/src/builtins/danger_zone.rs @@ -91,16 +91,23 @@ pub struct ForkAndExec; impl RuleSet for ForkAndExec { fn simple_rules(&self) -> Vec { let mut rules = vec![ - Sysno::fork, Sysno::vfork, - Sysno::execve, Sysno::execveat, - Sysno::wait4, Sysno::waitid, - Sysno::clone, Sysno::clone3, + #[cfg(target_arch = "x86_64")] + Sysno::fork, + #[cfg(target_arch = "x86_64")] + Sysno::vfork, + Sysno::execve, Sysno::execveat, + Sysno::wait4, Sysno::waitid, + Sysno::clone, Sysno::clone3, ]; // musl creates a pipe when it starts a new process, and fails the operation if it can't // create the pipe if cfg!(target_env = "musl") { - rules.extend([Sysno::pipe, Sysno::pipe2]); + rules.extend([ + #[cfg(target_arch = "x86_64")] + Sysno::pipe, + Sysno::pipe2, + ]); } rules diff --git a/src/builtins/network.rs b/src/builtins/network.rs index c959c1d..d6a4d50 100644 --- a/src/builtins/network.rs +++ b/src/builtins/network.rs @@ -11,16 +11,22 @@ use crate::{SeccompRule, RuleSet}; // TODO: add io_uring const NET_IO_SYSCALLS: &[Sysno] = &[ - Sysno::epoll_create, Sysno::epoll_create1, - Sysno::epoll_ctl, Sysno::epoll_wait, Sysno::epoll_pwait, Sysno::epoll_pwait2, - Sysno::select, Sysno::pselect6, - Sysno::poll, Sysno::ppoll, - - Sysno::accept, Sysno::accept4, - + #[cfg(target_arch = "x86_64")] + Sysno::epoll_create, + Sysno::epoll_create1, Sysno::epoll_ctl, + #[cfg(target_arch = "x86_64")] + Sysno::epoll_wait, + Sysno::epoll_pwait, Sysno::epoll_pwait2, + #[cfg(target_arch = "x86_64")] + Sysno::select, + Sysno::pselect6, + #[cfg(target_arch = "x86_64")] + Sysno::poll, + Sysno::ppoll, Sysno::accept, Sysno::accept4, // used in reqwest::blocking I guess to notify when blocking reads finish? - Sysno::eventfd, Sysno::eventfd2, - + #[cfg(target_arch = "x86_64")] + Sysno::eventfd, + Sysno::eventfd2, // Used to set tcp_nodelay Sysno::fcntl, Sysno::ioctl, Sysno::getsockopt, diff --git a/src/builtins/pipes.rs b/src/builtins/pipes.rs index 6dc8d15..e593a61 100644 --- a/src/builtins/pipes.rs +++ b/src/builtins/pipes.rs @@ -8,7 +8,11 @@ use crate::RuleSet; pub struct Pipes; impl RuleSet for Pipes { fn simple_rules(&self) -> Vec { - vec![Sysno::pipe, Sysno::pipe2] + vec![ + #[cfg(target_arch = "x86_64")] + Sysno::pipe, + Sysno::pipe2 + ] } fn name(&self) -> &'static str { diff --git a/src/builtins/systemio.rs b/src/builtins/systemio.rs index 9ff3dfe..be9a65f 100644 --- a/src/builtins/systemio.rs +++ b/src/builtins/systemio.rs @@ -17,18 +17,54 @@ use crate::landlock::{access, AccessFs, BitFlags}; use crate::{RuleSet, SeccompRule}; use super::YesReally; -pub(crate) const IO_READ_SYSCALLS: &[Sysno] = &[Sysno::read, Sysno::readv, Sysno::preadv, Sysno::preadv2, Sysno::pread64, Sysno::lseek]; -pub(crate) const IO_WRITE_SYSCALLS: &[Sysno] = &[Sysno::write, Sysno::writev, Sysno::pwritev, Sysno::pwritev2, Sysno::pwrite64, - Sysno::fsync, Sysno::fdatasync, Sysno::lseek]; -pub(crate) const IO_OPEN_SYSCALLS: &[Sysno] = &[Sysno::open, Sysno::openat, Sysno::openat2]; +pub(crate) const IO_READ_SYSCALLS: &[Sysno] = &[ + Sysno::read, + Sysno::readv, + Sysno::preadv, + Sysno::preadv2, + Sysno::pread64, + Sysno::lseek, +]; +pub(crate) const IO_WRITE_SYSCALLS: &[Sysno] = &[ + Sysno::write, + Sysno::writev, + Sysno::pwritev, + Sysno::pwritev2, + Sysno::pwrite64, + Sysno::fsync, + Sysno::fdatasync, + Sysno::lseek, +]; +pub(crate) const IO_OPEN_SYSCALLS: &[Sysno] = &[ + #[cfg(target_arch = "x86_64")] + Sysno::open, + Sysno::openat, + Sysno::openat2 +]; pub(crate) const IO_IOCTL_SYSCALLS: &[Sysno] = &[Sysno::ioctl, Sysno::fcntl]; // TODO: may want to separate fd-based and filename-based? -pub(crate) const IO_METADATA_SYSCALLS: &[Sysno] = &[Sysno::stat, Sysno::fstat, Sysno::newfstatat, - Sysno::lstat, Sysno::statx, - Sysno::getdents, Sysno::getdents64, - Sysno::getcwd]; +pub(crate) const IO_METADATA_SYSCALLS: &[Sysno] = &[ + #[cfg(target_arch = "x86_64")] + Sysno::stat, + Sysno::fstat, + #[cfg(target_arch = "x86_64")] + Sysno::newfstatat, + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + Sysno::fstatat, + #[cfg(target_arch = "x86_64")] + Sysno::lstat, + Sysno::statx, + #[cfg(target_arch = "x86_64")] + Sysno::getdents, + Sysno::getdents64, + Sysno::getcwd, +]; pub(crate) const IO_CLOSE_SYSCALLS: &[Sysno] = &[Sysno::close, Sysno::close_range]; -pub(crate) const IO_UNLINK_SYSCALLS: &[Sysno] = &[Sysno::unlink, Sysno::unlinkat]; +pub(crate) const IO_UNLINK_SYSCALLS: &[Sysno] = &[ + #[cfg(target_arch = "x86_64")] + Sysno::unlink, + Sysno::unlinkat +]; // TODO: split into SystemIO, SystemIOLandlock, SystemIOSeccompRestricted so that you can't call a // landlock function after using a seccomp argument filter function (or vice versa). You can still @@ -125,11 +161,14 @@ impl SystemIO { const WRITECREATE: u64 = O_WRONLY | O_RDWR | O_APPEND | O_CREAT | O_EXCL;// | O_TMPFILE; // flags are the second argument for open but the third for openat - let rule = SeccompRule::new(Sysno::open) - .and_condition(seccomp_arg_filter!(arg1 & WRITECREATE == 0)); - self.custom.entry(Sysno::open) - .or_insert_with(Vec::new) - .push(rule); + #[cfg(target_arch = "x86_64")] + { + let rule = SeccompRule::new(Sysno::open) + .and_condition(seccomp_arg_filter!(arg1 & WRITECREATE == 0)); + self.custom.entry(Sysno::open) + .or_insert_with(Vec::new) + .push(rule); + } let rule = SeccompRule::new(Sysno::openat) .and_condition(seccomp_arg_filter!(arg2 & WRITECREATE == 0)); @@ -317,6 +356,8 @@ impl SystemIO { self.insert_flags(path, new_flags); // allow relevant syscalls as well + // creat only exists on x86-64, aarch64 uses O_CREAT with open + #[cfg(target_arch = "x86_64")] self.allowed.extend(&[Sysno::creat]); self.allow_open().yes_really() } @@ -341,7 +382,11 @@ impl SystemIO { self.insert_flags(path, new_flags); // allow relevant syscalls as well - self.allowed.extend(&[Sysno::mkdir, Sysno::mkdirat]); + self.allowed.extend(&[ + #[cfg(target_arch = "x86_64")] + Sysno::mkdir, + Sysno::mkdirat + ]); self } @@ -352,7 +397,11 @@ impl SystemIO { self.insert_flags(path, new_flags); // allow relevant syscalls as well - self.allowed.extend(&[Sysno::unlink, Sysno::unlinkat]); + self.allowed.extend(&[ + #[cfg(target_arch = "x86_64")] + Sysno::unlink, + Sysno::unlinkat + ]); self } @@ -371,7 +420,11 @@ impl SystemIO { // allow relevant syscalls as well // unlinkat may be be used to remove directories as well so we include it here, since files // will be protected by landlock anyway. - self.allowed.extend(&[Sysno::rmdir, Sysno::unlinkat]); + self.allowed.extend(&[ + #[cfg(target_arch = "x86_64")] + Sysno::rmdir, + Sysno::unlinkat + ]); self } } diff --git a/src/isolate/isolate_sys.rs b/src/isolate/isolate_sys.rs index e7547b8..1f69ac8 100644 --- a/src/isolate/isolate_sys.rs +++ b/src/isolate/isolate_sys.rs @@ -18,6 +18,7 @@ use std::ffi::CString; use super::IsolateError; use std::io::Write; use std::os::fd::FromRawFd; +use std::os::raw::c_char; use std::collections::HashMap; @@ -142,7 +143,7 @@ pub fn make_tempdir(isolate_name: &str) -> PathBuf { let template_str = format!("/tmp/{}.XXXXXX\0", isolate_name); let mut dir_buf: Vec = template_str.clone().into_bytes(); - let dir_ptr: *mut i8 = dir_buf.as_mut_ptr().cast::(); + let dir_ptr: *mut c_char = dir_buf.as_mut_ptr().cast::(); let ret = unsafe { libc::mkdtemp(dir_ptr) }; fail_null!(ret, "failed to create temporary directory after clone"); diff --git a/src/lib.rs b/src/lib.rs index e57e092..ea9a4cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -530,8 +530,8 @@ impl SafetyContext { assert!(result.is_none(), "extrasafe logic error: somehow inserted the same syscall's rules twice"); } - #[cfg(not(all(target_arch = "x86_64", target_os = "linux")))] - compile_error!("extrasafe is currently only supported on linux x86_64"); + #[cfg(not(all(target_os = "linux", any(target_arch = "aarch64", target_arch = "x86_64"))))] + compile_error!("extrasafe is currently only supported on arm64 and amd64 linux"); let seccompiler_filter = SeccompilerFilter::new( rules_map,