How to Implement a Custom Platform Provider for LiteBox: A Complete Guide
To implement a custom Platform provider for LiteBox, you must create a zero-sized struct that implements the Provider umbrella trait and its seven mandatory sub-traits (RawMutexProvider, IPInterfaceProvider, TimeProvider, PunchthroughProvider, DebugLogProvider, and RawPointerProvider), then inject it into the ShimBuilder before running the guest.
LiteBox isolates guest programs from the host by delegating every OS-level service—mutexes, time, networking, and syscalls—to a pluggable platform provider. By writing a custom Platform provider for LiteBox, you can port the sandbox to a new operating system, embed it inside a specialized kernel, or run it under a hypervisor. This guide walks through the architecture, required traits, and a minimal working implementation based on the official Microsoft repository.
Architecture of a LiteBox Platform Provider
The Provider Umbrella Trait
At the center of the system is the empty Provider trait defined in litebox/src/platform/mod.rs. It serves as a capability aggregator, requiring implementors to satisfy several smaller sub-traits:
pub trait Provider:
RawMutexProvider
+ IPInterfaceProvider
+ TimeProvider
+ PunchthroughProvider
+ DebugLogProvider
+ RawPointerProvider
{}
Source: litebox/src/platform/mod.rs (lines 38–50)
A concrete platform type never implements methods on Provider itself; instead, it implements the sub-traits listed above.
Required Sub-traits for a Custom Platform Provider
To create a functional custom Platform provider for LiteBox, you must implement the following seven sub-traits. Each maps to a specific host capability:
| Sub-trait | Purpose | Implementation Notes |
|---|---|---|
RawMutexProvider |
Futex-style locking primitive | Wrap an AtomicU32 and call the host’s futex or WaitOnAddress API. Reference: litebox_platform_linux_kernel/src/lib.rs (lines 20–31). |
IPInterfaceProvider |
Raw IPv4 packet I/O | Implement send_ip_packet and receive_ip_packet using host TUN devices or raw sockets. |
TimeProvider |
Monotonic Instant and wall-clock SystemTime |
Use rdtsc on x86, QueryUnbiasedInterruptTimePrecise on Windows, or clock_gettime on POSIX. |
PunchthroughProvider |
Escape-hatch for non-standard host calls (e.g., FS-base manipulation) | Use ImpossiblePunchthroughProvider from litebox/src/platform/trivial_providers.rs if no extra syscalls are needed. |
DebugLogProvider |
Debug output | Write to stderr on POSIX or OutputDebugString on Windows. |
RawPointerProvider |
Type-safe raw pointer wrappers | Re-use UserConstPtr and UserMutPtr from litebox::platform::common_providers::userspace_pointers for user-space hosts. |
Optional traits such as ThreadProvider and PageManagementProvider are required only if your guest creates threads or manages its own memory mappings. See litebox_platform_linux_kernel/src/lib.rs and litebox_platform_windows_userland/src/lib.rs for production implementations of these advanced traits.
Step-by-Step: Building a Custom Platform Provider
The following minimal example creates a provider named MyHost suitable for a POSIX-like environment. It re-uses LiteBox’s trivial implementations for raw pointers and punch-throughs while providing stubbed (but compilable) implementations for networking and time.
Step 1: Define the Host Structure
Create a zero-sized struct. The provider should hold no runtime state unless the underlying host requires it.
// src/my_host.rs
use litebox::platform::{
Provider, RawMutexProvider, IPInterfaceProvider, TimeProvider,
PunchthroughProvider, DebugLogProvider, RawPointerProvider,
};
pub struct MyHost;
impl Provider for MyHost {}
Step 2: Implement RawMutexProvider
Wrap an AtomicU32 to serve as the futex word. The wake_many method should invoke the host’s futex wake primitive (stubbed here).
use core::sync::atomic::AtomicU32;
use litebox::platform::{RawMutex, RawMutexProvider};
pub struct MyRawMutex {
inner: AtomicU32,
}
impl MyRawMutex {
pub const fn new() -> Self {
Self { inner: AtomicU32::new(0) }
}
}
unsafe impl Send for MyRawMutex {}
unsafe impl Sync for MyRawMutex {}
impl RawMutex for MyRawMutex {
const INIT: Self = Self::new();
fn underlying_atomic(&self) -> &AtomicU32 {
&self.inner
}
fn wake_many(&self, _n: usize) -> usize {
// TODO: Replace with host futex wake or WaitOnAddress
0
}
}
impl RawMutexProvider for MyHost {
type RawMutex = MyRawMutex;
}
Step 3: Implement TimeProvider
Define Instant and SystemTime types, then implement the trait using the host’s monotonic and real-time clocks.
use core::time::Duration;
use litebox::platform::{Instant as InstantTrait, SystemTime as SystemTimeTrait, TimeProvider};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct MyInstant(u64); // nanoseconds
impl InstantTrait for MyInstant {
fn checked_duration_since(&self, earlier: &Self) -> Option<Duration> {
self.0.checked_sub(earlier.0).map(|ns| Duration::from_nanos(ns))
}
fn checked_add(&self, dur: Duration) -> Option<Self> {
self.0.checked_add(dur.as_nanos() as u64).map(MyInstant)
}
}
pub struct MySystemTime {
filetime: u64, // 100-ns intervals
}
impl SystemTimeTrait for MySystemTime {
const UNIX_EPOCH: Self = MySystemTime { filetime: 0 };
fn duration_since(&self, earlier: &Self) -> Result<Duration, Duration> {
if self.filetime >= earlier.filetime {
Ok(Duration::from_nanos((self.filetime - earlier.filetime) * 100))
} else {
Err(Duration::from_nanos((earlier.filetime - self.filetime) * 100))
}
}
}
impl TimeProvider for MyHost {
type Instant = MyInstant;
type SystemTime = MySystemTime;
fn now(&self) -> Self::Instant {
// Example using libc::clock_gettime
unsafe {
let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts);
MyInstant(ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64)
}
}
fn current_time(&self) -> Self::SystemTime {
unsafe {
let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
libc::clock_gettime(libc::CLOCK_REALTIME, &mut ts);
MySystemTime {
filetime: (ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64) / 100
}
}
}
}
Step 4: Implement IPInterfaceProvider
Stub out packet I/O; replace with TUN device logic or raw sockets for real networking.
use litebox::platform::{IPInterfaceProvider, SendError, ReceiveError};
impl IPInterfaceProvider for MyHost {
fn send_ip_packet(&self, _packet: &[u8]) -> Result<(), SendError> {
// TODO: Write to host TUN device
Ok(())
}
fn receive_ip_packet(&self, _packet: &mut [u8]) -> Result<usize, ReceiveError> {
// TODO: Read from host TUN device
Err(ReceiveError::WouldBlock)
}
}
Step 5: Implement PunchthroughProvider
Use the ImpossiblePunchthroughProvider from trivial_providers.rs if you do not need special host escapes like FS-base manipulation.
use litebox::platform::trivial_providers::ImpossiblePunchthroughProvider;
use litebox::platform::{PunchthroughProvider, PunchthroughToken};
impl PunchthroughProvider for MyHost {
type PunchthroughToken<'a> = <ImpossiblePunchthroughProvider as PunchthroughProvider>::PunchthroughToken<'a>;
fn get_punchthrough_token_for<'a>(
&self,
_: <Self::PunchthroughToken<'a> as PunchthroughToken>::Punchthrough,
) -> Option<Self::PunchthroughToken<'a>> {
None
}
}
Step 6: Implement DebugLogProvider and RawPointerProvider
Forward debug logs to stderr and reuse LiteBox’s transparent user-space pointers.
use std::io::Write;
use litebox::platform::{DebugLogProvider, RawPointerProvider};
use litebox::platform::common_providers::userspace_pointers::{UserConstPtr, UserMutPtr};
use zerocopy::{FromBytes, IntoBytes};
impl DebugLogProvider for MyHost {
fn debug_log_print(&self, msg: &str) {
let _ = std::io::stderr().write_all(msg.as_bytes());
}
}
impl RawPointerProvider for MyHost {
type RawConstPointer<T: FromBytes> = UserConstPtr<T>;
type RawMutPointer<T: FromBytes + IntoBytes> = UserMutPtr<T>;
}
Wiring Your Custom Provider into a LiteBox Guest
Once your provider is implemented, inject it into the shim using ShimBuilder before entering the guest. The following pattern mirrors the initialization code found in litebox_platform_linux_kernel::run_thread and litebox_platform_windows_userland::run_thread.
use litebox::shim::{EnterShim, ShimBuilder};
use litebox_common_linux::PtRegs;
// 1. Instantiate the provider (typically 'static for the process lifetime)
static HOST: MyHost = MyHost;
// 2. Build the shim with your custom platform
let shim = ShimBuilder::new()
.with_platform(&HOST) // Inject the custom Platform provider
.build()?; // Returns an EnterShim instance
// 3. Prepare initial guest context (e.g., from ELF loader)
let mut ctx = PtRegs::default();
// ... populate registers ...
// 4. Enter the guest
unsafe { shim.enter(&mut ctx) };
Key Source Files to Reference
When building a custom Platform provider for LiteBox, study these reference implementations in the microsoft/litebox repository:
litebox/src/platform/mod.rs– Defines theProviderumbrella trait and all sub-traits includingRawMutexProvider,TimeProvider, andIPInterfaceProvider.litebox/src/platform/trivial_providers.rs– Supplies reusable components likeImpossiblePunchthroughProvider,TransparentConstPtr, andTransparentMutPtrfor user-space hosts.litebox_platform_linux_kernel/src/lib.rs– Production kernel-mode provider demonstrating futex-basedRawMutex, thread creation viaclone, and FS-base punch-through handling.litebox_platform_windows_userland/src/lib.rs– Windows user-land provider showingWaitOnAddressmutexes,NtContinue-based guest switching, and TLS management.litebox_common_linux/src/lib.rs– Defines thePunchthroughSyscallenum used when implementingPunchthroughProviderfor Linux hosts.
Production Checklist for Custom Platform Providers
Before deploying a custom Platform provider for LiteBox, verify the following:
- Implement all mandatory sub-traits –
RawMutexProvider,IPInterfaceProvider,TimeProvider,PunchthroughProvider,DebugLogProvider, andRawPointerProviderare required for every provider. - Add thread support – Implement
ThreadProviderif your guest creates threads; reference the Linux kernel provider’sclonewrapper or the Windows provider’sCreateThreadusage. - Handle memory management – Implement
PageManagementProviderusing host syscalls likemmaporVirtualAllocif the guest manages its own address space. - Configure punch-throughs – Use
ImpossiblePunchthroughProviderfor pure sandboxes, or implement real FS-base/thread-local storage punch-throughs by handlingPunchthroughSyscallvariants. - Validate with integration tests – Run the
dev_testsandlitebox_syscall_rewritertest suites against your provider to ensure syscall compatibility. - Document host requirements – Add a
README.mdexplaining any host-specific setup, such as TUN device permissions forIPInterfaceProvideror required kernel capabilities.
Summary
Implementing a custom Platform provider for LiteBox involves creating a zero-sized struct that implements seven core sub-traits defined in litebox/src/platform/mod.rs. The provider acts as a bridge between the guest sandbox and the host OS, handling synchronization primitives, time, networking, and debug logging. For rapid prototyping, reuse the ImpossiblePunchthroughProvider and transparent pointer types from litebox/src/platform/trivial_providers.rs. For production deployments, study the Linux kernel and Windows user-land reference implementations to handle threads, memory management, and host-specific punch-throughs like FS-base manipulation.
Frequently Asked Questions
What is the minimum code required to create a custom Platform provider for LiteBox?
The absolute minimum requires a struct implementing the Provider trait (which is empty) and the seven mandatory sub-traits. You can satisfy PunchthroughProvider with ImpossiblePunchthroughProvider and RawPointerProvider with UserConstPtr/UserMutPtr from litebox::platform::common_providers::userspace_pointers. At a minimum, you must provide working implementations for RawMutexProvider (using an AtomicU32), TimeProvider (using host clocks), and DebugLogProvider (writing to stderr).
How do I handle thread creation in a custom Platform provider?
If your guest binary spawns threads, your custom Platform provider must implement the ThreadProvider trait. Refer to litebox_platform_linux_kernel/src/lib.rs for a kernel-mode example that wraps the clone syscall, or litebox_platform_windows_userland/src/lib.rs for a user-land example using CreateThread and NtContinue for context switching. You must also ensure your RawMutexProvider uses a host primitive that supports cross-thread synchronization, such as Linux futexes or Windows WaitOnAddress.
Can I implement a custom Platform provider for LiteBox without networking support?
Yes. If your sandboxed guest does not require network access, you can provide a stub implementation of IPInterfaceProvider that returns Err(ReceiveError::WouldBlock) from receive_ip_packet and silently drops packets in send_ip_packet. This satisfies the trait contract while disabling networking. However, if your guest expects a functional TCP/IP stack, you must implement real packet forwarding to a host TUN device or raw socket as shown in the Linux kernel provider.
Where does LiteBox call the Platform provider during guest execution?
LiteBox invokes the Platform provider through the shim layer. When you build the shim using ShimBuilder::with_platform, the provider is stored inside the shim context. During guest execution, whenever the guest triggers a syscall or needs a platform service (e.g., locking a mutex, checking the time, sending a network packet), LiteBox dispatches to the corresponding method on your provider. For example, FS-base manipulation on Linux flows through PunchthroughProvider::get_punchthrough_token_for with the GetFsBase variant defined in litebox_common_linux::PunchthroughSyscall.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →