Skip to content
Closed
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.

2 changes: 1 addition & 1 deletion dev_tests/src/ratchet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fn ratchet_globals() -> Result<()> {
("litebox/", 8),
("litebox_platform_linux_kernel/", 5),
("litebox_platform_linux_userland/", 5),
("litebox_platform_lvbs/", 21),
("litebox_platform_lvbs/", 22),
("litebox_platform_multiplex/", 1),
("litebox_platform_windows_userland/", 7),
("litebox_runner_linux_userland/", 1),
Expand Down
1 change: 1 addition & 0 deletions litebox/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub mod platform;
pub mod shim;
pub mod sync;
pub mod tls;
pub mod upcall;

// The core [`LiteBox`] object itself, re-exported here publicly, just to keep management of the
// code cleaner.
Expand Down
64 changes: 64 additions & 0 deletions litebox/src/upcall.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//! Upcall for platforms
//!
//! This module defines types and trait to provide upcalls for platforms.
//! A platform may receive some messages or requests from the host, devices,
//! or remote parties that it does not know how to handle. Rather than
//! making the platform be aware of all these messages or requests itself,
//! we implement upcalls to let platforms delegate handling of unknown
//! messages or requests to other layers of LiteBox (i.e., runner or shim).
//! Examples of such messages or requests include HVCI/Heki requests from
//! VTL0 and OP-TEE SMC calls from the normal world.
Comment on lines +9 to +10
Copy link
Member

Choose a reason for hiding this comment

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

Would signals from the outside world be a similar situation (i.e., should they be handled through this mechanism)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unlike the Linux shim case that all signals will be handled by the shim, certain exceptions/interrupts will be handled by the LVBS platform whereas some other ones are handled by the OP-TEE shim.

Copy link
Member

Choose a reason for hiding this comment

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

That's fine right? The platform can just decide not to forward along things to the shim. There is nothing in the design that says that the platform must forward everything over, right? Maybe I am misunderstanding why the signals world and the upcall world are drastically different. Might be easier to chat in-person?

Copy link
Contributor Author

@sangho2 sangho2 Jan 17, 2026

Choose a reason for hiding this comment

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

Yes, I think the current explanation is misleading. It is more about there are something the platform doesn't know how to handle and delegates them to the others if applicable.

//!
//! # Security considerations
//!
//! Unlike other upcalls like hyperupcalls from the hypervisor to a guest VM,
//! this upcall is handled within LiteBox's TCB (i.e., by either runner or shim).
//! Therefore, the security considerations for this upcall are similar to function
//! calls within LiteBox. However, care must be taken to ensure that the upcall's
//! parameters and return values are properly validated and sanitized to prevent
//! potential security vulnerabilities. This is because the parameters might be
Comment on lines +17 to +19
Copy link
Member

Choose a reason for hiding this comment

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

"care must be taken" -> by whom?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the upcall handler. will clarify this.

//! provided by untrusted sources and return values might contain sensitive
//! information. We assume that the upcall providers must implement necessary
//! security checks and validations. The platform would simply pass the parameters
//! and return values between untrusted sources and upcall providers (because it
//! does not have semantics to validate them). We can specify a function for early
//! validation at the platform side if needed but its advantages are not clear at
//! this moment (since there is no costly context switch within LiteBox).
Comment on lines +24 to +26
Copy link
Member

Choose a reason for hiding this comment

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

Nit: this sentence is surprising to see as part of the doc string. Might be better just as a comment, rather than being in the doc string.


use thiserror::Error;

/// An interface for upcalls from the platform to other layers of LiteBox.
pub trait Upcall {
/// The upcall parameter type to be passed from the platform.
type Parameter;

/// The upcall return type to be returned to the platform.
type Return;

/// Initialize the upcall handler. Must be called by the platform exactly once.
/// Per-thread initialization is possible but all threads must share the same
/// upcall handler.
Comment on lines +38 to +40
Copy link
Member

Choose a reason for hiding this comment

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

This comment is confusing. What happens if the platform does not invoke this? (I might have missed something but I didn't see the platform invoke this here). I also don't understand the "per-thread initialization is possible but ..." comment here. Should each thread invoke this? Or should different threads within the platform coordinate to make sure they have not invoked this more than once?

A slightly easier-to-use interface is likely something like "get singleton", so that the platform doesn't need to store/manage this, and the runner side can choose to make it more/less performant as it needs. Alternatively, one can just eliminate initialization and set up the execute to initialize on first use?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Basically, this is a callback registration. I agree that the comment is confusing. I'll fix this.

fn init(
&self,
) -> alloc::boxed::Box<
dyn crate::upcall::Upcall<Parameter = Self::Parameter, Return = Self::Return>,
>;
Comment on lines +41 to +45
Copy link
Member

Choose a reason for hiding this comment

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

You need self to get essentially a Box<dyn itself>? This is a confusing signature. Where do you get teh original Upcall to even invoke this initialization? Why is it returning a boxed version of itself?

Copy link
Contributor Author

@sangho2 sangho2 Jan 17, 2026

Choose a reason for hiding this comment

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

the runner has a function and registers it to the platform through this (Platform::register_upcall).


/// Execute the upcall with the given parameter. Since we do not expect that the
/// platform validates the parameters, the implementation of `execute` must validate
/// parameters to avoid potential security vulnerabilities. Also, it must sanitize
/// the return values before returning them to the platform.
Comment on lines +47 to +50
Copy link
Member

Choose a reason for hiding this comment

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

Minor: might be good to point at See [security considerations](crate::upcall) or such

fn execute(&self, ctx: &mut Self::Parameter) -> Result<Self::Return, UpcallError>;
Copy link
Member

Choose a reason for hiding this comment

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

I'm mildly surprised by seeing the &mut here for the parameter. Due to lifetime elision and such, Self::Return's lifetime cannot depend on that mutable reference anyways. A slightly cleaner design might be to just pass the parameter as a (consumed/owned) value? Alternatively, the Return should have a generic lifetime that gets bound to the lifetime of the parameter (or even more generally, it would get bound to a new lifetime that is itself bounded by the lifetimes of &self and ctx).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Technically, we don't need to struggle with lifetime. let me use values.

}

/// The error type for upcalls
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum UpcallError {
#[error("Upcall failed")]
Failure,
Comment on lines +58 to +59
Copy link
Member

Choose a reason for hiding this comment

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

Failed due to? It becomes impossible in the current design for the upcall to give more useful information to the place below. Would be good to make this generic in some way (can do it generically, or can do a Failure(Box<dyn Error>) might be enough).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let me do that.

#[error("Upcall needs to be retried")]
Retry,
Comment on lines +60 to +61
Copy link
Member

Choose a reason for hiding this comment

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

If the upcall needs to be retried, it needs to mention when it needs to be retried, right? Otherwise, if "retry immediately" is allowed, might as well do the retrying internally. The failure case is that "it cannot be handled right now, please try later", but yeah, something else feels necessary here in the output so that a platform can know when to retry.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Technically, there is no easy way to know when the platform can try it again. Let me think.

#[error("Upcall parameter is invalid")]
InvalidParameter,
}
24 changes: 24 additions & 0 deletions litebox_platform_lvbs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,19 @@ impl<Host: HostInterface> LinuxKernel<Host> {
) {
syscall_entry::init(shim);
}

/// Register the upcall.
pub fn register_upcall(
upcall: &'static (
dyn litebox::upcall::Upcall<
Parameter = litebox_common_linux::PtRegs,
Return = litebox_common_linux::PtRegs,
> + Send
+ Sync
),
Comment on lines +395 to +401
Copy link
Member

Choose a reason for hiding this comment

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

oof, this looks like rustfmt did a horrifyingly bad job here. Consider putting the type into a type alias (type Upcall = ...) to make this be nicer maintained?

) {
UPCALL.call_once(|| upcall);
}
}

