occlum_pal/
lib.rs

1//! Occlum SGX enclaves management.
2
3#![allow(non_upper_case_globals)]
4#![allow(non_camel_case_types)]
5#![allow(non_snake_case)]
6#![allow(deref_nullptr)]
7#![allow(dead_code)]
8
9mod sys;
10use sys::*;
11
12use std::ffi::CString;
13use std::fmt;
14use std::os::raw::{c_char, c_int};
15use std::os::unix::io::AsRawFd;
16use std::pin::Pin;
17use std::ptr;
18use std::sync::{Arc, Mutex};
19
20/// Occlum-PAL error codes.
21#[derive(Debug, Clone, Copy, Eq, PartialEq)]
22pub enum Error {
23    VersionError,
24    InitError,
25    CreateError,
26    ExecError,
27    ArgumentsError,
28    CStringError,
29}
30
31impl fmt::Display for Error {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        match self {
34            Error::VersionError => write!(f, "PAL API version mismatch"),
35            Error::InitError => write!(f, "Initialization error"),
36            Error::CreateError => write!(f, "Process creation error"),
37            Error::ExecError => write!(f, "Process execution error"),
38            Error::ArgumentsError => write!(f, "Arguments list error"),
39            Error::CStringError => write!(f, "String contains a bare \\0 character"),
40        }
41    }
42}
43
44impl std::error::Error for Error {}
45
46/// Log level.
47#[derive(Debug, Copy, Clone, PartialEq, Eq)]
48pub enum LogLevel {
49    Off,
50    Error,
51    Warn,
52    Info,
53    Trace,
54}
55
56impl LogLevel {
57    fn as_bytes(&self) -> &'static [u8] {
58        match *self {
59            LogLevel::Off => b"off",
60            LogLevel::Error => b"error",
61            LogLevel::Warn => b"warn",
62            LogLevel::Info => b"info",
63            LogLevel::Trace => b"trace",
64        }
65    }
66}
67
68/// Enclave configuration.
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct Config {
71    instance_dir: CString,
72    log_level: LogLevel,
73}
74
75impl Config {
76    /// Create a new enclave configuration.
77    /// `instance_dir` specifies the path of an Occlum instance directory, which is usually created with the
78    /// `occlum new` command. The default value is "."; that is, the current working directory
79    /// is the Occlum instance directory.
80    pub fn new(instance_dir: impl ToString) -> Result<Self, Error> {
81        Ok(Self {
82            instance_dir: CString::new(instance_dir.to_string().as_bytes())
83                .map_err(|_| Error::CStringError)?,
84            log_level: LogLevel::Off,
85        })
86    }
87}
88
89impl Default for Config {
90    fn default() -> Self {
91        Config {
92            instance_dir: CString::new(b".".to_vec()).unwrap(),
93            log_level: LogLevel::Off,
94        }
95    }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub struct Stdio {
100    stdin: c_int,
101    stdout: c_int,
102    stderr: c_int,
103}
104
105impl Default for Stdio {
106    fn default() -> Self {
107        Self {
108            stdin: 0,
109            stdout: 1,
110            stderr: 2,
111        }
112    }
113}
114
115impl Stdio {
116    /// Redirect the process standard descriptors to existing descriptors.
117    pub fn new(stdin: impl AsRawFd, stdout: impl AsRawFd, stderr: impl AsRawFd) -> Self {
118        Self {
119            stdin: stdin.as_raw_fd() as _,
120            stdout: stdout.as_raw_fd() as _,
121            stderr: stderr.as_raw_fd() as _,
122        }
123    }
124
125    fn to_api(&self) -> Pin<Box<occlum_stdio_fds>> {
126        Box::pin(occlum_stdio_fds {
127            stdin_fd: self.stdin,
128            stdout_fd: self.stdout,
129            stderr_fd: self.stderr,
130        })
131    }
132}
133
134/// A process running in an enclave.
135#[derive(Debug, PartialEq, Eq)]
136pub struct ProcessBuilder {
137    path: CString,
138    argv: Vec<CString>,
139    env: Vec<CString>,
140    pid: c_int,
141    stdio: Option<Stdio>,
142}
143
144#[derive(Debug)]
145pub struct CStringsVec {
146    cstrings: Vec<CString>,
147    ptrs: Vec<*const c_char>,
148}
149
150impl CStringsVec {
151    fn new(cstrings: Vec<CString>) -> Result<Self, Error> {
152        let mut ptrs = Vec::with_capacity(cstrings.len() + 1);
153        for cstring in &cstrings {
154            ptrs.push(cstring.as_ptr());
155        }
156        ptrs.push(ptr::null());
157        Ok(Self { cstrings, ptrs })
158    }
159
160    fn as_mut_ptr(&mut self) -> *mut *const c_char {
161        self.ptrs.as_mut_ptr()
162    }
163}
164
165#[derive(Debug)]
166struct ProcessApiConcrete {
167    path: CString,
168    argv_cstrings_vec: CStringsVec,
169    env_cstrings_vec: CStringsVec,
170    stdio_api: Pin<Box<occlum_stdio_fds>>,
171    pid: Pin<Box<c_int>>,
172}
173
174#[derive(Debug)]
175struct ProcessApi {
176    concrete: ProcessApiConcrete,
177    ptrs: Pin<Box<occlum_pal_create_process_args>>,
178}
179
180/// A process to be run in an enclave.
181#[derive(Debug)]
182pub struct Process {
183    enclave: Arc<Mutex<Enclave>>,
184    process_api: ProcessApi,
185}
186
187impl Process {
188    /// Return the process identifier.
189    pub fn process_id(&self) -> ProcessId {
190        ProcessId {
191            pid: unsafe { *self.process_api.ptrs.pid },
192        }
193    }
194
195    /// Execute the process inside the Occlum enclave
196    pub fn exec(&self) -> Result<ExitCode, Error> {
197        let mut exit_code: c_int = -1;
198        let mut exec_args = Box::pin(occlum_pal_exec_args {
199            pid: self.process_id().pid,
200            exit_value: &mut exit_code,
201        });
202        let exec_result = unsafe { occlum_pal_exec(&mut *exec_args) };
203        if exec_result == 0 {
204            Ok(exit_code)
205        } else {
206            Err(Error::CreateError)
207        }
208    }
209}
210
211/// A process identifier, that can be used to send signals to the process.
212#[derive(Debug, Clone, PartialEq, Eq, Hash)]
213pub struct ProcessId {
214    pid: c_int,
215}
216
217impl ProcessId {
218    /// Kill the process.
219    pub fn kill(self) -> Result<(), Error> {
220        let result = unsafe { occlum_pal_kill(self.pid, 9) };
221        if result == 0 {
222            Ok(())
223        } else {
224            Err(Error::ExecError)
225        }
226    }
227
228    /// Send SIGTERM to the process.
229    pub fn terminate(self) -> Result<(), Error> {
230        let result = unsafe { occlum_pal_kill(self.pid, 16) };
231        if result == 0 {
232            Ok(())
233        } else {
234            Err(Error::ExecError)
235        }
236    }
237}
238
239pub type ExitCode = c_int;
240
241impl ProcessBuilder {
242    /// Create a new process.
243    /// `path` is the path of the executable to run.
244    /// `argv` is the list of arguments to pass to the executable. If not `None`, the first argument must be the application name, as seen by the application itself.
245    /// `env` is the list of environment variables to pass to the application.
246    /// `stdio` is the standard I/O descriptors to use for the process.
247    pub fn new(
248        path: impl ToString,
249        argv: Option<&[String]>,
250        env: Option<&[String]>,
251        stdio: Option<Stdio>,
252    ) -> Result<Self, Error> where {
253        let path = CString::new(path.to_string().as_bytes()).map_err(|_| Error::CStringError)?;
254        let argv = match argv {
255            None => vec![path.clone()],
256            Some(argv) => {
257                let mut y = vec![];
258                for x in argv {
259                    y.push(CString::new(x.to_string().as_bytes()).map_err(|_| Error::CStringError)?)
260                }
261                y
262            }
263        };
264        if argv.is_empty() {
265            return Err(Error::ArgumentsError);
266        }
267
268        let env = env.unwrap_or(&[]);
269        let env = {
270            let mut y = vec![];
271            for x in env {
272                y.push(CString::new(x.to_string().as_bytes()).map_err(|_| Error::CStringError)?)
273            }
274            y
275        };
276
277        Ok(Self {
278            path,
279            argv,
280            env,
281            pid: -1,
282            stdio,
283        })
284    }
285
286    /// Create a new process to be run in the enclave.
287    pub fn build(self, enclave: &Arc<Mutex<Enclave>>) -> Result<Process, Error> {
288        let stdio_api = match self.stdio {
289            None => Stdio::default().to_api(),
290            Some(stdio) => stdio.to_api(),
291        };
292        let mut process_api_concrete = ProcessApiConcrete {
293            path: self.path,
294            argv_cstrings_vec: CStringsVec::new(self.argv)?,
295            env_cstrings_vec: CStringsVec::new(self.env)?,
296            stdio_api,
297            pid: Box::pin(-1),
298        };
299        let mut process_api_ptrs = Box::pin(occlum_pal_create_process_args {
300            path: process_api_concrete.path.as_ptr(),
301            argv: process_api_concrete.argv_cstrings_vec.as_mut_ptr(),
302            env: process_api_concrete.env_cstrings_vec.as_mut_ptr(),
303            stdio: &*process_api_concrete.stdio_api,
304            pid: &mut *process_api_concrete.pid,
305        });
306        if unsafe { occlum_pal_create_process(&mut *process_api_ptrs) } != 0 {
307            return Err(Error::CreateError);
308        }
309        Ok(Process {
310            enclave: enclave.clone(),
311            process_api: ProcessApi {
312                concrete: process_api_concrete,
313                ptrs: process_api_ptrs,
314            },
315        })
316    }
317}
318
319#[derive(Debug)]
320struct ConfigApiConcrete {
321    instance_dir: CString,
322    log_level: CString,
323}
324
325#[derive(Debug)]
326struct ConfigApi {
327    concrete: ConfigApiConcrete,
328    ptrs: Pin<Box<occlum_pal_attr>>,
329}
330
331/// An Occlum SGX enclave.
332#[derive(Debug)]
333pub struct Enclave {
334    config: Config,
335    config_api: ConfigApi,
336}
337
338impl Enclave {
339    /// Create a new SGX enclave with the given configuration.
340    pub fn new(config: Config) -> Result<Arc<Mutex<Self>>, Error> {
341        if (unsafe { occlum_pal_get_version() } <= 0) {
342            return Err(Error::VersionError);
343        }
344
345        let config_api_concrete = ConfigApiConcrete {
346            instance_dir: CString::new(config.instance_dir.as_bytes())
347                .map_err(|_| Error::CStringError)?,
348            log_level: CString::new(config.log_level.as_bytes())
349                .map_err(|_| Error::CStringError)?,
350        };
351        let config_api_ptrs = Box::pin(occlum_pal_attr {
352            instance_dir: config_api_concrete.instance_dir.as_ptr() as *const c_char,
353            log_level: config_api_concrete.log_level.as_bytes().as_ptr() as *const c_char,
354        });
355        if unsafe { occlum_pal_init(&*config_api_ptrs) } != 0 {
356            return Err(Error::InitError);
357        }
358        Ok(Arc::new(Mutex::new(Enclave {
359            config,
360            config_api: ConfigApi {
361                concrete: config_api_concrete,
362                ptrs: config_api_ptrs,
363            },
364        })))
365    }
366
367    fn destroy(&mut self) {
368        unsafe {
369            occlum_pal_destroy();
370        }
371    }
372}
373
374impl Drop for Enclave {
375    fn drop(&mut self) {
376        self.destroy();
377    }
378}