diff --git a/Cargo.lock b/Cargo.lock index e591800b7..22303bcc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1208,7 +1208,6 @@ dependencies = [ "crossbeam-channel", "crossbeam-queue", "env_logger", - "flatbuffers", "gdbstub", "gdbstub_arch", "goblin", diff --git a/src/hyperlight_common/src/flatbuffer_wrappers/function_call.rs b/src/hyperlight_common/src/flatbuffer_wrappers/function_call.rs index 987fb369a..c736a32f2 100644 --- a/src/hyperlight_common/src/flatbuffer_wrappers/function_call.rs +++ b/src/hyperlight_common/src/flatbuffer_wrappers/function_call.rs @@ -18,7 +18,7 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; use anyhow::{bail, Error, Result}; -use flatbuffers::{size_prefixed_root, WIPOffset}; +use flatbuffers::{size_prefixed_root, FlatBufferBuilder, WIPOffset}; #[cfg(feature = "tracing")] use tracing::{instrument, Span}; @@ -41,7 +41,6 @@ pub enum FunctionCallType { } /// `Functioncall` represents a call to a function in the guest or host. -#[derive(Clone)] pub struct FunctionCall { /// The function name pub function_name: String, @@ -72,81 +71,19 @@ impl FunctionCall { pub fn function_call_type(&self) -> FunctionCallType { self.function_call_type.clone() } -} - -#[cfg_attr(feature = "tracing", instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace"))] -pub fn validate_guest_function_call_buffer(function_call_buffer: &[u8]) -> Result<()> { - let guest_function_call_fb = size_prefixed_root::(function_call_buffer) - .map_err(|e| anyhow::anyhow!("Error reading function call buffer: {:?}", e))?; - match guest_function_call_fb.function_call_type() { - FbFunctionCallType::guest => Ok(()), - other => { - bail!("Invalid function call type: {:?}", other); - } - } -} -#[cfg_attr(feature = "tracing", instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace"))] -pub fn validate_host_function_call_buffer(function_call_buffer: &[u8]) -> Result<()> { - let host_function_call_fb = size_prefixed_root::(function_call_buffer) - .map_err(|e| anyhow::anyhow!("Error reading function call buffer: {:?}", e))?; - match host_function_call_fb.function_call_type() { - FbFunctionCallType::host => Ok(()), - other => { - bail!("Invalid function call type: {:?}", other); - } - } -} + /// Encodes self into the given builder and returns the encoded data. + pub fn encode<'a>(&self, builder: &'a mut FlatBufferBuilder) -> &'a [u8] { + let function_name = builder.create_string(&self.function_name); -impl TryFrom<&[u8]> for FunctionCall { - type Error = Error; - #[cfg_attr(feature = "tracing", instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace"))] - fn try_from(value: &[u8]) -> Result { - let function_call_fb = size_prefixed_root::(value) - .map_err(|e| anyhow::anyhow!("Error reading function call buffer: {:?}", e))?; - let function_name = function_call_fb.function_name(); - let function_call_type = match function_call_fb.function_call_type() { - FbFunctionCallType::guest => FunctionCallType::Guest, - FbFunctionCallType::host => FunctionCallType::Host, - other => { - bail!("Invalid function call type: {:?}", other); - } - }; - let expected_return_type = function_call_fb.expected_return_type().try_into()?; - - let parameters = function_call_fb - .parameters() - .map(|v| { - v.iter() - .map(|p| p.try_into()) - .collect::>>() - }) - .transpose()?; - - Ok(Self { - function_name: function_name.to_string(), - parameters, - function_call_type, - expected_return_type, - }) - } -} - -impl TryFrom for Vec { - type Error = Error; - #[cfg_attr(feature = "tracing", instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace"))] - fn try_from(value: FunctionCall) -> Result> { - let mut builder = flatbuffers::FlatBufferBuilder::new(); - let function_name = builder.create_string(&value.function_name); - - let function_call_type = match value.function_call_type { + let function_call_type = match self.function_call_type { FunctionCallType::Guest => FbFunctionCallType::guest, FunctionCallType::Host => FbFunctionCallType::host, }; - let expected_return_type = value.expected_return_type.into(); + let expected_return_type = self.expected_return_type.into(); - let parameters = match &value.parameters { + let parameters = match &self.parameters { Some(p) => { let num_items = p.len(); let mut parameters: Vec> = Vec::with_capacity(num_items); @@ -154,9 +91,9 @@ impl TryFrom for Vec { for param in p { match param { ParameterValue::Int(i) => { - let hlint = hlint::create(&mut builder, &hlintArgs { value: *i }); + let hlint = hlint::create(builder, &hlintArgs { value: *i }); let parameter = Parameter::create( - &mut builder, + builder, &ParameterArgs { value_type: FbParameterValue::hlint, value: Some(hlint.as_union_value()), @@ -165,9 +102,9 @@ impl TryFrom for Vec { parameters.push(parameter); } ParameterValue::UInt(ui) => { - let hluint = hluint::create(&mut builder, &hluintArgs { value: *ui }); + let hluint = hluint::create(builder, &hluintArgs { value: *ui }); let parameter = Parameter::create( - &mut builder, + builder, &ParameterArgs { value_type: FbParameterValue::hluint, value: Some(hluint.as_union_value()), @@ -176,9 +113,9 @@ impl TryFrom for Vec { parameters.push(parameter); } ParameterValue::Long(l) => { - let hllong = hllong::create(&mut builder, &hllongArgs { value: *l }); + let hllong = hllong::create(builder, &hllongArgs { value: *l }); let parameter = Parameter::create( - &mut builder, + builder, &ParameterArgs { value_type: FbParameterValue::hllong, value: Some(hllong.as_union_value()), @@ -187,10 +124,9 @@ impl TryFrom for Vec { parameters.push(parameter); } ParameterValue::ULong(ul) => { - let hlulong = - hlulong::create(&mut builder, &hlulongArgs { value: *ul }); + let hlulong = hlulong::create(builder, &hlulongArgs { value: *ul }); let parameter = Parameter::create( - &mut builder, + builder, &ParameterArgs { value_type: FbParameterValue::hlulong, value: Some(hlulong.as_union_value()), @@ -199,9 +135,9 @@ impl TryFrom for Vec { parameters.push(parameter); } ParameterValue::Float(f) => { - let hlfloat = hlfloat::create(&mut builder, &hlfloatArgs { value: *f }); + let hlfloat = hlfloat::create(builder, &hlfloatArgs { value: *f }); let parameter = Parameter::create( - &mut builder, + builder, &ParameterArgs { value_type: FbParameterValue::hlfloat, value: Some(hlfloat.as_union_value()), @@ -210,10 +146,9 @@ impl TryFrom for Vec { parameters.push(parameter); } ParameterValue::Double(d) => { - let hldouble = - hldouble::create(&mut builder, &hldoubleArgs { value: *d }); + let hldouble = hldouble::create(builder, &hldoubleArgs { value: *d }); let parameter = Parameter::create( - &mut builder, + builder, &ParameterArgs { value_type: FbParameterValue::hldouble, value: Some(hldouble.as_union_value()), @@ -223,9 +158,9 @@ impl TryFrom for Vec { } ParameterValue::Bool(b) => { let hlbool: WIPOffset> = - hlbool::create(&mut builder, &hlboolArgs { value: *b }); + hlbool::create(builder, &hlboolArgs { value: *b }); let parameter = Parameter::create( - &mut builder, + builder, &ParameterArgs { value_type: FbParameterValue::hlbool, value: Some(hlbool.as_union_value()), @@ -236,10 +171,10 @@ impl TryFrom for Vec { ParameterValue::String(s) => { let hlstring = { let val = builder.create_string(s.as_str()); - hlstring::create(&mut builder, &hlstringArgs { value: Some(val) }) + hlstring::create(builder, &hlstringArgs { value: Some(val) }) }; let parameter = Parameter::create( - &mut builder, + builder, &ParameterArgs { value_type: FbParameterValue::hlstring, value: Some(hlstring.as_union_value()), @@ -251,13 +186,13 @@ impl TryFrom for Vec { let vec_bytes = builder.create_vector(v); let hlvecbytes = hlvecbytes::create( - &mut builder, + builder, &hlvecbytesArgs { value: Some(vec_bytes), }, ); let parameter = Parameter::create( - &mut builder, + builder, &ParameterArgs { value_type: FbParameterValue::hlvecbytes, value: Some(hlvecbytes.as_union_value()), @@ -279,7 +214,7 @@ impl TryFrom for Vec { }; let function_call = FbFunctionCall::create( - &mut builder, + builder, &FbFunctionCallArgs { function_name: Some(function_name), parameters, @@ -288,9 +223,40 @@ impl TryFrom for Vec { }, ); builder.finish_size_prefixed(function_call, None); - let res = builder.finished_data().to_vec(); + builder.finished_data() + } +} + +impl TryFrom<&[u8]> for FunctionCall { + type Error = Error; + #[cfg_attr(feature = "tracing", instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace"))] + fn try_from(value: &[u8]) -> Result { + let function_call_fb = size_prefixed_root::(value) + .map_err(|e| anyhow::anyhow!("Error reading function call buffer: {:?}", e))?; + let function_name = function_call_fb.function_name(); + let function_call_type = match function_call_fb.function_call_type() { + FbFunctionCallType::guest => FunctionCallType::Guest, + FbFunctionCallType::host => FunctionCallType::Host, + other => { + bail!("Invalid function call type: {:?}", other); + } + }; + let expected_return_type = function_call_fb.expected_return_type().try_into()?; + let parameters = function_call_fb + .parameters() + .map(|v| { + v.iter() + .map(|p| p.try_into()) + .collect::>>() + }) + .transpose()?; - Ok(res) + Ok(Self { + function_name: function_name.to_string(), + parameters, + function_call_type, + expected_return_type, + }) } } @@ -303,7 +269,8 @@ mod tests { #[test] fn read_from_flatbuffer() -> Result<()> { - let test_data: Vec = FunctionCall::new( + let mut builder = FlatBufferBuilder::new(); + let test_data = FunctionCall::new( "PrintTwelveArgs".to_string(), Some(vec![ ParameterValue::String("1".to_string()), @@ -322,10 +289,9 @@ mod tests { FunctionCallType::Guest, ReturnType::Int, ) - .try_into() - .unwrap(); + .encode(&mut builder); - let function_call = FunctionCall::try_from(test_data.as_slice())?; + let function_call = FunctionCall::try_from(test_data)?; assert_eq!(function_call.function_name, "PrintTwelveArgs"); assert!(function_call.parameters.is_some()); let parameters = function_call.parameters.unwrap(); diff --git a/src/hyperlight_common/src/lib.rs b/src/hyperlight_common/src/lib.rs index f05baa9f9..3d4e6c436 100644 --- a/src/hyperlight_common/src/lib.rs +++ b/src/hyperlight_common/src/lib.rs @@ -36,3 +36,6 @@ pub mod flatbuffer_wrappers; mod flatbuffers; /// cbindgen:ignore pub mod mem; + +extern crate flatbuffers as fb; +pub use fb::FlatBufferBuilder; diff --git a/src/hyperlight_guest/src/guest_function_call.rs b/src/hyperlight_guest/src/guest_function_call.rs index 60d5548db..2e4c5bcbd 100644 --- a/src/hyperlight_guest/src/guest_function_call.rs +++ b/src/hyperlight_guest/src/guest_function_call.rs @@ -93,7 +93,7 @@ fn internal_dispatch_function() -> Result<()> { set_error(e.kind.clone(), e.message.as_str()); })?; - push_shared_output_data(result_vec) + push_shared_output_data(&result_vec) } // This is implemented as a separate function to make sure that epilogue in the internal_dispatch_function is called before the halt() diff --git a/src/hyperlight_guest/src/host_function_call.rs b/src/hyperlight_guest/src/host_function_call.rs index c5a496c8c..d0e3b9f1b 100644 --- a/src/hyperlight_guest/src/host_function_call.rs +++ b/src/hyperlight_guest/src/host_function_call.rs @@ -26,6 +26,7 @@ use hyperlight_common::flatbuffer_wrappers::function_types::{ use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; use hyperlight_common::flatbuffer_wrappers::util::get_flatbuffer_result; use hyperlight_common::mem::RunMode; +use hyperlight_common::FlatBufferBuilder; use crate::error::{HyperlightGuestError, Result}; use crate::host_error::check_for_host_error; @@ -73,11 +74,8 @@ pub fn call_host_function( validate_host_function_call(&host_function_call)?; - let host_function_call_buffer: Vec = host_function_call - .try_into() - .expect("Unable to serialize host function call"); - - push_shared_output_data(host_function_call_buffer)?; + let mut builder = FlatBufferBuilder::new(); + push_shared_output_data(host_function_call.encode(&mut builder))?; outb(OutBAction::CallFunction as u16, 0); diff --git a/src/hyperlight_guest/src/logging.rs b/src/hyperlight_guest/src/logging.rs index 45cee65dd..a2fd84efc 100644 --- a/src/hyperlight_guest/src/logging.rs +++ b/src/hyperlight_guest/src/logging.rs @@ -44,7 +44,7 @@ fn write_log_data( .try_into() .expect("Failed to convert GuestLogData to bytes"); - push_shared_output_data(bytes).expect("Unable to push log data to shared output data"); + push_shared_output_data(&bytes).expect("Unable to push log data to shared output data"); } pub fn log_message( diff --git a/src/hyperlight_guest/src/shared_output_data.rs b/src/hyperlight_guest/src/shared_output_data.rs index 520a369f3..2d82230e2 100644 --- a/src/hyperlight_guest/src/shared_output_data.rs +++ b/src/hyperlight_guest/src/shared_output_data.rs @@ -16,7 +16,6 @@ limitations under the License. use alloc::format; use alloc::string::ToString; -use alloc::vec::Vec; use core::slice::from_raw_parts_mut; use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; @@ -24,7 +23,7 @@ use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; use crate::error::{HyperlightGuestError, Result}; use crate::P_PEB; -pub fn push_shared_output_data(data: Vec) -> Result<()> { +pub fn push_shared_output_data(data: &[u8]) -> Result<()> { let peb_ptr = unsafe { P_PEB.unwrap() }; let shared_buffer_size = unsafe { (*peb_ptr).outputdata.outputDataSize as usize }; let odb = unsafe { diff --git a/src/hyperlight_host/Cargo.toml b/src/hyperlight_host/Cargo.toml index 5fdb74cc0..02fa9def7 100644 --- a/src/hyperlight_host/Cargo.toml +++ b/src/hyperlight_host/Cargo.toml @@ -26,7 +26,6 @@ rand = { version = "0.9" } cfg-if = { version = "1.0.0" } libc = { version = "0.2.172" } paste = "1.0" -flatbuffers = "25.2.10" page_size = "0.6.0" termcolor = "1.2.0" bitflags = "2.9.0" diff --git a/src/hyperlight_host/benches/benchmarks.rs b/src/hyperlight_host/benches/benchmarks.rs index db71d8a95..f3d6497e0 100644 --- a/src/hyperlight_host/benches/benchmarks.rs +++ b/src/hyperlight_host/benches/benchmarks.rs @@ -15,11 +15,12 @@ limitations under the License. */ use std::sync::{Arc, Mutex}; +use std::time::Duration; use criterion::{criterion_group, criterion_main, Criterion}; use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnType}; use hyperlight_host::func::HostFunction2; -use hyperlight_host::sandbox::{MultiUseSandbox, UninitializedSandbox}; +use hyperlight_host::sandbox::{MultiUseSandbox, SandboxConfiguration, UninitializedSandbox}; use hyperlight_host::sandbox_state::sandbox::EvolvableSandbox; use hyperlight_host::sandbox_state::transition::Noop; use hyperlight_host::GuestBinary; @@ -69,6 +70,40 @@ fn guest_call_benchmark(c: &mut Criterion) { }); }); + // This benchmark includes time to first clone a vector and string, so it is not a "pure' benchmark of the guest call, but it's still useful + group.bench_function("guest_call_with_large_parameters", |b| { + const SIZE: usize = 50 * 1024 * 1024; // 50 MB + let large_vec = vec![0u8; SIZE]; + let large_string = unsafe { String::from_utf8_unchecked(large_vec.clone()) }; // Safety: indeed above vec is valid utf8 + + let mut config = SandboxConfiguration::default(); + config.set_input_data_size(2 * SIZE + (1024 * 1024)); // 2 * SIZE + 1 MB, to allow 1MB for the rest of the serialized function call + config.set_heap_size(SIZE as u64 * 15); + config.set_max_execution_time(Duration::from_secs(3)); + + let sandbox = UninitializedSandbox::new( + GuestBinary::FilePath(simple_guest_as_string().unwrap()), + Some(config), + None, + None, + ) + .unwrap(); + let mut sandbox = sandbox.evolve(Noop::default()).unwrap(); + + b.iter(|| { + sandbox + .call_guest_function_by_name( + "LargeParameters", + ReturnType::Void, + Some(vec![ + ParameterValue::VecBytes(large_vec.clone()), + ParameterValue::String(large_string.clone()), + ]), + ) + .unwrap() + }); + }); + // Benchmarks a guest function call calling into the host. // The benchmark does **not** include the time to reset the sandbox memory after the call. group.bench_function("guest_call_with_call_to_host_function", |b| { diff --git a/src/hyperlight_host/src/error.rs b/src/hyperlight_host/src/error.rs index b2ea080ce..d4b7d3f9c 100644 --- a/src/hyperlight_host/src/error.rs +++ b/src/hyperlight_host/src/error.rs @@ -32,7 +32,6 @@ use std::time::SystemTimeError; #[cfg(target_os = "windows")] use crossbeam_channel::{RecvError, SendError}; -use flatbuffers::InvalidFlatbuffer; use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnValue}; use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; use serde::{Deserialize, Serialize}; @@ -167,10 +166,6 @@ pub enum HyperlightError { #[error("Failed To Convert Size to usize")] IntConversionFailure(#[from] TryFromIntError), - /// The flatbuffer is invalid - #[error("The flatbuffer is invalid")] - InvalidFlatBuffer(#[from] InvalidFlatbuffer), - /// Conversion of str to Json failed #[error("Conversion of str data to json failed")] JsonConversionFailure(#[from] serde_json::Error), diff --git a/src/hyperlight_host/src/func/guest_dispatch.rs b/src/hyperlight_host/src/func/guest_dispatch.rs index 4462a8a66..14da0a210 100644 --- a/src/hyperlight_host/src/func/guest_dispatch.rs +++ b/src/hyperlight_host/src/func/guest_dispatch.rs @@ -48,13 +48,9 @@ pub(crate) fn call_function_on_guest( return_type, ); - let buffer: Vec = fc - .try_into() - .map_err(|_| HyperlightError::Error("Failed to serialize FunctionCall".to_string()))?; - { let mem_mgr = wrapper_getter.get_mgr_wrapper_mut(); - mem_mgr.as_mut().write_guest_function_call(&buffer)?; + mem_mgr.as_mut().write_guest_function_call(&fc)?; } let mut hv_handler = wrapper_getter.get_hv_handler().clone(); diff --git a/src/hyperlight_host/src/mem/mgr.rs b/src/hyperlight_host/src/mem/mgr.rs index 2c4f38e83..08f373735 100644 --- a/src/hyperlight_host/src/mem/mgr.rs +++ b/src/hyperlight_host/src/mem/mgr.rs @@ -19,13 +19,12 @@ use std::cmp::Ordering; use std::str::from_utf8; use std::sync::{Arc, Mutex}; -use hyperlight_common::flatbuffer_wrappers::function_call::{ - validate_guest_function_call_buffer, FunctionCall, -}; +use hyperlight_common::flatbuffer_wrappers::function_call::{FunctionCall, FunctionCallType}; use hyperlight_common::flatbuffer_wrappers::function_types::ReturnValue; use hyperlight_common::flatbuffer_wrappers::guest_error::{ErrorCode, GuestError}; use hyperlight_common::flatbuffer_wrappers::guest_log_data::GuestLogData; use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails; +use hyperlight_common::FlatBufferBuilder; use serde_json::from_str; use tracing::{instrument, Span}; @@ -617,18 +616,20 @@ impl SandboxMemoryManager { /// Writes a guest function call to memory #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn write_guest_function_call(&mut self, buffer: &[u8]) -> Result<()> { - validate_guest_function_call_buffer(buffer).map_err(|e| { - new_error!( - "Guest function call buffer validation failed: {}", - e.to_string() - ) - })?; - + pub(crate) fn write_guest_function_call(&mut self, function_call: &FunctionCall) -> Result<()> { + match function_call.function_call_type() { + FunctionCallType::Host => { + log_then_return!( + "Tried to serialize a host function call as a guest function call" + ); + } + FunctionCallType::Guest => {} + }; + let mut builder = FlatBufferBuilder::new(); self.shared_mem.push_buffer( self.layout.input_data_buffer_offset, self.layout.sandbox_memory_config.get_input_data_size(), - buffer, + function_call.encode(&mut builder), ) } diff --git a/src/tests/rust_guests/simpleguest/src/main.rs b/src/tests/rust_guests/simpleguest/src/main.rs index 45dd558c7..6382cacd7 100644 --- a/src/tests/rust_guests/simpleguest/src/main.rs +++ b/src/tests/rust_guests/simpleguest/src/main.rs @@ -714,6 +714,22 @@ fn add(function_call: &FunctionCall) -> Result> { } } +// Does nothing, but used for testing large parameters +fn large_parameters(function_call: &FunctionCall) -> Result> { + if let (ParameterValue::VecBytes(v), ParameterValue::String(s)) = ( + function_call.parameters.clone().unwrap()[0].clone(), + function_call.parameters.clone().unwrap()[1].clone(), + ) { + black_box((v, s)); + Ok(get_flatbuffer_result(())) + } else { + Err(HyperlightGuestError::new( + ErrorCode::GuestFunctionParameterTypeMismatch, + "Invalid parameters passed to large_parameters".to_string(), + )) + } +} + #[no_mangle] pub extern "C" fn hyperlight_main() { let set_static_def = GuestFunctionDefinition::new( @@ -1108,6 +1124,14 @@ pub extern "C" fn hyperlight_main() { trigger_exception as usize, ); register_function(trigger_exception_def); + + let large_parameters_def = GuestFunctionDefinition::new( + "LargeParameters".to_string(), + Vec::from(&[ParameterType::VecBytes, ParameterType::String]), + ReturnType::Void, + large_parameters as usize, + ); + register_function(large_parameters_def); } #[no_mangle]