hyperlight_host/func/
host_functions.rs

1/*
2Copyright 2024 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::{ExtraAllowedSyscall, UninitializedSandbox};
24use crate::{log_then_return, new_error, Result};
25
26/// A representation of a host function.
27/// This is a thin wrapper around a `Fn(Args) -> Result<Output>`.
28#[derive(Clone)]
29pub struct HostFunction<Output, Args>
30where
31    Args: ParameterTuple,
32    Output: SupportedReturnType,
33{
34    // This is a thin wrapper around a `Fn(Args) -> Result<Output>`.
35    // But unlike `Fn` which is a trait, this is a concrete type.
36    // This allows us to:
37    //  1. Impose constraints on the function arguments and return type.
38    //  2. Impose a single function signature.
39    //
40    // This second point is important because the `Fn` trait is generic
41    // over the function arguments (with an associated return type).
42    // This means that a given type could implement `Fn` for multiple
43    // function signatures.
44    // This means we can't do something like:
45    // ```rust,ignore
46    // impl<Args, Output, F> SomeTrait for F
47    // where
48    //     F: Fn(Args) -> Result<Output>,
49    // { ... }
50    // ```
51    // because the concrete type F might implement `Fn` for multiple times,
52    // and that would means implementing `SomeTrait` multiple times for the
53    // same type.
54
55    // Use Arc in here instead of Box because it's useful in tests and
56    // presumably in other places to be able to clone a HostFunction and
57    // use it across different sandboxes.
58    func: Arc<dyn Fn(Args) -> Result<Output> + Send + Sync + 'static>,
59}
60
61pub(crate) struct TypeErasedHostFunction {
62    func: Box<dyn Fn(Vec<ParameterValue>) -> Result<ReturnValue> + Send + Sync + 'static>,
63}
64
65impl<Args, Output> HostFunction<Output, Args>
66where
67    Args: ParameterTuple,
68    Output: SupportedReturnType,
69{
70    /// Call the host function with the given arguments.
71    pub fn call(&self, args: Args) -> Result<Output> {
72        (self.func)(args)
73    }
74}
75
76impl TypeErasedHostFunction {
77    pub(crate) fn call(&self, args: Vec<ParameterValue>) -> Result<ReturnValue> {
78        (self.func)(args)
79    }
80}
81
82impl<Args, Output> From<HostFunction<Output, Args>> for TypeErasedHostFunction
83where
84    Args: ParameterTuple,
85    Output: SupportedReturnType,
86{
87    fn from(func: HostFunction<Output, Args>) -> TypeErasedHostFunction {
88        TypeErasedHostFunction {
89            func: Box::new(move |args: Vec<ParameterValue>| {
90                let args = Args::from_value(args)?;
91                Ok(func.call(args)?.into_value())
92            }),
93        }
94    }
95}
96
97macro_rules! impl_host_function {
98    ([$N:expr] ($($p:ident: $P:ident),*)) => {
99        /*
100        // Normally for a `Fn + Send + Sync` we don't need to use a Mutex
101        // like we do in the case of a `FnMut`.
102        // However, we can't implement `IntoHostFunction` for `Fn` and `FnMut`
103        // because `FnMut` is a supertrait of `Fn`.
104        */
105
106        impl<F, R, $($P),*> From<F> for HostFunction<R::ReturnType, ($($P,)*)>
107        where
108            F: FnMut($($P),*) -> R + Send + 'static,
109            ($($P,)*): ParameterTuple,
110            R: ResultType,
111        {
112            fn from(mut func: F) -> HostFunction<R::ReturnType, ($($P,)*)> {
113                let func = move |($($p,)*): ($($P,)*)| -> Result<R::ReturnType> {
114                    func($($p),*).into_result()
115                };
116                let func = Mutex::new(func);
117                HostFunction {
118                    func: Arc::new(move |args: ($($P,)*)| {
119                        func.try_lock()
120                            .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
121                            (args)
122                    })
123                }
124            }
125        }
126    };
127}
128
129for_each_tuple!(impl_host_function);
130
131pub(crate) fn register_host_function<Args: ParameterTuple, Output: SupportedReturnType>(
132    func: impl Into<HostFunction<Output, Args>>,
133    sandbox: &mut UninitializedSandbox,
134    name: &str,
135    extra_allowed_syscalls: Option<Vec<ExtraAllowedSyscall>>,
136) -> Result<()> {
137    let func = func.into().into();
138
139    if let Some(_eas) = extra_allowed_syscalls {
140        if cfg!(all(feature = "seccomp", target_os = "linux")) {
141            // Register with extra allowed syscalls
142            #[cfg(all(feature = "seccomp", target_os = "linux"))]
143            {
144                sandbox
145                    .host_funcs
146                    .try_lock()
147                    .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
148                    .register_host_function_with_syscalls(name.to_string(), func, _eas)?;
149            }
150        } else {
151            // Log and return an error
152            log_then_return!(
153                "Extra allowed syscalls are only supported on Linux with seccomp enabled"
154            );
155        }
156    } else {
157        // Register without extra allowed syscalls
158        sandbox
159            .host_funcs
160            .try_lock()
161            .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
162            .register_host_function(name.to_string(), func)?;
163    }
164
165    Ok(())
166}