enclave_runner/
loader.rs

1/* Copyright (c) Fortanix, Inc.
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7use std::arch::x86_64::{self, CpuidResult};
8use std::fs::File;
9use std::io::{Error as IoError, ErrorKind, Read, Result as IoResult};
10use std::ops::RangeInclusive;
11use std::os::raw::c_void;
12use std::path::Path;
13use std::{arch, str};
14
15use thiserror::Error as ThisError;
16use anyhow::{Context, format_err};
17
18#[cfg(feature = "crypto-openssl")]
19use openssl::{
20    hash::Hasher,
21    pkey::PKey,
22};
23
24use sgx_isa::{Attributes, AttributesFlags, Miscselect, Sigstruct};
25use sgxs::sgxs::PageReader;
26use sgxs::crypto::{SgxHashOps, SgxRsaOps};
27use sgxs::loader::{Load, MappingInfo, Tcs};
28use sgxs::sigstruct::{self, EnclaveHash, Signer};
29
30use crate::tcs::DebugBuffer;
31use crate::usercalls::UsercallExtension;
32use crate::{Command, Library};
33
34enum EnclaveSource<'a> {
35    Path(&'a Path),
36    File(File),
37    Data(&'a [u8]),
38}
39
40impl<'a> EnclaveSource<'a> {
41    fn try_clone(&self) -> Option<Self> {
42        match *self {
43            EnclaveSource::Path(path) => Some(EnclaveSource::Path(path)),
44            EnclaveSource::Data(data) => Some(EnclaveSource::Data(data)),
45            EnclaveSource::File(_) => None,
46        }
47    }
48}
49
50impl<'a> Read for EnclaveSource<'a> {
51    fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
52        if let &mut EnclaveSource::Path(path) = self {
53            let file = File::open(path)?;
54            *self = EnclaveSource::File(file);
55        }
56
57        match *self {
58            EnclaveSource::File(ref mut file) => file.read(buf),
59            EnclaveSource::Data(ref mut data) => data.read(buf),
60            EnclaveSource::Path(_) => unreachable!(),
61        }
62    }
63}
64
65pub struct EnclaveBuilder<'a> {
66    enclave: EnclaveSource<'a>,
67    signature: Option<Sigstruct>,
68    attributes: Option<Attributes>,
69    miscselect: Option<Miscselect>,
70    usercall_ext: Option<Box<dyn UsercallExtension>>,
71    load_and_sign: Option<Box<dyn FnOnce(Signer) -> Result<Sigstruct, anyhow::Error>>>,
72    hash_enclave: Option<Box<dyn FnOnce(&mut EnclaveSource<'_>) -> Result<EnclaveHash, anyhow::Error>>>,
73    forward_panics: bool,
74    force_time_usercalls: bool,
75    cmd_args: Option<Vec<Vec<u8>>>,
76    num_worker_threads: Option<usize>,
77}
78
79#[derive(Debug, ThisError)]
80pub enum EnclavePanic {
81    /// The first byte of the debug buffer was 0
82    #[error("Enclave panicked.")]
83    NoDebugBuf,
84    /// The debug buffer could be interpreted as a zero-terminated UTF-8 string
85    #[error("Enclave panicked: {}", _0)]
86    DebugStr(String),
87    /// The first byte of the debug buffer was not 0, but it was also not a
88    /// zero-terminated UTF-8 string
89    #[error("Enclave panicked: {:?}", _0)]
90    DebugBuf(Vec<u8>),
91}
92
93impl From<DebugBuffer> for EnclavePanic {
94    fn from(buf: DebugBuffer) -> EnclavePanic {
95        if buf[0] == 0 {
96            EnclavePanic::NoDebugBuf
97        } else {
98            match str::from_utf8(buf.split(|v| *v == 0).next().unwrap()) {
99                Ok(s) => EnclavePanic::DebugStr(s.to_owned()),
100                Err(_) => EnclavePanic::DebugBuf(buf.to_vec()),
101            }
102        }
103    }
104}
105
106// Erased here refers to Type Erasure
107#[derive(Debug)]
108pub(crate) struct ErasedTcs {
109    address: *mut c_void,
110    // This represents a resource so we need to maintain ownership even if not
111    // used
112    #[allow(dead_code)]
113    tcs: Box<dyn Tcs>,
114}
115
116// Would be `send` if we didn't cache the raw pointer address
117unsafe impl Send for ErasedTcs {}
118
119impl ErasedTcs {
120    fn new<T: Tcs + 'static>(tcs: T) -> ErasedTcs {
121        ErasedTcs {
122            address: tcs.address(),
123            tcs: Box::new(tcs),
124        }
125    }
126}
127
128impl Tcs for ErasedTcs {
129    fn address(&self) -> *mut c_void {
130        self.address
131    }
132}
133
134impl<'a> EnclaveBuilder<'a> {
135    pub fn new(enclave_path: &'a Path) -> EnclaveBuilder<'a> {
136        Self::new_with_source(EnclaveSource::Path(enclave_path))
137    }
138
139    pub fn new_from_memory(enclave_data: &'a [u8]) -> EnclaveBuilder<'a> {
140        Self::new_with_source(EnclaveSource::Data(enclave_data))
141    }
142
143    fn new_with_source(enclave: EnclaveSource<'a>) -> EnclaveBuilder<'a> {
144        let mut ret = EnclaveBuilder {
145            enclave,
146            attributes: None,
147            miscselect: None,
148            signature: None,
149            usercall_ext: None,
150            load_and_sign: None,
151            hash_enclave: None,
152            forward_panics: false,
153            force_time_usercalls: true, // By default, keep the old behavior of always doing a usercall on an insecure_time call
154            cmd_args: None,
155            num_worker_threads: None,
156        };
157
158        let _ = ret.coresident_signature();
159
160        #[cfg(feature = "crypto-openssl")]
161        ret.with_dummy_signature_signer::<Hasher, _, _, _, _>(|der| {
162            PKey::private_key_from_der(der).unwrap().rsa().unwrap()
163        });
164
165        ret
166    }
167
168    fn generate_xfrm(max_ssaframesize_in_pages: u32) -> u64 {
169        fn cpuid(eax: u32, ecx: u32) -> Option<CpuidResult> {
170            unsafe {
171                if eax <= x86_64::__get_cpuid_max(0).0 {
172                    Some(x86_64::__cpuid_count(eax, ecx))
173                } else {
174                    None
175                }
176            }
177        }
178
179        fn xgetbv0() -> u64 {
180            unsafe { arch::x86_64::_xgetbv(0) }
181        }
182
183        debug_assert_ne!(0, max_ssaframesize_in_pages);
184        let xcr0 = xgetbv0();
185
186        // See algorithm of Intel x86 dev manual Chpt 40.7.2.2
187        let xfrm = (0..64)
188            .map(|bit| {
189                let select = 0x1 << bit;
190
191                if bit == 0 || bit == 1 {
192                    return select; // Bit 0 and 1 always need to be set
193                }
194
195                if xcr0 & select == 0 {
196                    return 0;
197                }
198
199                let CpuidResult { ebx: base, eax: size, .. } = match cpuid(0x0d, bit) {
200                    None | Some(CpuidResult { ebx: 0, .. }) => return 0,
201                    Some(v) => v,
202                };
203
204                if max_ssaframesize_in_pages * 0x1000 < base + size {
205                    return 0;
206                }
207
208                select
209            })
210            .fold(0, |xfrm, b| xfrm | b);
211
212        // Intel x86 dev manual Vol 3 Chpt 13.3:
213        // "Executing the XSETBV instruction causes a general-protection fault (#GP) if ECX = 0
214        // and EAX[17] ≠ EAX[18] (TILECFG and TILEDATA must be enabled together). This implies
215        // that the value of XCR0[18:17] is always either 00b or 11b."
216        // The enclave entry code executes xrstor, and we may have just cleared bit 18, so we
217        // need to correct the invariant
218        const XCR0_TILE_BITS: u64 = 0b11 << 17;
219        match xfrm & XCR0_TILE_BITS {
220            XCR0_TILE_BITS | 0 => xfrm,
221            _ => xfrm & !XCR0_TILE_BITS,
222        }
223    }
224
225    fn generate_dummy_signature(&mut self) -> Result<Sigstruct, anyhow::Error> {
226        let mut enclave = self.enclave.try_clone().unwrap();
227        let create_info = PageReader::new(&mut enclave)?;
228        let mut enclave = self.enclave.try_clone().unwrap();
229        let hash = match self.hash_enclave.take() {
230            Some(f) => f(&mut enclave)?,
231            None => return Err(format_err!("either compile with default features or use with_dummy_signature_signer()"))
232        };
233        let mut signer = Signer::new(hash);
234
235        let attributes = self.attributes.unwrap_or_else(|| Attributes {
236            flags: AttributesFlags::DEBUG | AttributesFlags::MODE64BIT,
237            xfrm: Self::generate_xfrm(create_info.0.ecreate.ssaframesize),
238        });
239        signer
240            .attributes_flags(attributes.flags, !0)
241            .attributes_xfrm(attributes.xfrm, !0);
242
243        if let Some(miscselect) = self.miscselect {
244            signer.miscselect(miscselect, !0);
245        }
246
247        match self.load_and_sign.take() {
248            Some(f) => f(signer),
249            None => Err(format_err!("either compile with default features or use with_dummy_signature_signer()"))
250        }
251    }
252
253    pub fn dummy_signature(&mut self) -> &mut Self {
254        self.signature = None;
255        self
256    }
257
258    /// Use custom implemetations of [`SgxHashOps`] and [`SgxRsaOps`] for producing dummy signature.
259    ///
260    /// The hasher is specified through type parameter `H`, and the signer through `S`.
261    /// `load_key` is used to parse an RSA private key in DER format and should return a type `T`
262    /// that implements `AsRef<S>` where `S` is a type that implements [`SgxRsaOps`]. `E` is the
263    /// associated `Error` type of `S` when implementing [`SgxRsaOps`].
264    ///
265    /// [`SgxHashOps`]: ../sgxs/crypto/trait.SgxHashOps.html
266    /// [`SgxRsaOps`]: ../sgxs/crypto/trait.SgxRsaOps.html
267    pub fn with_dummy_signature_signer<H, S, F, E, T>(&mut self, load_key: F)
268    where
269        H: SgxHashOps,
270        E: std::error::Error + Send + Sync + 'static,
271        S: SgxRsaOps<Error = E>,
272        T: AsRef<S>,
273        F: 'static + FnOnce(&[u8]) -> T,
274    {
275        self.load_and_sign = Some(Box::new(move |signer| {
276            let key = load_key(include_bytes!("dummy.key"));
277            signer.sign::<_, H>(key.as_ref()).map_err(|e| e.into())
278        }));
279        self.hash_enclave = Some(Box::new(|stream| {
280            EnclaveHash::from_stream::<_, H>(stream)
281        }));
282    }
283
284    pub fn coresident_signature(&mut self) -> IoResult<&mut Self> {
285        if let EnclaveSource::Path(path) = self.enclave {
286            let sigfile = path.with_extension("sig");
287            self.signature(sigfile)
288        } else {
289            Err(IoError::new(
290                ErrorKind::NotFound,
291                "Can't load coresident signature for non-file enclave",
292            ))
293        }
294    }
295
296    pub fn signature<P: AsRef<Path>>(&mut self, path: P) -> IoResult<&mut Self> {
297        let mut file = File::open(path)?;
298        self.signature = Some(sigstruct::read(&mut file)?);
299        Ok(self)
300    }
301
302    pub fn sigstruct(&mut self, sigstruct: Sigstruct) -> &mut Self {
303        self.signature = Some(sigstruct);
304        self
305    }
306
307    pub fn attributes(&mut self, attributes: Attributes) -> &mut Self {
308        self.attributes = Some(attributes);
309        self
310    }
311
312    pub fn miscselect(&mut self, miscselect: Miscselect) -> &mut Self {
313        self.miscselect = Some(miscselect);
314        self
315    }
316
317    pub fn usercall_extension<T: Into<Box<dyn UsercallExtension>>>(&mut self, extension: T) {
318        self.usercall_ext = Some(extension.into());
319    }
320
321    /// Whether to panic the runner if any enclave thread panics.
322    /// Defaults to `false`.
323    /// Note: If multiple enclaves are loaded, and an enclave with this set to
324    /// true panics, then all enclaves handled by this runner will exit because
325    /// the runner itself will panic.
326    pub fn forward_panics(&mut self, fp: bool) -> &mut Self {
327        self.forward_panics = fp;
328        self
329    }
330
331    /// SGXv2 platforms allow enclaves to use the `rdtsc` instruction. This can speed up
332    /// performance significantly as enclave no longer need to call out to userspace to request the
333    /// current time. Unfortunately, older enclaves are not compatible with new enclave runners.
334    /// Also, sometimes the behavior of enclaves always calling out the userspace needs to be
335    /// simulated. This setting enforces the old behavior.
336    pub fn force_insecure_time_usercalls(&mut self, force_time_usercalls: bool) -> &mut Self {
337        self.force_time_usercalls = force_time_usercalls;
338        self
339    }
340
341    fn initialized_args_mut(&mut self) -> &mut Vec<Vec<u8>> {
342        self.cmd_args.get_or_insert_with(|| vec![b"enclave".to_vec()])
343    }
344
345    /// Adds multiple arguments to pass to enclave's `fn main`.
346    /// **NOTE:** This is not an appropriate channel for passing secrets or
347    /// security configurations to the enclave.
348    ///
349    /// **NOTE:** This is only applicable to [`Command`] enclaves.
350    /// Adding command arguments and then calling [`build_library`] will cause
351    /// a panic.
352    ///
353    /// [`Command`]: struct.Command.html
354    /// [`build_library`]: struct.EnclaveBuilder.html#method.build_library
355    pub fn args<I, S>(&mut self, args: I) -> &mut Self
356    where
357        I: IntoIterator<Item = S>,
358        S: AsRef<[u8]>,
359    {
360        let args = args.into_iter().map(|a| a.as_ref().to_owned());
361        self.initialized_args_mut().extend(args);
362        self
363    }
364
365    /// Adds an argument to pass to enclave's `fn main`.
366    /// **NOTE:** This is not an appropriate channel for passing secrets or
367    /// security configurations to the enclave.
368    ///
369    /// **NOTE:** This is only applicable to [`Command`] enclaves.
370    /// Adding command arguments and then calling [`build_library`] will cause
371    /// a panic.
372    ///
373    /// [`build_library`]: struct.EnclaveBuilder.html#method.build_library
374    pub fn arg<S: AsRef<[u8]>>(&mut self, arg: S) -> &mut Self {
375        let arg = arg.as_ref().to_owned();
376        self.initialized_args_mut().push(arg);
377        self
378    }
379
380    /// Sets the number of worker threads used to run the enclave.
381    ///
382    /// **NOTE:** This is only applicable to [`Command`] enclaves.
383    /// Setting this and then calling [`build_library`](Self::build_library) will cause a panic.
384    pub fn num_worker_threads(&mut self, num_worker_threads: usize) -> &mut Self {
385        self.num_worker_threads = Some(num_worker_threads);
386        self
387    }
388
389    fn load<T: Load>(
390        mut self,
391        loader: &mut T,
392    ) -> Result<(Vec<ErasedTcs>, *mut c_void, usize, bool, bool), anyhow::Error> {
393        let signature = match self.signature {
394            Some(sig) => sig,
395            None => self
396                .generate_dummy_signature()
397                .context("While generating dummy signature")?,
398        };
399        let attributes = self.attributes.unwrap_or(signature.attributes);
400        let miscselect = self.miscselect.unwrap_or(signature.miscselect);
401        let mapping = loader.load(&mut self.enclave, &signature, attributes, miscselect)?;
402        let forward_panics = self.forward_panics;
403        let force_time_usercalls = self.force_time_usercalls;
404        if mapping.tcss.is_empty() {
405            unimplemented!()
406        }
407        Ok((
408            mapping.tcss.into_iter().map(ErasedTcs::new).collect(),
409            mapping.info.address(),
410            mapping.info.size(),
411            forward_panics,
412            force_time_usercalls,
413        ))
414    }
415
416    pub fn build<T: Load>(mut self, loader: &mut T) -> Result<Command, anyhow::Error> {
417        if let Some(num_worker_threads) = self.num_worker_threads {
418            const NUM_WORKER_THREADS_RANGE: RangeInclusive<usize> = 1..=65536;
419            anyhow::ensure!(
420                NUM_WORKER_THREADS_RANGE.contains(&num_worker_threads),
421                "`num_worker_threads` must be in range {NUM_WORKER_THREADS_RANGE:?}"
422            );
423        }
424        let num_worker_threads = self.num_worker_threads.unwrap_or_else(num_cpus::get);
425
426        self.initialized_args_mut();
427        let args = self.cmd_args.take().unwrap_or_default();
428        let c = self.usercall_ext.take();
429        self.load(loader)
430            .map(|(t, a, s, fp, dti)| Command::internal_new(t, a, s, c, fp, dti, args, num_worker_threads))
431    }
432
433    /// Panics if you have previously called [`arg`], [`args`], or [`num_worker_threads`].
434    ///
435    /// [`arg`]: struct.EnclaveBuilder.html#method.arg
436    /// [`args`]: struct.EnclaveBuilder.html#method.args
437    /// [`num_worker_threads`]: Self::num_worker_threads()
438    pub fn build_library<T: Load>(mut self, loader: &mut T) -> Result<Library, anyhow::Error> {
439        assert!(self.cmd_args.is_none(), "Command arguments do not apply to Library enclaves.");
440        assert!(self.num_worker_threads.is_none(), "`num_worker_threads` cannot be specified for Library enclaves.");
441        let c = self.usercall_ext.take();
442        self.load(loader)
443            .map(|(t, a, s, fp, dti)| Library::internal_new(t, a, s, c, fp, dti))
444    }
445}