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:

Production Checklist for Custom Platform Providers

Before deploying a custom Platform provider for LiteBox, verify the following:

  • Implement all mandatory sub-traitsRawMutexProvider, IPInterfaceProvider, TimeProvider, PunchthroughProvider, DebugLogProvider, and RawPointerProvider are required for every provider.
  • Add thread support – Implement ThreadProvider if your guest creates threads; reference the Linux kernel provider’s clone wrapper or the Windows provider’s CreateThread usage.
  • Handle memory management – Implement PageManagementProvider using host syscalls like mmap or VirtualAlloc if the guest manages its own address space.
  • Configure punch-throughs – Use ImpossiblePunchthroughProvider for pure sandboxes, or implement real FS-base/thread-local storage punch-throughs by handling PunchthroughSyscall variants.
  • Validate with integration tests – Run the dev_tests and litebox_syscall_rewriter test suites against your provider to ensure syscall compatibility.
  • Document host requirements – Add a README.md explaining any host-specific setup, such as TUN device permissions for IPInterfaceProvider or 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →