Add the support for setting log level at runtime

Now one can specify the log level of the LibOS by setting `OCCLUM_LOG_LEVEL`
environment variable. The possible values are "off", "error", "warn",
"info", and "trace".

However, for the sake of security, the log level of a release enclave
(DisableDebug = 1 in Enclave.xml) is always "off" (i.e., no log) regardless of
the log level specified by the untrusted environment.
This commit is contained in:
Tate, Hongliang Tian 2020-03-24 14:59:36 +00:00
parent 6d7cf7b9f6
commit 9713e74ed9
15 changed files with 143 additions and 55 deletions

@ -10,13 +10,20 @@ enclave {
include "occlum_edl_types.h" include "occlum_edl_types.h"
trusted { trusted {
/*
* Initialize the LibOS according to the specified attributes.
*
* @retval On success, return 0; otherwise, return -1.
*/
public int occlum_ecall_init([in, string] const char* log_level);
/* /*
* Create a new LibOS process to do the task specified by the given * Create a new LibOS process to do the task specified by the given
* arguments. * arguments.
* *
* @retval On success, return the thread ID of the * @retval On success, return the thread ID of the
* newly-created process (pid == tid for a new process). On error, * newly-created process (pid == tid for a new process). On error,
* return -1. The user can check errno for the concrete error type. * return -1.
*/ */
public int occlum_ecall_new_process( public int occlum_ecall_new_process(
[in, string] const char* executable_path, [in, string] const char* executable_path,
@ -28,14 +35,9 @@ enclave {
* This API is synchronous: it returns until the LibOS thread exits. * This API is synchronous: it returns until the LibOS thread exits.
* *
* @retval On success, return the exit status of the thread. On error, * @retval On success, return the exit status of the thread. On error,
* return -1. The user can check errno for the concrete error type. * return -1.
*/ */
public int occlum_ecall_exec_thread(int libos_tid, int host_tid); public int occlum_ecall_exec_thread(int libos_tid, int host_tid);
/*
* A do-nothing ECall for debug purpose only.
*/
public void occlum_ecall_nop(void);
}; };
untrusted { untrusted {

@ -5,13 +5,51 @@ use std::ffi::{CStr, CString, OsString};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Once; use std::sync::Once;
use util::log::LevelFilter;
use util::mem_util::from_untrusted::*; use util::mem_util::from_untrusted::*;
use util::sgx::allow_debug as sgx_allow_debug;
const ENCLAVE_PATH: &'static str = ".occlum/build/lib/libocclum-libos.signed.so"; const ENCLAVE_PATH: &'static str = ".occlum/build/lib/libocclum-libos.signed.so";
lazy_static! { lazy_static! {
static ref INIT_ONCE: Once = Once::new(); static ref INIT_ONCE: Once = Once::new();
static ref ALLOW_RUN: AtomicBool = AtomicBool::new(false); static ref HAS_INIT: AtomicBool = AtomicBool::new(false);
}
#[no_mangle]
pub extern "C" fn occlum_ecall_init(log_level: *const c_char) -> i32 {
if HAS_INIT.load(Ordering::SeqCst) == true {
return EXIT_STATUS_INTERNAL_ERROR;
}
let log_level = {
let input_log_level = match parse_log_level(log_level) {
Err(e) => {
eprintln!("invalid log level: {}", e.backtrace());
return EXIT_STATUS_INTERNAL_ERROR;
}
Ok(log_level) => log_level,
};
// Use the input log level if and only if the enclave allows debug
if sgx_allow_debug() {
input_log_level
} else {
LevelFilter::Off
}
};
INIT_ONCE.call_once(|| {
// Init the log infrastructure first so that log messages will be printed afterwards
util::log::init(log_level);
// Init MPX for SFI
util::mpx_util::mpx_enable();
// Register exception handlers (support cpuid & rdtsc for now)
register_exception_handlers();
HAS_INIT.store(true, Ordering::SeqCst);
});
0
} }
#[no_mangle] #[no_mangle]
@ -19,25 +57,9 @@ pub extern "C" fn occlum_ecall_new_process(
path_buf: *const c_char, path_buf: *const c_char,
argv: *const *const c_char, argv: *const *const c_char,
) -> i32 { ) -> i32 {
INIT_ONCE.call_once(|| { if HAS_INIT.load(Ordering::SeqCst) == false {
// Init the log infrastructure first so that log messages will be printed afterwards return EXIT_STATUS_INTERNAL_ERROR;
use util::log::LevelFilter; }
let log_level = match option_env!("LIBOS_LOG") {
Some("error") => LevelFilter::Error,
Some("warn") => LevelFilter::Warn,
Some("info") => LevelFilter::Info,
Some("debug") => LevelFilter::Debug,
Some("trace") => LevelFilter::Trace,
_ => LevelFilter::Error, // errors are printed be default
};
util::log::init(log_level);
// Init MPX for SFI
util::mpx_util::mpx_enable();
// Register exception handlers (support cpuid & rdtsc for now)
register_exception_handlers();
ALLOW_RUN.store(true, Ordering::SeqCst);
});
let (path, args) = match parse_arguments(path_buf, argv) { let (path, args) = match parse_arguments(path_buf, argv) {
Ok(path_and_args) => path_and_args, Ok(path_and_args) => path_and_args,
@ -61,7 +83,7 @@ pub extern "C" fn occlum_ecall_new_process(
#[no_mangle] #[no_mangle]
pub extern "C" fn occlum_ecall_exec_thread(libos_pid: i32, host_tid: i32) -> i32 { pub extern "C" fn occlum_ecall_exec_thread(libos_pid: i32, host_tid: i32) -> i32 {
if ALLOW_RUN.load(Ordering::SeqCst) == false { if HAS_INIT.load(Ordering::SeqCst) == false {
return EXIT_STATUS_INTERNAL_ERROR; return EXIT_STATUS_INTERNAL_ERROR;
} }
@ -80,14 +102,36 @@ pub extern "C" fn occlum_ecall_exec_thread(libos_pid: i32, host_tid: i32) -> i32
.unwrap_or(EXIT_STATUS_INTERNAL_ERROR) .unwrap_or(EXIT_STATUS_INTERNAL_ERROR)
} }
#[no_mangle]
pub extern "C" fn occlum_ecall_nop() {}
// Use -128 as a special value to indicate internal error from libos, not from // Use -128 as a special value to indicate internal error from libos, not from
// user programs. The LibOS ensures that an user program can only return a // user programs. The LibOS ensures that an user program can only return a
// value between 0 and 255 (inclusive). // value between 0 and 255 (inclusive).
const EXIT_STATUS_INTERNAL_ERROR: i32 = -128; const EXIT_STATUS_INTERNAL_ERROR: i32 = -128;
fn parse_log_level(level_chars: *const c_char) -> Result<LevelFilter> {
const DEFAULT_LEVEL: LevelFilter = LevelFilter::Off;
if level_chars.is_null() {
return Ok(DEFAULT_LEVEL);
}
let level_string = {
let level_cstring = clone_cstring_safely(level_chars)?;
level_cstring
.into_string()
.map_err(|e| errno!(EINVAL, "log_level contains valid utf-8 data"))?
.to_lowercase()
};
Ok(match level_string.as_str() {
"off" => LevelFilter::Off,
"error" => LevelFilter::Error,
"warn" => LevelFilter::Warn,
"info" => LevelFilter::Info,
"debug" => LevelFilter::Debug,
"trace" => LevelFilter::Trace,
_ => DEFAULT_LEVEL, // Default
})
}
fn parse_arguments( fn parse_arguments(
path_ptr: *const c_char, path_ptr: *const c_char,
argv: *const *const c_char, argv: *const *const c_char,

@ -2,11 +2,10 @@
use super::*; use super::*;
mod attestation;
mod consts; mod consts;
use self::attestation::*;
use self::consts::*; use self::consts::*;
use util::sgx::*;
#[derive(Debug)] #[derive(Debug)]
pub struct DevSgx; pub struct DevSgx;

@ -96,7 +96,9 @@ impl Log for SimpleLogger {
} }
} }
fn flush(&self) { fn flush(&self) {
//unsafe { occlum_ocall_flush_log(); } unsafe {
occlum_ocall_flush_log();
}
} }
} }

@ -93,6 +93,9 @@ pub mod from_untrusted {
// TODO: strict check! // TODO: strict check!
pub fn clone_cstring_safely(out_ptr: *const c_char) -> Result<CString> { pub fn clone_cstring_safely(out_ptr: *const c_char) -> Result<CString> {
check_ptr(out_ptr)?; check_ptr(out_ptr)?;
if out_ptr.is_null() {
return_errno!(EINVAL, "null ptr");
}
// TODO: using from_ptr directly is not safe // TODO: using from_ptr directly is not safe
let cstr = unsafe { CStr::from_ptr(out_ptr) }; let cstr = unsafe { CStr::from_ptr(out_ptr) };
let cstring = CString::from(cstr); let cstring = CString::from(cstr);

@ -4,3 +4,4 @@ pub mod log;
pub mod mem_util; pub mod mem_util;
pub mod mpx_util; pub mod mpx_util;
pub mod ring_buf; pub mod ring_buf;
pub mod sgx;

@ -1,4 +1,4 @@
//! SGX attestation. //! SGX utility.
use super::*; use super::*;
@ -17,3 +17,8 @@ pub use sgx_types::{
pub use self::sgx_attestation_agent::SgxAttestationAgent; pub use self::sgx_attestation_agent::SgxAttestationAgent;
pub use self::sgx_quote::SgxQuote; pub use self::sgx_quote::SgxQuote;
pub use self::sgx_report::{create_report, get_self_target, verify_report}; pub use self::sgx_report::{create_report, get_self_target, verify_report};
pub fn allow_debug() -> bool {
let self_report = create_report(None, None).expect("create a self report should never fail");
(self_report.body.attributes.flags & SGX_FLAGS_DEBUG) == SGX_FLAGS_DEBUG
}

@ -1,8 +1,8 @@
#ifndef __OCCLUM_EDL_TYPES__ #ifndef __OCCLUM_EDL_TYPES__
#define __OCCLUM_EDL_TYPES__ #define __OCCLUM_EDL_TYPES__
#include <time.h> // import struct timespec #include <time.h> // import struct timespec
#include <sys/time.h> // import struct timeval #include <sys/time.h> // import struct timeval
#include <sys/uio.h> // import struct iovec #include <sys/uio.h> // import struct iovec
#endif /* __OCCLUM_EDL_TYPES__ */ #endif /* __OCCLUM_EDL_TYPES__ */

@ -5,20 +5,42 @@
extern "C" { extern "C" {
#endif #endif
/*
* Occlum PAL attributes
*/
typedef struct {
// Occlum instance dir.
//
// Specifies the path of an Occlum instance directory. Usually, this
// directory is initialized by executing "occlum init" command, which
// creates a hidden directory named ".occlum/". This ".occlum/" is an
// Occlum instance directory. The name of the directory is not necesarrily
// ".occlum"; it can be renamed to an arbitrary name.
//
// Mandatory field. Must not be NULL.
const char* instance_dir;
// Log level.
//
// Specifies the log level of Occlum LibOS. Valid values: "off", "error",
// "warn", "info", and "trace". Case insensitive.
//
// Optional field. If NULL, the LibOS will treat it as "off".
const char* log_level;
} occlum_pal_attr_t;
#define OCCLUM_PAL_ATTR_INITVAL { \
.instance_dir = NULL, \
.log_level = NULL \
}
/* /*
* @brief Initialize an Occlum enclave * @brief Initialize an Occlum enclave
* *
* @param instance_dir Specifies the path of an Occlum instance directory. * @param attr Mandatory input. Attributes for Occlum.
* Usually, this directory is initialized by executing
* "occlum init" command, which creates a hidden
* directory named ".occlum/". This ".occlum/" is an
* Occlum instance directory. The name of the directory is
* not necesarrily ".occlum"; it can be renamed to an
* arbitrary name.
* *
* @retval If 0, then success; otherwise, check errno for the exact error type. * @retval If 0, then success; otherwise, check errno for the exact error type.
*/ */
int occlum_pal_init(const char* instance_dir); int occlum_pal_init(occlum_pal_attr_t* attr);
/* /*
* @brief Execute a command inside the Occlum enclave * @brief Execute a command inside the Occlum enclave

@ -5,10 +5,14 @@
#include "pal_log.h" #include "pal_log.h"
#include "pal_syscall.h" #include "pal_syscall.h"
int occlum_pal_init(const char* instance_dir) { int occlum_pal_init(occlum_pal_attr_t* attr) {
errno = 0; errno = 0;
if (instance_dir == NULL) { if (attr == NULL) {
errno = EINVAL;
return -1;
}
if (attr->instance_dir == NULL) {
errno = EINVAL; errno = EINVAL;
return -1; return -1;
} }
@ -20,7 +24,7 @@ int occlum_pal_init(const char* instance_dir) {
return -1; return -1;
} }
if (pal_init_enclave(instance_dir) < 0) { if (pal_init_enclave(attr->instance_dir) < 0) {
return -1; return -1;
} }
@ -29,13 +33,17 @@ int occlum_pal_init(const char* instance_dir) {
// 2) Initialize the global data structures inside the enclave (which is // 2) Initialize the global data structures inside the enclave (which is
// automatically done by Intel SGX SDK). // automatically done by Intel SGX SDK).
eid = pal_get_enclave_id(); eid = pal_get_enclave_id();
sgx_status_t ecall_status = occlum_ecall_nop(eid); int ret;
sgx_status_t ecall_status = occlum_ecall_init(eid, &ret, attr->log_level);
if (ecall_status != SGX_SUCCESS) { if (ecall_status != SGX_SUCCESS) {
const char* sgx_err = pal_get_sgx_error_msg(ecall_status); const char* sgx_err = pal_get_sgx_error_msg(ecall_status);
PAL_ERROR("Failed to do ECall: %s", sgx_err); PAL_ERROR("Failed to do ECall: %s", sgx_err);
return -1; return -1;
} }
if (ret < 0) {
errno = EINVAL;
return -1;
}
return 0; return 0;
} }

@ -24,8 +24,10 @@ int main(int argc, char* argv[]) {
const char** cmd_args = (const char**) &argv[2]; const char** cmd_args = (const char**) &argv[2];
// Init Occlum PAL // Init Occlum PAL
const char* instance_dir = get_instance_dir(); occlum_pal_attr_t attr = OCCLUM_PAL_ATTR_INITVAL;
if (occlum_pal_init(instance_dir) < 0) { attr.instance_dir = get_instance_dir();
attr.log_level = getenv("OCCLUM_LOG_LEVEL");
if (occlum_pal_init(&attr) < 0) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }

@ -8,14 +8,14 @@
// Helper macros // Helper macros
// ============================================================================ // ============================================================================
#define NTHREADS (4) #define NTHREADS (3)
#define STACK_SIZE (8 * 1024) #define STACK_SIZE (8 * 1024)
// ============================================================================ // ============================================================================
// The test case of concurrent counter // The test case of concurrent counter
// ============================================================================ // ============================================================================
#define LOCAL_COUNT (100000UL) #define LOCAL_COUNT (1000UL)
#define EXPECTED_GLOBAL_COUNT (LOCAL_COUNT * NTHREADS) #define EXPECTED_GLOBAL_COUNT (LOCAL_COUNT * NTHREADS)
struct thread_arg { struct thread_arg {