hyperlight_host/func/
host_functions.rs

1/*
2Copyright 2025  The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17use std::sync::{Arc, Mutex};
18
19use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnValue};
20
21use super::utils::for_each_tuple;
22use super::{ParameterTuple, ResultType, SupportedReturnType};
23use crate::sandbox::host_funcs::FunctionEntry;
24use crate::sandbox::{ExtraAllowedSyscall, UninitializedSandbox};
25use crate::{Result, new_error};
26
27/// A sandbox on which (primitive) host functions can be registered
28///
29pub trait Registerable {
30    /// Register a primitive host function
31    fn register_host_function<Args: ParameterTuple, Output: SupportedReturnType>(
32        &mut self,
33        name: &str,
34        hf: impl Into<HostFunction<Output, Args>>,
35    ) -> Result<()>;
36    /// Register a primitive host function whose worker thread has
37    /// extra permissive seccomp filters installed
38    #[cfg(all(feature = "seccomp", target_os = "linux"))]
39    fn register_host_function_with_syscalls<Args: ParameterTuple, Output: SupportedReturnType>(
40        &mut self,
41        name: &str,
42        hf: impl Into<HostFunction<Output, Args>>,
43        eas: Vec<ExtraAllowedSyscall>,
44    ) -> Result<()>;
45}
46impl Registerable for UninitializedSandbox {
47    fn register_host_function<Args: ParameterTuple, Output: SupportedReturnType>(
48        &mut self,
49        name: &str,
50        hf: impl Into<HostFunction<Output, Args>>,
51    ) -> Result<()> {
52        let mut hfs = self
53            .host_funcs
54            .try_lock()
55            .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
56
57        let entry = FunctionEntry {
58            function: hf.into().into(),
59            extra_allowed_syscalls: None,
60            parameter_types: Args::TYPE,
61            return_type: Output::TYPE,
62        };
63
64        (*hfs).register_host_function(name.to_string(), entry, self.mgr.unwrap_mgr_mut())
65    }
66    #[cfg(all(feature = "seccomp", target_os = "linux"))]
67    fn register_host_function_with_syscalls<Args: ParameterTuple, Output: SupportedReturnType>(
68        &mut self,
69        name: &str,
70        hf: impl Into<HostFunction<Output, Args>>,
71        eas: Vec<ExtraAllowedSyscall>,
72    ) -> Result<()> {
73        let mut hfs = self
74            .host_funcs
75            .try_lock()
76            .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
77
78        let entry = FunctionEntry {
79            function: hf.into().into(),
80            extra_allowed_syscalls: Some(eas),
81            parameter_types: Args::TYPE,
82            return_type: Output::TYPE,
83        };
84
85        (*hfs).register_host_function(name.to_string(), entry, self.mgr.unwrap_mgr_mut())
86    }
87}
88
89/// A representation of a host function.
90/// This is a thin wrapper around a `Fn(Args) -> Result<Output>`.
91#[derive(Clone)]
92pub struct HostFunction<Output, Args>
93where
94    Args: ParameterTuple,
95    Output: SupportedReturnType,
96{
97    // This is a thin wrapper around a `Fn(Args) -> Result<Output>`.
98    // But unlike `Fn` which is a trait, this is a concrete type.
99    // This allows us to:
100    //  1. Impose constraints on the function arguments and return type.
101    //  2. Impose a single function signature.
102    //
103    // This second point is important because the `Fn` trait is generic
104    // over the function arguments (with an associated return type).
105    // This means that a given type could implement `Fn` for multiple
106    // function signatures.
107    // This means we can't do something like:
108    // ```rust,ignore
109    // impl<Args, Output, F> SomeTrait for F
110    // where
111    //     F: Fn(Args) -> Result<Output>,
112    // { ... }
113    // ```
114    // because the concrete type F might implement `Fn` for multiple times,
115    // and that would means implementing `SomeTrait` multiple times for the
116    // same type.
117
118    // Use Arc in here instead of Box because it's useful in tests and
119    // presumably in other places to be able to clone a HostFunction and
120    // use it across different sandboxes.
121    func: Arc<dyn Fn(Args) -> Result<Output> + Send + Sync + 'static>,
122}
123
124pub(crate) struct TypeErasedHostFunction {
125    func: Box<dyn Fn(Vec<ParameterValue>) -> Result<ReturnValue> + Send + Sync + 'static>,
126}
127
128impl<Args, Output> HostFunction<Output, Args>
129where
130    Args: ParameterTuple,
131    Output: SupportedReturnType,
132{
133    /// Call the host function with the given arguments.
134    pub fn call(&self, args: Args) -> Result<Output> {
135        (self.func)(args)
136    }
137}
138
139impl TypeErasedHostFunction {
140    pub(crate) fn call(&self, args: Vec<ParameterValue>) -> Result<ReturnValue> {
141        (self.func)(args)
142    }
143}
144
145impl<Args, Output> From<HostFunction<Output, Args>> for TypeErasedHostFunction
146where
147    Args: ParameterTuple,
148    Output: SupportedReturnType,
149{
150    fn from(func: HostFunction<Output, Args>) -> TypeErasedHostFunction {
151        TypeErasedHostFunction {
152            func: Box::new(move |args: Vec<ParameterValue>| {
153                let args = Args::from_value(args)?;
154                Ok(func.call(args)?.into_value())
155            }),
156        }
157    }
158}
159
160macro_rules! impl_host_function {
161    ([$N:expr] ($($p:ident: $P:ident),*)) => {
162        /*
163        // Normally for a `Fn + Send + Sync` we don't need to use a Mutex
164        // like we do in the case of a `FnMut`.
165        // However, we can't implement `IntoHostFunction` for `Fn` and `FnMut`
166        // because `FnMut` is a supertrait of `Fn`.
167        */
168
169        impl<F, R, $($P),*> From<F> for HostFunction<R::ReturnType, ($($P,)*)>
170        where
171            F: FnMut($($P),*) -> R + Send + 'static,
172            ($($P,)*): ParameterTuple,
173            R: ResultType,
174        {
175            fn from(mut func: F) -> HostFunction<R::ReturnType, ($($P,)*)> {
176                let func = move |($($p,)*): ($($P,)*)| -> Result<R::ReturnType> {
177                    func($($p),*).into_result()
178                };
179                let func = Mutex::new(func);
180                HostFunction {
181                    func: Arc::new(move |args: ($($P,)*)| {
182                        func.try_lock()
183                            .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
184                            (args)
185                    })
186                }
187            }
188        }
189    };
190}
191
192for_each_tuple!(impl_host_function);
193
194pub(crate) fn register_host_function<Args: ParameterTuple, Output: SupportedReturnType>(
195    func: impl Into<HostFunction<Output, Args>>,
196    sandbox: &mut UninitializedSandbox,
197    name: &str,
198    extra_allowed_syscalls: Option<Vec<ExtraAllowedSyscall>>,
199) -> Result<()> {
200    let func = func.into().into();
201
202    let entry = FunctionEntry {
203        function: func,
204        extra_allowed_syscalls: extra_allowed_syscalls.clone(),
205        parameter_types: Args::TYPE,
206        return_type: Output::TYPE,
207    };
208
209    sandbox
210        .host_funcs
211        .try_lock()
212        .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
213        .register_host_function(name.to_string(), entry, sandbox.mgr.unwrap_mgr_mut())?;
214
215    Ok(())
216}