Skip to content
Merged
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
5 changes: 2 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,10 @@ jobs:
- run: ./.github/tools/github_actions_run_cargo clippy --all-targets --all-features $MACOS_AARCH64_CRATES
- run: ./.github/tools/github_actions_run_cargo build $MACOS_AARCH64_CRATES
- run: ./.github/tools/github_actions_run_cargo nextest $MACOS_AARCH64_CRATES
- name: Test native macOS runner with test-only stdio
- name: Test native macOS runner with in-process broker
run: >-
./.github/tools/github_actions_run_cargo nextest
-p litebox_runner_macos_userland --features test-stdio
-E 'test(=static_macho_rewriter_e2e)'
-p litebox_runner_macos_userland --features test-broker
- name: Check native macOS platform without subpage compatibility
run: |
./.github/tools/github_actions_run_cargo clippy --all-targets --no-default-features -p litebox_platform_macos_userland
Expand Down
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.

1 change: 1 addition & 0 deletions litebox_common_macos/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ edition = "2024"

[dependencies]
litebox = { path = "../litebox", version = "0.1.0" }
litebox_broker_protocol = { path = "../litebox_broker_protocol", version = "0.1.0", default-features = false }
bitflags = { version = "2.13.1", default-features = false }
litebox_syscall_rewriter = { path = "../litebox_syscall_rewriter", version = "0.1.0", default-features = false }
litebox_common_linux = { path = "../litebox_common_linux", version = "0.1.0" }
Expand Down
1 change: 1 addition & 0 deletions litebox_common_macos/src/errno.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ pub enum Errno {
ESHUTDOWN = 58,
ETIMEDOUT = 60,
ECONNREFUSED = 61,
ENAMETOOLONG = 63,
ENOTEMPTY = 66,
ENOSYS = 78,
EOPNOTSUPP = 102,
Expand Down
42 changes: 42 additions & 0 deletions litebox_common_macos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,48 @@ bitflags::bitflags! {
}
}

bitflags::bitflags! {
/// Supported Darwin `open` flags.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct OpenFlags: core::ffi::c_int {
// This is the absence of WRONLY/RDWR bits; do not test it with `contains`.
const RDONLY = 0;
const WRONLY = 0x0001;
const RDWR = 0x0002;
const NONBLOCK = 0x0004;
const APPEND = 0x0008;
const NOFOLLOW = 0x0100;
const CREAT = 0x0200;
const TRUNC = 0x0400;
const EXCL = 0x0800;
const NOCTTY = 0x0002_0000;
const DIRECTORY = 0x0010_0000;
const CLOEXEC = 0x0100_0000;
}
}

bitflags::bitflags! {
/// Darwin descriptor-local flags.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct FileDescriptorFlags: u32 {
const FD_CLOEXEC = 1;
}
}

/// Darwin pathname limit, including the terminating NUL.
pub const PATH_MAX: usize = 1024;

bitflags::bitflags! {
/// Supported Darwin `mmap` flags.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MmapFlags: core::ffi::c_int {
const SHARED = 0x0001;
const PRIVATE = 0x0002;
const FIXED = 0x0010;
const ANONYMOUS = 0x1000;
}
}

/// Native Apple Silicon page size.
pub const PAGE_SIZE: usize = 16384;

Expand Down
78 changes: 78 additions & 0 deletions litebox_common_macos/src/syscall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
//! Typed BSD syscall decoding.

use litebox::utils::{ReinterpretSignedExt as _, ReinterpretUnsignedExt as _, TruncateExt as _};
use litebox_broker_protocol::fs::FileMode;
use zerocopy::{FromBytes, IntoBytes};