impl<Host: HostInterface> RawMutexProvider for LinuxKernel<Host> {
Expand Down Expand Up @@ -755,6 +768,17 @@ impl<Host: HostInterface> StdioProvider for LinuxKernel<Host> {
}
}

// Use static for upcall (we can use kernel TLS if needed) and `PtRegs` for now.
static UPCALL: spin::Once<
&'static (
dyn litebox::upcall::Upcall<
Parameter = litebox_common_linux::PtRegs,
Return = litebox_common_linux::PtRegs,
> + Send
+ Sync
),
> = spin::Once::new();

// NOTE: The below code is a naive workaround to let LVBS code to access the platform.
// Rather than doing this, we should implement LVBS interface/provider for the platform.

Expand Down
7 changes: 6 additions & 1 deletion litebox_platform_lvbs/src/mshv/vsm_optee_smc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ pub(crate) fn optee_smc_dispatch(optee_smc_args_pfn: u64) -> i64 {
with_per_cpu_variables_mut(|per_cpu_variables| {
per_cpu_variables.save_extended_states(HV_VTL_SECURE);
});
// TODO: Implement OP-TEE SMC for TA command invocation here.

// Placeholder for now
let upcall = crate::UPCALL.get().expect("OP-TEE upcall not registered");
let mut ctx = litebox_common_linux::PtRegs::default();
let _ = upcall.execute(&mut ctx);

Choose a reason for hiding this comment

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

Did you intend to ignore the return the value here?

Seems like this should be properly handled/propagated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As wrote in the comment, this is just a placeholder. we should handle the return value.

Copy link
Member

Choose a reason for hiding this comment

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

Minor nit: keeping a TODO:/XXX:/... or such signifier makes it easier to grep in the future quickly

Copy link
Contributor Author

Choose a reason for hiding this comment

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


debug_serial_println!("VSM function call for OP-TEE message");
with_per_cpu_variables_mut(|per_cpu_variables| {
per_cpu_variables.restore_extended_states(HV_VTL_SECURE);
Expand Down
2 changes: 1 addition & 1 deletion litebox_runner_lvbs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ edition = "2024"

[dependencies]
litebox = { version = "0.1.0", path = "../litebox" }
litebox_common_linux = { path = "../litebox_common_linux/", version = "0.1.0" }
litebox_platform_lvbs = { version = "0.1.0", path = "../litebox_platform_lvbs", default-features = false, features = ["interrupt"] }
litebox_platform_multiplex = { version = "0.1.0", path = "../litebox_platform_multiplex", default-features = false, features = ["platform_lvbs"] }
litebox_shim_optee = { path = "../litebox_shim_optee/", version = "0.1.0" }


[target.'cfg(target_arch = "x86_64")'.dependencies]
x86_64 = { version = "0.15.2", default-features = false, features = ["instructions"] }

Expand Down
27 changes: 27 additions & 0 deletions litebox_runner_lvbs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![no_std]

extern crate alloc;

use core::panic::PanicInfo;
use litebox_platform_lvbs::{
arch::{gdt, get_core_id, instrs::hlt_loop, interrupts},
Expand Down Expand Up @@ -91,6 +93,7 @@ pub fn init() -> Option<&'static Platform> {
interrupts::init_idt();
x86_64::instructions::interrupts::enable();
Platform::register_shim(&litebox_shim_optee::OpteeShim);
Platform::register_upcall(&crate::OpteeMsgHandler);

ret
}
Expand All @@ -99,6 +102,30 @@ pub fn run(platform: Option<&'static Platform>) -> ! {
vtl_switch_loop_entry(platform)
}

pub struct OpteeMsgHandler;
impl litebox::upcall::Upcall for OpteeMsgHandler {
type Parameter = litebox_common_linux::PtRegs;
type Return = litebox_common_linux::PtRegs;
Comment on lines +107 to +108

Choose a reason for hiding this comment

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

Curious to know why you need the registers here?

Do you intend to run a thread during upcall.execute?

Copy link
Contributor Author

@sangho2 sangho2 Jan 7, 2026

Choose a reason for hiding this comment

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

This is just for defining parameter and return types. We don't specify any particular data types here yet. The reason I use PtRegs here is because it is the most common data type used in many places in the LiteBox code base.


fn init(
&self,
) -> alloc::boxed::Box<
dyn litebox::upcall::Upcall<
Parameter = litebox_common_linux::PtRegs,
Return = litebox_common_linux::PtRegs,
>,
> {
alloc::boxed::Box::new(OpteeMsgHandler {})
}

fn execute(
&self,
_ctx: &mut Self::Parameter,
) -> Result<Self::Return, litebox::upcall::UpcallError> {
todo!("invoke OP-TEE message handlers")
}
}

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
serial_println!("{}", info);
Expand Down