Skip to content

Commit e99716a

Browse files
authored
Refactor HostFuncsWrapper (#480)
* Refactor HostFuncsWrapper so that the helper functions are private methods Signed-off-by: Jorge Prendes <[email protected]> * Use a struct instead of a tuple for HostFuncsWrapper Signed-off-by: Jorge Prendes <[email protected]> * Rename HostFuncsWrapper -> FunctionRegistry Signed-off-by: Jorge Prendes <[email protected]> --------- Signed-off-by: Jorge Prendes <[email protected]>
1 parent 277ca60 commit e99716a

File tree

6 files changed

+110
-107
lines changed

6 files changed

+110
-107
lines changed

src/hyperlight_host/src/sandbox/host_funcs.rs

Lines changed: 95 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,25 @@ use crate::{new_error, Result};
2828

2929
#[derive(Default, Clone)]
3030
/// A Wrapper around details of functions exposed by the Host
31-
pub struct HostFuncsWrapper {
32-
functions_map: HashMap<String, (HyperlightFunction, Option<Vec<ExtraAllowedSyscall>>)>,
31+
pub struct FunctionRegistry {
32+
functions_map: HashMap<String, FunctionEntry>,
3333
}
3434

35-
impl HostFuncsWrapper {
35+
#[derive(Clone)]
36+
pub struct FunctionEntry {
37+
pub function: HyperlightFunction,
38+
pub extra_allowed_syscalls: Option<Vec<ExtraAllowedSyscall>>,
39+
}
40+
41+
impl FunctionRegistry {
3642
/// Register a host function with the sandbox.
3743
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
3844
pub(crate) fn register_host_function(
3945
&mut self,
4046
name: String,
4147
func: HyperlightFunction,
4248
) -> Result<()> {
43-
register_host_function_helper(self, name, func, None)
49+
self.register_host_function_helper(name, func, None)
4450
}
4551

4652
/// Register a host function with the sandbox, with a list of extra syscalls
@@ -53,7 +59,7 @@ impl HostFuncsWrapper {
5359
func: HyperlightFunction,
5460
extra_allowed_syscalls: Vec<ExtraAllowedSyscall>,
5561
) -> Result<()> {
56-
register_host_function_helper(self, name, func, Some(extra_allowed_syscalls))
62+
self.register_host_function_helper(name, func, Some(extra_allowed_syscalls))
5763
}
5864

5965
/// Assuming a host function called `"HostPrint"` exists, and takes a
@@ -63,11 +69,7 @@ impl HostFuncsWrapper {
6369
/// and `Err` otherwise.
6470
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
6571
pub(super) fn host_print(&mut self, msg: String) -> Result<i32> {
66-
let res = call_host_func_impl(
67-
&self.functions_map,
68-
"HostPrint",
69-
vec![ParameterValue::String(msg)],
70-
)?;
72+
let res = self.call_host_func_impl("HostPrint", vec![ParameterValue::String(msg)])?;
7173
res.try_into()
7274
.map_err(|_| HostFunctionNotFound("HostPrint".to_string()))
7375
}
@@ -84,97 +86,45 @@ impl HostFuncsWrapper {
8486
name: &str,
8587
args: Vec<ParameterValue>,
8688
) -> Result<ReturnValue> {
87-
call_host_func_impl(&self.functions_map, name, args)
89+
self.call_host_func_impl(name, args)
8890
}
89-
}
90-
91-
fn register_host_function_helper(
92-
self_: &mut HostFuncsWrapper,
93-
name: String,
94-
func: HyperlightFunction,
95-
extra_allowed_syscalls: Option<Vec<ExtraAllowedSyscall>>,
96-
) -> Result<()> {
97-
if let Some(_syscalls) = extra_allowed_syscalls {
98-
#[cfg(all(feature = "seccomp", target_os = "linux"))]
99-
self_.functions_map.insert(name, (func, Some(_syscalls)));
10091

92+
fn register_host_function_helper(
93+
&mut self,
94+
name: String,
95+
function: HyperlightFunction,
96+
extra_allowed_syscalls: Option<Vec<ExtraAllowedSyscall>>,
97+
) -> Result<()> {
10198
#[cfg(not(all(feature = "seccomp", target_os = "linux")))]
102-
return Err(new_error!(
103-
"Extra syscalls are only supported on Linux with seccomp"
104-
));
105-
} else {
106-
self_.functions_map.insert(name, (func, None));
99+
if extra_allowed_syscalls.is_some() {
100+
return Err(new_error!(
101+
"Extra syscalls are only supported on Linux with seccomp"
102+
));
103+
}
104+
self.functions_map.insert(
105+
name,
106+
FunctionEntry {
107+
function,
108+
extra_allowed_syscalls,
109+
},
110+
);
111+
Ok(())
107112
}
108113

109-
Ok(())
110-
}
111-
112-
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
113-
fn call_host_func_impl(
114-
host_funcs: &HashMap<String, (HyperlightFunction, Option<Vec<ExtraAllowedSyscall>>)>,
115-
name: &str,
116-
args: Vec<ParameterValue>,
117-
) -> Result<ReturnValue> {
118-
// Inner function containing the common logic
119-
fn call_func(
120-
host_funcs: &HashMap<String, (HyperlightFunction, Option<Vec<ExtraAllowedSyscall>>)>,
121-
name: &str,
122-
args: Vec<ParameterValue>,
123-
) -> Result<ReturnValue> {
124-
let func_with_syscalls = host_funcs
114+
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
115+
fn call_host_func_impl(&self, name: &str, args: Vec<ParameterValue>) -> Result<ReturnValue> {
116+
let FunctionEntry {
117+
function,
118+
extra_allowed_syscalls,
119+
} = self
120+
.functions_map
125121
.get(name)
126122
.ok_or_else(|| HostFunctionNotFound(name.to_string()))?;
127123

128-
let func = func_with_syscalls.0.clone();
129-
130-
#[cfg(all(feature = "seccomp", target_os = "linux"))]
131-
{
132-
let syscalls = func_with_syscalls.1.clone();
133-
let seccomp_filter =
134-
crate::seccomp::guest::get_seccomp_filter_for_host_function_worker_thread(
135-
syscalls,
136-
)?;
137-
seccompiler::apply_filter(&seccomp_filter)?;
138-
}
139-
140-
crate::metrics::maybe_time_and_emit_host_call(name, || func.call(args))
141-
}
142-
143-
cfg_if::cfg_if! {
144-
if #[cfg(all(feature = "seccomp", target_os = "linux"))] {
145-
// Clone variables for the thread
146-
let host_funcs_cloned = host_funcs.clone();
147-
let name_cloned = name.to_string();
148-
let args_cloned = args.clone();
149-
150-
// Create a new thread when seccomp is enabled on Linux
151-
let join_handle = std::thread::Builder::new()
152-
.name(format!("Host Function Worker Thread for: {:?}", name_cloned))
153-
.spawn(move || {
154-
// We have a `catch_unwind` here because, if a disallowed syscall is issued,
155-
// we handle it by panicking. This is to avoid returning execution to the
156-
// offending host function—for two reasons: (1) if a host function is issuing
157-
// disallowed syscalls, it could be unsafe to return to, and (2) returning
158-
// execution after trapping the disallowed syscall can lead to UB (e.g., try
159-
// running a host function that attempts to sleep without `SYS_clock_nanosleep`,
160-
// you'll block the syscall but panic in the aftermath).
161-
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| call_func(&host_funcs_cloned, &name_cloned, args_cloned))) {
162-
Ok(val) => val,
163-
Err(err) => {
164-
if let Some(crate::HyperlightError::DisallowedSyscall) = err.downcast_ref::<crate::HyperlightError>() {
165-
return Err(crate::HyperlightError::DisallowedSyscall)
166-
}
167-
168-
crate::log_then_return!("Host function {} panicked", name_cloned);
169-
}
170-
}
171-
})?;
172-
173-
join_handle.join().map_err(|_| new_error!("Error joining thread executing host function"))?
174-
} else {
175-
// Directly call the function without creating a new thread
176-
call_func(host_funcs, name, args)
177-
}
124+
// Create a new thread when seccomp is enabled on Linux
125+
maybe_with_seccomp(name, extra_allowed_syscalls.as_deref(), || {
126+
crate::metrics::maybe_time_and_emit_host_call(name, || function.call(args))
127+
})
178128
}
179129
}
180130

@@ -197,3 +147,55 @@ pub(super) fn default_writer_func(s: String) -> Result<i32> {
197147
}
198148
}
199149
}
150+
151+
#[cfg(all(feature = "seccomp", target_os = "linux"))]
152+
fn maybe_with_seccomp<T: Send>(
153+
name: &str,
154+
syscalls: Option<&[ExtraAllowedSyscall]>,
155+
f: impl FnOnce() -> Result<T> + Send,
156+
) -> Result<T> {
157+
use crate::seccomp::guest::get_seccomp_filter_for_host_function_worker_thread;
158+
159+
// Use a scoped thread so that we can pass around references without having to clone them.
160+
crossbeam::thread::scope(|s| {
161+
s.builder()
162+
.name(format!("Host Function Worker Thread for: {name:?}",))
163+
.spawn(move |_| {
164+
let seccomp_filter = get_seccomp_filter_for_host_function_worker_thread(syscalls)?;
165+
seccompiler::apply_filter(&seccomp_filter)?;
166+
167+
// We have a `catch_unwind` here because, if a disallowed syscall is issued,
168+
// we handle it by panicking. This is to avoid returning execution to the
169+
// offending host function—for two reasons: (1) if a host function is issuing
170+
// disallowed syscalls, it could be unsafe to return to, and (2) returning
171+
// execution after trapping the disallowed syscall can lead to UB (e.g., try
172+
// running a host function that attempts to sleep without `SYS_clock_nanosleep`,
173+
// you'll block the syscall but panic in the aftermath).
174+
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {
175+
Ok(val) => val,
176+
Err(err) => {
177+
if let Some(crate::HyperlightError::DisallowedSyscall) =
178+
err.downcast_ref::<crate::HyperlightError>()
179+
{
180+
return Err(crate::HyperlightError::DisallowedSyscall);
181+
}
182+
183+
crate::log_then_return!("Host function {} panicked", name);
184+
}
185+
}
186+
})?
187+
.join()
188+
.map_err(|_| new_error!("Error joining thread executing host function"))?
189+
})
190+
.map_err(|_| new_error!("Error joining thread executing host function"))?
191+
}
192+
193+
#[cfg(not(all(feature = "seccomp", target_os = "linux")))]
194+
fn maybe_with_seccomp<T: Send>(
195+
_name: &str,
196+
_syscalls: Option<&[ExtraAllowedSyscall]>,
197+
f: impl FnOnce() -> Result<T> + Send,
198+
) -> Result<T> {
199+
// No seccomp, just call the function
200+
f()
201+
}

src/hyperlight_host/src/sandbox/initialized_multi_use.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use hyperlight_common::flatbuffer_wrappers::function_types::{
2121
};
2222
use tracing::{instrument, Span};
2323

24-
use super::host_funcs::HostFuncsWrapper;
24+
use super::host_funcs::FunctionRegistry;
2525
use super::{MemMgrWrapper, WrapperGetter};
2626
use crate::func::call_ctx::MultiUseGuestCallContext;
2727
use crate::func::guest_dispatch::call_function_on_guest;
@@ -41,7 +41,7 @@ use crate::Result;
4141
/// in this case the state of the sandbox is not reset until the context is finished and the `MultiUseSandbox` is returned.
4242
pub struct MultiUseSandbox {
4343
// We need to keep a reference to the host functions, even if the compiler marks it as unused. The compiler cannot detect our dynamic usages of the host function in `HyperlightFunction::call`.
44-
pub(super) _host_funcs: Arc<Mutex<HostFuncsWrapper>>,
44+
pub(super) _host_funcs: Arc<Mutex<FunctionRegistry>>,
4545
pub(crate) mem_mgr: MemMgrWrapper<HostSharedMemory>,
4646
hv_handler: HypervisorHandler,
4747
}
@@ -73,7 +73,7 @@ impl MultiUseSandbox {
7373
/// (as a `From` implementation would be)
7474
#[instrument(skip_all, parent = Span::current(), level = "Trace")]
7575
pub(super) fn from_uninit(
76-
host_funcs: Arc<Mutex<HostFuncsWrapper>>,
76+
host_funcs: Arc<Mutex<FunctionRegistry>>,
7777
mgr: MemMgrWrapper<HostSharedMemory>,
7878
hv_handler: HypervisorHandler,
7979
) -> MultiUseSandbox {

src/hyperlight_host/src/sandbox/outb.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use log::{Level, Record};
2424
use tracing::{instrument, Span};
2525
use tracing_log::format_trace;
2626

27-
use super::host_funcs::HostFuncsWrapper;
27+
use super::host_funcs::FunctionRegistry;
2828
use super::mem_mgr::MemMgrWrapper;
2929
use crate::hypervisor::handlers::{OutBHandler, OutBHandlerFunction, OutBHandlerWrapper};
3030
use crate::mem::mgr::SandboxMemoryManager;
@@ -97,7 +97,7 @@ pub(super) fn outb_log(mgr: &mut SandboxMemoryManager<HostSharedMemory>) -> Resu
9797
#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
9898
fn handle_outb_impl(
9999
mem_mgr: &mut MemMgrWrapper<HostSharedMemory>,
100-
host_funcs: Arc<Mutex<HostFuncsWrapper>>,
100+
host_funcs: Arc<Mutex<FunctionRegistry>>,
101101
port: u16,
102102
data: Vec<u8>,
103103
) -> Result<()> {
@@ -153,7 +153,7 @@ fn handle_outb_impl(
153153
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
154154
pub(crate) fn outb_handler_wrapper(
155155
mut mem_mgr_wrapper: MemMgrWrapper<HostSharedMemory>,
156-
host_funcs_wrapper: Arc<Mutex<HostFuncsWrapper>>,
156+
host_funcs_wrapper: Arc<Mutex<FunctionRegistry>>,
157157
) -> OutBHandlerWrapper {
158158
let outb_func: OutBHandlerFunction = Box::new(move |port, payload| {
159159
handle_outb_impl(

src/hyperlight_host/src/sandbox/uninitialized.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use tracing::{instrument, Span};
2525

2626
#[cfg(gdb)]
2727
use super::config::DebugInfo;
28-
use super::host_funcs::{default_writer_func, HostFuncsWrapper};
28+
use super::host_funcs::{default_writer_func, FunctionRegistry};
2929
use super::mem_mgr::MemMgrWrapper;
3030
use super::run_options::SandboxRunOptions;
3131
use super::uninitialized_evolve::evolve_impl_multi_use;
@@ -48,7 +48,7 @@ use crate::{log_build_details, log_then_return, new_error, MultiUseSandbox, Resu
4848
/// `UninitializedSandbox` into an initialized `Sandbox`.
4949
pub struct UninitializedSandbox {
5050
/// Registered host functions
51-
pub(crate) host_funcs: Arc<Mutex<HostFuncsWrapper>>,
51+
pub(crate) host_funcs: Arc<Mutex<FunctionRegistry>>,
5252
/// The memory manager for the sandbox.
5353
pub(crate) mgr: MemMgrWrapper<ExclusiveSharedMemory>,
5454
pub(crate) run_inprocess: bool,
@@ -184,7 +184,7 @@ impl UninitializedSandbox {
184184

185185
mem_mgr_wrapper.write_memory_layout(run_inprocess)?;
186186

187-
let host_funcs = Arc::new(Mutex::new(HostFuncsWrapper::default()));
187+
let host_funcs = Arc::new(Mutex::new(FunctionRegistry::default()));
188188

189189
let mut sandbox = Self {
190190
host_funcs,

src/hyperlight_host/src/sandbox/uninitialized_evolve.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use crate::mem::ptr::RawPtr;
3131
use crate::mem::shared_mem::GuestSharedMemory;
3232
#[cfg(gdb)]
3333
use crate::sandbox::config::DebugInfo;
34-
use crate::sandbox::host_funcs::HostFuncsWrapper;
34+
use crate::sandbox::host_funcs::FunctionRegistry;
3535
use crate::sandbox::mem_access::mem_access_handler_wrapper;
3636
use crate::sandbox::outb::outb_handler_wrapper;
3737
use crate::sandbox::{HostSharedMemory, MemMgrWrapper};
@@ -56,7 +56,7 @@ fn evolve_impl<TransformFunc, ResSandbox: Sandbox>(
5656
) -> Result<ResSandbox>
5757
where
5858
TransformFunc: Fn(
59-
Arc<Mutex<HostFuncsWrapper>>,
59+
Arc<Mutex<FunctionRegistry>>,
6060
MemMgrWrapper<HostSharedMemory>,
6161
HypervisorHandler,
6262
) -> Result<ResSandbox>,
@@ -105,7 +105,7 @@ pub(super) fn evolve_impl_multi_use(u_sbox: UninitializedSandbox) -> Result<Mult
105105
fn hv_init(
106106
hshm: &MemMgrWrapper<HostSharedMemory>,
107107
gshm: SandboxMemoryManager<GuestSharedMemory>,
108-
host_funcs: Arc<Mutex<HostFuncsWrapper>>,
108+
host_funcs: Arc<Mutex<FunctionRegistry>>,
109109
max_init_time: Duration,
110110
max_exec_time: Duration,
111111
max_wait_for_cancellation: Duration,

src/hyperlight_host/src/seccomp/guest.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,15 @@ fn syscalls_allowlist() -> Result<Vec<(i64, Vec<SeccompRule>)>> {
6969
/// (e.g., `KVM_SET_USER_MEMORY_REGION`, `KVM_GET_API_VERSION`, `KVM_CREATE_VM`,
7070
/// or `KVM_CREATE_VCPU`).
7171
pub(crate) fn get_seccomp_filter_for_host_function_worker_thread(
72-
extra_allowed_syscalls: Option<Vec<ExtraAllowedSyscall>>,
72+
extra_allowed_syscalls: Option<&[ExtraAllowedSyscall]>,
7373
) -> Result<BpfProgram> {
7474
let mut allowed_syscalls = syscalls_allowlist()?;
7575

7676
if let Some(extra_allowed_syscalls) = extra_allowed_syscalls {
7777
allowed_syscalls.extend(
7878
extra_allowed_syscalls
79-
.into_iter()
79+
.iter()
80+
.copied()
8081
.map(|syscall| (syscall, vec![])),
8182
);
8283

0 commit comments

Comments
 (0)