use crate::{
MmapFlags, OpenFlags, VmProtection,
errno::Errno,
user_pointers::{UserPtr, UserPtrMut},
};
Expand All @@ -16,6 +18,7 @@ pub mod nr {
pub const EXIT: usize = 1;
pub const READ: usize = 3;
pub const WRITE: usize = 4;
pub const OPEN: usize = 5;
pub const CLOSE: usize = 6;
pub const GETPID: usize = 20;
pub const GETUID: usize = 24;
Expand All @@ -24,8 +27,12 @@ pub mod nr {
pub const DUP: usize = 41;
pub const GETEGID: usize = 43;
pub const GETGID: usize = 47;
pub const MUNMAP: usize = 73;
pub const MPROTECT: usize = 74;
pub const MMAP: usize = 197;
pub const READ_NOCANCEL: usize = 396;
pub const WRITE_NOCANCEL: usize = 397;
pub const OPEN_NOCANCEL: usize = 398;
pub const CLOSE_NOCANCEL: usize = 399;
}

Expand Down Expand Up @@ -69,12 +76,34 @@ pub enum SyscallRequest {
buf: UserPtr<u8>,
count: usize,
},
Open {
path: UserPtr<core::ffi::c_char>,
flags: OpenFlags,
mode: FileMode,
},
Close {
fd: i32,
},
Dup {
fd: i32,
},
Mmap {
address: usize,
length: usize,
protection: VmProtection,
flags: MmapFlags,
fd: i32,
offset: i64,
},
Munmap {
address: usize,
length: usize,
},
Mprotect {
address: usize,
length: usize,
protection: VmProtection,
},
Getpid,
Getppid,
Getuid,
Expand Down Expand Up @@ -121,8 +150,30 @@ impl SyscallRequest {
buf: UserPtr::from_usize(args[1]),
count: args[2],
},
nr::OPEN | nr::OPEN_NOCANCEL => Self::Open {
path: UserPtr::from_usize(args[0]),
flags: OpenFlags::from_bits(int_arg(1)).ok_or(Errno::EINVAL)?,
mode: FileMode::from_u32_bits_truncate(int_arg(2).reinterpret_as_unsigned()),
},
nr::CLOSE | nr::CLOSE_NOCANCEL => Self::Close { fd: int_arg(0) },
nr::DUP => Self::Dup { fd: int_arg(0) },
nr::MMAP => Self::Mmap {
address: args[0],
length: args[1],
protection: VmProtection::from_bits(int_arg(2)).ok_or(Errno::EINVAL)?,
flags: MmapFlags::from_bits(int_arg(3)).ok_or(Errno::EINVAL)?,
fd: int_arg(4),
offset: args[5].reinterpret_as_signed() as i64,
},
nr::MUNMAP => Self::Munmap {
address: args[0],
length: args[1],
},
nr::MPROTECT => Self::Mprotect {
address: args[0],
length: args[1],
protection: VmProtection::from_bits(int_arg(2)).ok_or(Errno::EINVAL)?,
},
nr::GETPID => Self::Getpid,
nr::GETPPID => Self::Getppid,
nr::GETUID => Self::Getuid,
Expand Down Expand Up @@ -179,6 +230,33 @@ mod tests {
SyscallRequest::from_args(u32::MAX as usize - 2, [0; 8]),
Ok(SyscallRequest::MachAbsoluteTime)
));
let request = SyscallRequest::from_args(
nr::MMAP,
[0x4000, 0x8000, 5, 0x12, usize::MAX, 0x1234, 0, 0],
)
.unwrap();
assert!(matches!(
request,
SyscallRequest::Mmap {
address: 0x4000,
length: 0x8000,
protection,
flags,
fd: -1,
offset: 0x1234,
} if protection == (VmProtection::READ | VmProtection::EXECUTE)
&& flags == (MmapFlags::PRIVATE | MmapFlags::FIXED)
));
for (protection, flags) in [(8, 2), (1, 4)] {
assert_eq!(
SyscallRequest::from_args(
nr::MMAP,
[0, 0x4000, protection, flags, usize::MAX, 0, 0, 0],
)
.unwrap_err(),
Errno::EINVAL
);
}
}

