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
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.

4 changes: 2 additions & 2 deletions dev_bench/unixbench/prepare_unixbench.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ def prepare_benchmark(
"""
Prepare a single benchmark using litebox_packager.

The packager discovers dependencies, rewrites all ELFs, and creates a tar
(including litebox_rtld_audit.so). The rewritten main binary is extracted
The packager discovers dependencies, rewrites all ELFs, and creates a tar.
The rewritten main binary is extracted
from the tar and placed alongside it.
Comment on lines +64 to 66
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: reflow text


Returns True on success.
Expand Down
1 change: 0 additions & 1 deletion dev_tests/src/boilerplate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ const SKIP_FILES: &[&str] = &[
"LICENSE",
"litebox/src/sync/mutex.rs",
"litebox/src/sync/rwlock.rs",
"litebox_rtld_audit/Makefile",
"litebox_runner_linux_on_windows_userland/tests/test-bins/hello_exec_nolibc",
"litebox_runner_linux_on_windows_userland/tests/test-bins/hello_thread",
"litebox_runner_linux_on_windows_userland/tests/test-bins/hello_thread_static",
Expand Down
1 change: 0 additions & 1 deletion dev_tests/src/ratchet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ fn ratchet_globals() -> Result<()> {
("litebox_platform_lvbs/", 23),
("litebox_platform_multiplex/", 1),
("litebox_platform_windows_userland/", 8),
("litebox_runner_linux_userland/", 1),
("litebox_runner_lvbs/", 5),
("litebox_runner_snp/", 1),
("litebox_shim_linux/", 1),
Expand Down
40 changes: 35 additions & 5 deletions litebox_common_linux/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ pub enum ElfParseError<E> {
BadTrampoline,
#[error("Invalid trampoline version")]
BadTrampolineVersion,
#[error("Binary not patched for syscall rewriting")]
UnpatchedBinary,
#[error("Unsupported ELF type")]
UnsupportedType,
#[error("Bad interpreter")]
Expand All @@ -141,6 +143,7 @@ impl<E: Into<Errno>> From<ElfParseError<E>> for Errno {
| ElfParseError::BadFormat
| ElfParseError::BadTrampoline
| ElfParseError::BadTrampolineVersion
| ElfParseError::UnpatchedBinary
| ElfParseError::BadInterp
| ElfParseError::UnsupportedType => Errno::ENOEXEC,
ElfParseError::Io(err) => err.into(),
Expand Down Expand Up @@ -218,6 +221,11 @@ impl ElfParsedFile {
})
}

/// Returns `true` if a trampoline was parsed and will be mapped by `load()`.
pub fn has_trampoline(&self) -> bool {
self.trampoline.is_some()
}

/// Parse the LiteBox trampoline data, if any.
///
/// The trampoline header is located at the end of the file (last 32/20 bytes).
Expand Down Expand Up @@ -251,7 +259,8 @@ impl ElfParsedFile {

// File must be large enough to contain the header
if file_size < header_size as u64 {
return Ok(());
// Too small for a trampoline header — binary is unpatched.
return Err(ElfParseError::UnpatchedBinary);
}

// Read the header from the end of the file
Expand All @@ -267,8 +276,9 @@ impl ElfParsedFile {
if &header_buf[0..7] == b"LITEBOX" {
return Err(ElfParseError::BadTrampolineVersion);
}
// No trampoline found, which is OK (not all binaries are rewritten)
return Ok(());
// No trampoline found. When using the syscall rewriter backend
// (syscall_entry_point != 0), all binaries must be patched.
return Err(ElfParseError::UnpatchedBinary);
}

let (file_offset, vaddr, trampoline_size) = if cfg!(target_pointer_width = "64") {
Expand All @@ -293,9 +303,11 @@ impl ElfParsedFile {
)
};

// Validate trampoline size
// trampoline_size == 0 means the rewriter checked this binary and found
// no syscall instructions. The magic header acts as a "checked" marker so
// the runtime skips eager code-segment patching. No trampoline to map.
if trampoline_size == 0 {
return Err(ElfParseError::BadTrampoline);
return Ok(());
}

// Verify the file offset is page-aligned (as required by the rewriter)
Expand Down Expand Up @@ -567,6 +579,24 @@ pub trait ReadAt {
fn size(&mut self) -> Result<u64, Self::Error>;
}

impl ReadAt for &[u8] {
type Error = Errno;

fn read_at(&mut self, offset: u64, buf: &mut [u8]) -> Result<(), Self::Error> {
let offset: usize = offset.truncate();
let end = offset.checked_add(buf.len()).ok_or(Errno::EINVAL)?;
if end > self.len() {
return Err(Errno::EINVAL);
}
buf.copy_from_slice(&self[offset..end]);
Ok(())
}

fn size(&mut self) -> Result<u64, Self::Error> {
Ok(self.len() as u64)
}
}

pub trait MapMemory {
type Error;

Expand Down
39 changes: 1 addition & 38 deletions litebox_packager/build.rs
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that the entire body of this has been removed, I don't think this file is needed anymore, right?

Original file line number Diff line number Diff line change
@@ -1,43 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

use std::path::PathBuf;

const RTLD_AUDIT_DIR: &str = "../litebox_rtld_audit";

fn main() {
let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
if target_arch != "x86_64" {
return;
}

let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
let mut make_cmd = std::process::Command::new("make");
make_cmd
.current_dir(RTLD_AUDIT_DIR)
.env("OUT_DIR", &out_dir)
.env("ARCH", &target_arch);
// Always build without DEBUG for the packager -- packaged binaries are
// release artifacts.
make_cmd.env_remove("DEBUG");
// Force rebuild in case a stale artifact exists from a different config.
let _ = std::fs::remove_file(out_dir.join("litebox_rtld_audit.so"));

let output = make_cmd
.output()
.expect("Failed to execute make for rtld_audit");
assert!(
output.status.success(),
"failed to build rtld_audit.so via make:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
assert!(
out_dir.join("litebox_rtld_audit.so").exists(),
"Build failed to create litebox_rtld_audit.so"
);

println!("cargo:rerun-if-changed={RTLD_AUDIT_DIR}/rtld_audit.c");
println!("cargo:rerun-if-changed={RTLD_AUDIT_DIR}/Makefile");
println!("cargo:rerun-if-changed=build.rs");
// rtld_audit has been removed; nothing to build.
}
17 changes: 0 additions & 17 deletions litebox_packager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,23 +358,6 @@ fn finalize_tar(
});
}

// Include the rtld audit library so the rewriter backend can load it.
#[cfg(target_arch = "x86_64")]
{
const RTLD_AUDIT_TAR_PATH: &str = "lib/litebox_rtld_audit.so";
if !added_tar_paths.insert(RTLD_AUDIT_TAR_PATH.to_string()) {
bail!(
"tar already contains {RTLD_AUDIT_TAR_PATH} -- \
remove the conflicting entry or use --no-rewrite"
);
}
tar_entries.push(TarEntry {
tar_path: RTLD_AUDIT_TAR_PATH.to_string(),
data: include_bytes!(concat!(env!("OUT_DIR"), "/litebox_rtld_audit.so")).to_vec(),
mode: 0o755,
});
}

// Build tar.
eprintln!("Creating {}...", args.output.display());
build_tar(&tar_entries, &args.output)?;
Expand Down
58 changes: 55 additions & 3 deletions litebox_platform_linux_userland/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,8 @@ core::arch::global_asm!(
"
.section .tbss
.align 8
saved_restart_addr:
.quad 0
scratch:
.quad 0
host_sp:
Expand Down Expand Up @@ -651,6 +653,13 @@ syscall_callback:
// expectations of `interrupt_signal_handler`.
mov BYTE PTR gs:in_guest@tpoff, 0

// Save guest R11 (syscall call-site restart address from the rewriter
// trampoline) to TLS before it is clobbered by the fsbase/gsbase save
// sequence below. This value is not placed in pt_regs (which holds
// RFLAGS in the r11 slot per the kernel ABI); instead it is kept in
// TLS for future SA_RESTART support.
mov gs:saved_restart_addr@tpoff, r11

// Restore host fs base.
rdfsbase r11
mov gs:guest_fsbase@tpoff, r11
Expand All @@ -660,6 +669,25 @@ syscall_callback:
// Switch to the top of the guest context.
mov r11, rsp
mov rsp, fs:guest_context_top@tpoff
jmp .Lsyscall_save_regs

.globl syscall_callback_redzone
syscall_callback_redzone:
// Same as syscall_callback, but the trampoline has already reserved
// 128 bytes below RSP to protect the SysV red zone.
mov BYTE PTR gs:in_guest@tpoff, 0
mov gs:saved_restart_addr@tpoff, r11
rdfsbase r11
mov gs:guest_fsbase@tpoff, r11
rdgsbase r11
wrfsbase r11

// The trampoline lowered RSP by 128 bytes with LEA, so recover the
// architectural guest stack pointer before saving pt_regs.
lea r11, [rsp + 128]
mov rsp, fs:guest_context_top@tpoff

.Lsyscall_save_regs:

// TODO: save float and vector registers (xsave or fxsave)
// Save caller-saved registers
Expand All @@ -678,7 +706,7 @@ syscall_callback:
push r8 // pt_regs->r8
push r9 // pt_regs->r9
push r10 // pt_regs->r10
push [rsp + 88] // pt_regs->r11 = rflags
push [rsp + 88] // pt_regs->r11 = rflags (matching real syscall ABI)
push rbx // pt_regs->bx
push rbp // pt_regs->bp
push r12 // pt_regs->r12
Expand Down Expand Up @@ -1967,6 +1995,8 @@ impl litebox::platform::StdioProvider for LinuxUserland {
unsafe extern "C" {
// Defined in asm blocks above
fn syscall_callback() -> isize;
#[cfg(target_arch = "x86_64")]
fn syscall_callback_redzone() -> isize;
fn exception_callback();
fn interrupt_callback();
fn switch_to_guest_start();
Expand Down Expand Up @@ -2047,7 +2077,24 @@ impl ThreadContext<'_> {

impl litebox::platform::SystemInfoProvider for LinuxUserland {
fn get_syscall_entry_point(&self) -> usize {
syscall_callback as *const () as usize
// When the seccomp/systrap backend is active, syscall instructions are
// trapped via SIGSYS — no binary rewriting needed.
#[cfg(feature = "systrap_backend")]
if self
.seccomp_interception_enabled
.load(std::sync::atomic::Ordering::SeqCst)
{
return 0;
}

Comment on lines +2080 to +2089
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised by this conditional here. Do we need to give a 0 here? If it is not needed, it can just be kept at the non-tweaked default behavior, no?

#[cfg(target_arch = "x86_64")]
{
syscall_callback_redzone as *const () as usize
}
#[cfg(target_arch = "x86")]
{
syscall_callback as *const () as usize
}
}

fn get_vdso_address(&self) -> Option<usize> {
Expand Down Expand Up @@ -2714,7 +2761,12 @@ unsafe fn interrupt_signal_handler(
// FUTURE: handle trampoline code, too. This is somewhat less important
// because it's probably fine for the shim to observe a guest context that
// is inside the trampoline.
if ip == syscall_callback as *const () as usize {
#[cfg(target_arch = "x86")]
let is_at_syscall_callback = ip == syscall_callback as *const () as usize;
#[cfg(target_arch = "x86_64")]
let is_at_syscall_callback = ip == syscall_callback_redzone as *const () as usize
|| ip == syscall_callback as *const () as usize;
if is_at_syscall_callback {
Comment on lines +2764 to +2769
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems somewhat repetitive, and also somewhat surprising. get_syscall_entry_point only uses redzone variant, but this one checks both. Wouldn't it be correct to just do a ip == get_syscall_entry_point()?

// No need to clear `in_guest` or set interrupt; the syscall handler will
// clear `in_guest` and call into the shim.
return;
Expand Down
44 changes: 34 additions & 10 deletions litebox_platform_windows_userland/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,10 @@ struct TlsState {
host_bp: Cell<*mut u128>,
guest_context_top: Cell<*mut litebox_common_linux::PtRegs>,
scratch: Cell<usize>,
/// Syscall call-site restart address from the rewriter trampoline,
/// saved here for future SA_RESTART support. Not stored in pt_regs
/// (which holds RFLAGS in the r11 slot per the kernel ABI).
saved_restart_addr: Cell<usize>,
is_in_guest: Cell<bool>,
interrupt: Cell<bool>,
continue_context:
Expand All @@ -433,6 +437,7 @@ impl TlsState {
host_bp: Cell::new(core::ptr::null_mut()),
guest_context_top: core::ptr::null_mut::<litebox_common_linux::PtRegs>().into(),
scratch: 0.into(),
saved_restart_addr: 0.into(),
is_in_guest: false.into(),
interrupt: false.into(),
continue_context: Box::default(),
Expand Down Expand Up @@ -549,19 +554,37 @@ unsafe extern "C-unwind" fn run_thread_arch(thread_ctx: &mut ThreadContext, tls_
jmp .Ldone

// This entry point is called from the guest when it issues a syscall
// instruction.
// instruction. The rewriter trampoline has already:
// 1. Reserved 128 bytes below RSP to protect the SysV red zone
// 2. Loaded the call-site restart address into R11 (for SA_RESTART)
// 3. Loaded the return address into RCX
//
// At entry, the register context is the guest context with the
// return address in rcx. r11 is an available scratch register (it would
// contain rflags if the syscall instruction had actually been issued).
.globl syscall_callback
syscall_callback:
// All other registers hold guest state.
.globl syscall_callback_redzone
syscall_callback_redzone:
// Save guest R11 (restart address from rewriter trampoline) to
// TEB.ArbitraryUserPointer (gs:[0x28]) before the TLS index lookup
// clobbers R11. This slot is per-thread and the window is very
// narrow: only ~20 instructions of inline asm with no API calls,
// no Rust code, and no DLL activity, so the ntdll loader (which
// also uses this slot for debugger communication) cannot interfere.
mov gs:[0x28], r11
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it okay not to save/restore the original value of gs:[0x28]?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so. I didn't see a better way to solve this problem.

// Get the TLS state from the TLS slot and clear the in-guest flag.
mov r11d, DWORD PTR [rip + {TLS_INDEX}]
mov r11, QWORD PTR gs:[r11 * 8 + TEB_TLS_SLOTS_OFFSET]
mov BYTE PTR [r11 + {IS_IN_GUEST}], 0
// Set rsp to the top of the guest context.
// Recover the restart address from the TEB slot and store it in TLS.
// We use SCRATCH as a temporary since all guest GPRs must be preserved
// and RSP modifications would break the stack pointer recovery below.
push QWORD PTR gs:[0x28]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stack overflow?

pop QWORD PTR [r11 + {SAVED_RESTART_ADDR}]
// Recover the architectural guest stack pointer (undo the 128-byte
// red zone reservation) and store it in SCRATCH. LEA is used instead
// of ADD to avoid clobbering RFLAGS before pushfq.
lea rsp, [rsp + 128]
mov QWORD PTR [r11 + {SCRATCH}], rsp

.Lsyscall_callback_common:
mov rsp, QWORD PTR [r11 + {GUEST_CONTEXT_TOP}]

// TODO: save float and vector registers (xsave or fxsave)
Expand All @@ -581,7 +604,7 @@ syscall_callback:
push r8 // pt_regs->r8
push r9 // pt_regs->r9
push r10 // pt_regs->r10
push [rsp + 88] // pt_regs->r11 = rflags
push [rsp + 88] // pt_regs->r11 = rflags (matching real syscall ABI)
push rbx // pt_regs->bx
push rbp // pt_regs->bp
push r12
Expand Down Expand Up @@ -646,6 +669,7 @@ interrupt_callback:
HOST_BP = const core::mem::offset_of!(TlsState, host_bp),
GUEST_CONTEXT_TOP = const core::mem::offset_of!(TlsState, guest_context_top),
SCRATCH = const core::mem::offset_of!(TlsState, scratch),
SAVED_RESTART_ADDR = const core::mem::offset_of!(TlsState, saved_restart_addr),
IS_IN_GUEST = const core::mem::offset_of!(TlsState, is_in_guest),
);
}
Expand Down Expand Up @@ -1947,7 +1971,7 @@ impl litebox::mm::allocator::MemoryProvider for WindowsUserland {

unsafe extern "C" {
// Defined in asm blocks above
fn syscall_callback() -> isize;
fn syscall_callback_redzone() -> isize;
fn exception_callback() -> isize;
fn interrupt_callback();
fn switch_to_guest_start();
Expand Down Expand Up @@ -2037,7 +2061,7 @@ impl ThreadContext<'_> {

impl litebox::platform::SystemInfoProvider for WindowsUserland {
fn get_syscall_entry_point(&self) -> usize {
syscall_callback as *const () as usize
syscall_callback_redzone as *const () as usize
}

fn get_vdso_address(&self) -> Option<usize> {
Expand Down
Loading
Loading