#[cfg(target_arch = "aarch64")]
Expand Down
4 changes: 2 additions & 2 deletions litebox_runner_macos_userland/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ litebox_syscall_rewriter = { path = "../litebox_syscall_rewriter", version = "0.
litebox = { path = "../litebox", version = "0.1.0" }

[features]
# In-process broker fixture with buffered stdin and captured stdout/stderr.
test-stdio = ["dep:litebox", "dep:litebox_broker_core", "dep:litebox_broker_host", "dep:litebox_broker_local", "dep:litebox_broker_protocol"]
# Development-only in-process broker fixture.
test-broker = ["dep:litebox", "dep:litebox_broker_core", "dep:litebox_broker_host", "dep:litebox_broker_local", "dep:litebox_broker_protocol"]

[lints]
workspace = true
10 changes: 5 additions & 5 deletions litebox_runner_macos_userland/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use anyhow::{Context as _, Result, bail};
use clap::Parser;
use litebox_common_macos::TaskParams;
use litebox_platform_macos_userland::{GuestAbi, MacosUserland, set_guest_abi};
#[cfg(not(feature = "test-stdio"))]
#[cfg(not(feature = "test-broker"))]
use litebox_shim_macos::MacosShimBuilder;
use std::ffi::CString;

Expand All @@ -23,7 +23,7 @@ pub struct CliArgs {
pub environment_variables: Vec<String>,
}

#[cfg(feature = "test-stdio")]
#[cfg(feature = "test-broker")]
mod test_broker;

pub fn run(cli_args: CliArgs) -> Result<i32> {
Expand All @@ -45,9 +45,9 @@ pub fn run(cli_args: CliArgs) -> Result<i32> {
.collect::<Result<Vec<_>, _>>()
.context("NUL in environment entry")?;
let platform = MacosUserland::new();
#[cfg(not(feature = "test-stdio"))]
#[cfg(not(feature = "test-broker"))]
let builder = MacosShimBuilder::new(platform);
#[cfg(feature = "test-stdio")]
#[cfg(feature = "test-broker")]
let (builder, stdio) = test_broker::setup(platform)?;
let program = builder
.build()
Expand All @@ -63,7 +63,7 @@ pub fn run(cli_args: CliArgs) -> Result<i32> {
unsafe {
litebox_platform_macos_userland::run_thread(entrypoints, &mut initial_ctx);
}
#[cfg(feature = "test-stdio")]
#[cfg(feature = "test-broker")]
test_broker::flush_output(&stdio)?;
process
.exit_status()
Expand Down
2 changes: 1 addition & 1 deletion litebox_runner_macos_userland/src/test_broker.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

//! In-process broker fixture for the test-stdio runner feature.
//! In-process broker fixture for the test-broker runner feature.

use anyhow::{Context as _, Result, anyhow};
use litebox::LiteBox;
Expand Down
18 changes: 11 additions & 7 deletions litebox_runner_macos_userland/tests/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#![cfg(all(target_os = "macos", target_arch = "aarch64"))]

use litebox_common_macos::{TaskParams, VmProtection, loader::MachoParsedFile};
#[cfg(feature = "test-stdio")]
#[cfg(feature = "test-broker")]
use std::io::Write as _;
use std::{
path::{Path, PathBuf},
Expand Down Expand Up @@ -128,20 +128,24 @@ fn assert_svc_gates(original: &[u8], rewritten: &[u8]) -> usize {
}

/// Both feature configurations exercise the same AOT pipeline and gate ABI.
/// test-stdio adds I/O checks; without it the fixture checks stdio is absent.
/// test-broker adds I/O checks; without it the fixture checks stdio is absent.
#[test]
fn static_macho_rewriter_e2e() {
let dir = tempfile::tempdir().unwrap();
let source = format!(
".set TEST_STDIO, {}\n{}",
usize::from(cfg!(feature = "test-stdio")),
usize::from(cfg!(feature = "test-broker")),
include_str!("fixtures/static_macho.S"),
);
let binary = assemble(dir.path(), &source);
let hooked = rewrite(&binary);
let original = std::fs::read(&binary).unwrap();
let rewritten = std::fs::read(&hooked).unwrap();
let expected_sites = if cfg!(feature = "test-stdio") { 23 } else { 15 };
let expected_sites = if cfg!(feature = "test-broker") {
23
} else {
15
};
assert_eq!(assert_svc_gates(&original, &rewritten), expected_sites);
let parsed = MachoParsedFile::parse(&original).unwrap();
// Parsing is independent of the byte slice's alignment.
Expand All @@ -162,12 +166,12 @@ fn static_macho_rewriter_e2e() {
.stderr(Stdio::piped())
.spawn()
.unwrap();
#[cfg(feature = "test-stdio")]
#[cfg(feature = "test-broker")]
child.stdin.take().unwrap().write_all(b"hello\n").unwrap();
// No input is needed in the default configuration.
drop(child.stdin.take());
let output = child.wait_with_output().unwrap();
#[cfg(feature = "test-stdio")]
#[cfg(feature = "test-broker")]
{
println!("guest stdout: {}", String::from_utf8_lossy(&output.stdout));
eprintln!("guest stderr: {}", String::from_utf8_lossy(&output.stderr));
Expand All @@ -178,7 +182,7 @@ fn static_macho_rewriter_e2e() {
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let expected: &[u8] = if cfg!(feature = "test-stdio") {
let expected: &[u8] = if cfg!(feature = "test-broker") {
b"hello\n"
} else {
b""
Expand Down
2 changes: 1 addition & 1 deletion litebox_shim_macos/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ edition = "2024"

[dependencies]
litebox = { path = "../litebox", version = "0.1.0" }
litebox_broker_protocol = { path = "../litebox_broker_protocol", version = "0.1.0", default-features = false }
litebox_common_macos = { path = "../litebox_common_macos", version = "0.1.0" }
litebox_util_log = { path = "../litebox_util_log", version = "0.1.0" }
litebox_syscall_rewriter = { path = "../litebox_syscall_rewriter", version = "0.1.0", default-features = false }
Expand All @@ -13,7 +14,6 @@ litebox_syscall_rewriter = { path = "../litebox_syscall_rewriter", version = "0.
litebox_broker_core = { path = "../litebox_broker_core", version = "0.1.0", features = ["test-support"] }
litebox_broker_host = { path = "../litebox_broker_host", version = "0.1.0", features = ["test-support"] }
litebox_broker_local = { path = "../litebox_broker_local", version = "0.1.0" }
litebox_broker_protocol = { path = "../litebox_broker_protocol", version = "0.1.0" }
litebox_platform_macos_userland = { path = "../litebox_platform_macos_userland", version = "0.1.0" }

[lints]
Expand Down
26 changes: 25 additions & 1 deletion litebox_shim_macos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! Minimal Darwin BSD shim for static AArch64 Mach-O guests.
//!
//! Guest mappings and file operations use LiteBox. The runner supplies inherited
//! descriptors. Guest file opening and networking are unsupported.
//! descriptors. Networking is unsupported.

#![no_std]
#![cfg(target_arch = "aarch64")]
Expand Down Expand Up @@ -257,8 +257,32 @@ impl<P: ShimPlatform> Task<P> {
let bytes = buf.to_owned_slice::<P>(length).ok_or(Errno::EFAULT)?;
self.do_write(&fd, &bytes)
}
SyscallRequest::Open { path, flags, mode } => {
let path = self.read_path(path)?;
self.sys_open(path, flags, mode).to_syscall_result()
}
SyscallRequest::Close { fd } => self.sys_close(fd).to_syscall_result(),
SyscallRequest::Dup { fd } => self.sys_dup(fd).to_syscall_result(),
SyscallRequest::Mmap {
address,
length,
protection,
flags,
fd,
offset,
} => self
.sys_mmap(address, length, protection, flags, fd, offset)
.to_syscall_result(),
SyscallRequest::Munmap { address, length } => {
self.sys_munmap(address, length).to_syscall_result()
}
SyscallRequest::Mprotect {
address,
length,
protection,
} => self
.sys_mprotect(address, length, protection)
.to_syscall_result(),
SyscallRequest::Getpid => Ok(self.sys_getpid().cast_unsigned() as usize),
SyscallRequest::Getppid => Ok(self.sys_getppid().cast_unsigned() as usize),
SyscallRequest::Getuid => Ok(self.sys_getuid() as usize),
Expand Down
Loading
Loading