Skip to content

Commit

Permalink
[PAL/Linux-SGX] Add new implementation for clock emulation without re…
Browse files Browse the repository at this point in the history
…lying on cpuid and steady clock frequency heuristics (fast-clock)

Signed-off-by: Jonathan Shamir <[email protected]>
  • Loading branch information
jonathan-sha committed Jul 23, 2024
1 parent afb8a35 commit f163c04
Show file tree
Hide file tree
Showing 12 changed files with 504 additions and 249 deletions.
2 changes: 0 additions & 2 deletions common/include/arch/x86_64/cpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ enum extended_state_sub_leaf {
#define PROC_FREQ_LEAF 0x16
#define AMX_TILE_INFO_LEAF 0x1D
#define AMX_TMUL_INFO_LEAF 0x1E
#define HYPERVISOR_INFO_LEAF 0x40000000
#define HYPERVISOR_VMWARE_TIME_LEAF 0x40000010
#define MAX_INPUT_EXT_VALUE_LEAF 0x80000000
#define EXT_SIGNATURE_AND_FEATURES_LEAF 0x80000001
#define CPU_BRAND_LEAF 0x80000002
Expand Down
82 changes: 60 additions & 22 deletions pal/src/host/linux-sgx/enclave_ocalls.c
Original file line number Diff line number Diff line change
Expand Up @@ -1766,47 +1766,85 @@ int ocall_shutdown(int sockfd, int how) {
return retval;
}

int ocall_gettime(uint64_t* microsec_ptr) {
int ocall_gettime(uint64_t* microsec_ptr, uint64_t* tsc_ptr) {
int retval = 0;
struct ocall_gettime* ocall_gettime_args;
struct ocall_gettime* ocall_gettime_args = NULL;

void* old_ustack = sgx_prepare_ustack();
ocall_gettime_args = sgx_alloc_on_ustack_aligned(sizeof(*ocall_gettime_args),
alignof(*ocall_gettime_args));
if (!ocall_gettime_args) {
sgx_reset_ustack(old_ustack);
return -EPERM;
retval = -EPERM;
goto out;
}

/* Last seen time value. This guards against time rewinding. */
static uint64_t last_microsec = 0;
uint64_t last_microsec_before_ocall = __atomic_load_n(&last_microsec, __ATOMIC_ACQUIRE);
struct gettime_guard
{
spinlock_t lock;
uint64_t microsec;
uint64_t tsc;
};
static struct gettime_guard last_value = {
.lock = INIT_SPINLOCK_UNLOCKED,
.microsec = 0,
.tsc = 0,
};

spinlock_lock(&last_value.lock);
uint64_t last_microsec_before_ocall = last_value.microsec;
uint64_t last_tsc_before_ocall = last_value.tsc;
spinlock_unlock(&last_value.lock);

uint64_t tsc_before_ocall = 0;
uint64_t tsc_after_ocall = 0;
do {
tsc_before_ocall = get_tsc();
retval = sgx_exitless_ocall(OCALL_GETTIME, ocall_gettime_args);
tsc_after_ocall = get_tsc();
} while (retval == -EINTR);

if (retval < 0 && retval != -EINVAL && retval != -EPERM) {
retval = -EPERM;
}
if (retval != 0) {
goto out;
}

if (!retval) {
uint64_t microsec = COPY_UNTRUSTED_VALUE(&ocall_gettime_args->microsec);
if (microsec < last_microsec_before_ocall) {
/* Probably a malicious host. */
log_error("OCALL_GETTIME returned time value smaller than in the previous call");
_PalProcessExit(1);
}
/* Update `last_microsec`. */
uint64_t expected_microsec = last_microsec_before_ocall;
while (expected_microsec < microsec) {
if (__atomic_compare_exchange_n(&last_microsec, &expected_microsec, microsec,
/*weak=*/true, __ATOMIC_RELEASE, __ATOMIC_ACQUIRE)) {
break;
}
}
*microsec_ptr = MAX(microsec, expected_microsec);
/* detect malicious host - time and tsc must monotonically increase */
uint64_t new_microsec = COPY_UNTRUSTED_VALUE(&ocall_gettime_args->microsec);
uint64_t new_tsc = COPY_UNTRUSTED_VALUE(&ocall_gettime_args->tsc);
if (new_microsec < last_microsec_before_ocall) {
log_error("OCALL_GETTIME returned time value smaller than in the previous call");
_PalProcessExit(1);
}
if (new_tsc <= last_tsc_before_ocall) {
log_error("OCALL_GETTIME returned TSC value smaller than in previous call");
_PalProcessExit(1);
}
if (!((tsc_before_ocall < new_tsc) && (new_tsc < tsc_after_ocall))) {
log_error("OCALL_GETTIME returned TSC value inconsistent with values taken within the enclave");
_PalProcessExit(1);
}

/* Update `last_value` guard. */
spinlock_lock(&last_value.lock);
if (last_value.tsc < new_tsc) {
last_value.microsec = new_microsec;
last_value.tsc = new_tsc;
} else {
/* there was a more recent ocall */
new_microsec = last_value.microsec;
new_tsc = last_value.tsc;
}
spinlock_unlock(&last_value.lock);

*microsec_ptr = new_microsec;
if (tsc_ptr != NULL) {
*tsc_ptr = new_tsc;
}

out:
sgx_reset_ustack(old_ustack);
return retval;
}
Expand Down
2 changes: 1 addition & 1 deletion pal/src/host/linux-sgx/enclave_ocalls.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ int ocall_create_process(size_t nargs, const char** args, uintptr_t (*reserved_m

int ocall_futex(uint32_t* uaddr, int op, int val, uint64_t* timeout_us);

int ocall_gettime(uint64_t* microsec);
int ocall_gettime(uint64_t* microsec, uint64_t* tsc);

void ocall_sched_yield(void);

Expand Down
4 changes: 3 additions & 1 deletion pal/src/host/linux-sgx/host_ocalls.c
Original file line number Diff line number Diff line change
Expand Up @@ -603,8 +603,10 @@ static long sgx_ocall_shutdown(void* args) {
static long sgx_ocall_gettime(void* args) {
struct ocall_gettime* ocall_gettime_args = args;
struct timeval tv;
uint64_t tsc = get_tsc();
DO_SYSCALL(gettimeofday, &tv, NULL);
ocall_gettime_args->microsec = tv.tv_sec * (uint64_t)1000000 + tv.tv_usec;
ocall_gettime_args->microsec = tv.tv_sec * (uint64_t)1000000UL + tv.tv_usec;
ocall_gettime_args->tsc = tsc;
return 0;
}

Expand Down
1 change: 1 addition & 0 deletions pal/src/host/linux-sgx/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ libpal_sgx = shared_library('pal',
'pal_sockets.c',
'pal_streams.c',
'pal_threading.c',
'utils/fast_clock.c',
pal_sgx_asm_offsets_h,
pal_common_sources,
pal_linux_common_sources_enclave,
Expand Down
9 changes: 6 additions & 3 deletions pal/src/host/linux-sgx/pal_exception.c
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,14 @@ static void save_pal_context(PAL_CONTEXT* ctx, sgx_cpu_context_t* uc,
}
}

#include "utils/fast_clock.h"

int g_atomic_is_rdtsc_emulated = 0;
static void emulate_rdtsc_and_print_warning(sgx_cpu_context_t* uc) {
if (FIRST_TIME()) {
/* if we end up emulating RDTSC/RDTSCP instruction, we cannot use invariant TSC */
extern uint64_t g_tsc_hz;
g_tsc_hz = 0;
/* if we end up emulating RDTSC/RDTSCP instruction, we cannot use TSC-based clock emulation */
__atomic_store_n(&g_atomic_is_rdtsc_emulated, 1, __ATOMIC_SEQ_CST);
fast_clock_disable(&g_fast_clock);
log_warning("all RDTSC/RDTSCP instructions are emulated (imprecisely) via gettime() "
"syscall.");
}
Expand Down
2 changes: 0 additions & 2 deletions pal/src/host/linux-sgx/pal_linux.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,6 @@ void _PalExceptionHandler(uint32_t trusted_exit_info_,
uint32_t untrusted_external_event, sgx_cpu_context_t* uc,
PAL_XREGS_STATE* xregs_state, sgx_arch_exinfo_t* exinfo);

void init_tsc(void);

int init_cpuid(void);

int init_enclave(void);
Expand Down
32 changes: 18 additions & 14 deletions pal/src/host/linux-sgx/pal_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "pal_topology.h"
#include "toml.h"
#include "toml_utils.h"
#include "utils/fast_clock.h"

struct pal_linuxsgx_state g_pal_linuxsgx_state;

Expand Down Expand Up @@ -407,7 +408,6 @@ static int import_and_init_extra_runtime_domain_names(struct pal_dns_host_conf*
extern void* g_enclave_base;
extern void* g_enclave_top;
extern bool g_allowed_files_warn;
extern uint64_t g_tsc_hz;
extern size_t g_unused_tcs_pages_num;

static int print_warnings_on_insecure_configs(PAL_HANDLE parent_process) {
Expand Down Expand Up @@ -552,11 +552,17 @@ static int print_warnings_on_insecure_configs(PAL_HANDLE parent_process) {
return ret;
}

static void print_warning_on_invariant_tsc(PAL_HANDLE parent_process) {
if (!parent_process && !g_tsc_hz) {
/* Warn only in the first process. */
log_warning("Could not set up Invariant TSC (CPU is too old or you run on a VM that does "
"not expose corresponding CPUID leaves). This degrades performance.");
static void print_warnings_on_disabled_clock_emulation(PAL_HANDLE parent_process) {
if (parent_process) {
return; /* Warn only in the first process */
}

/* We call get_tsc() early in pal_linux_main -
* if rdtsc opcode is emulated, the error handler disables fast-clock
*/
if (!fast_clock_is_enabled(&g_fast_clock)) {
log_warning("Could not enable fast clock emulation (CPU is too old or VM does "
"not support TSC within SGX enclave). This degrades performance.");
}
}

Expand All @@ -581,8 +587,7 @@ static void post_callback(void) {
ocall_exit(1, /*is_exitgroup=*/true);
}

print_warning_on_invariant_tsc(g_pal_common_state.parent_process);

print_warnings_on_disabled_clock_emulation(g_pal_common_state.parent_process);
print_warnings_on_invalid_dns_host_conf(g_pal_common_state.parent_process);
}

Expand Down Expand Up @@ -725,12 +730,11 @@ noreturn void pal_linux_main(void* uptr_libpal_uri, size_t libpal_uri_len, void*

SET_ENCLAVE_TCB(ready_for_exceptions, 1UL);

/* initialize "Invariant TSC" HW feature for fast and accurate gettime and immediately probe
* RDTSC instruction inside SGX enclave (via dummy get_tsc) -- it is possible that
* the CPU supports invariant TSC but doesn't support executing RDTSC inside SGX enclave, in
* this case the SIGILL exception is generated and leads to emulate_rdtsc_and_print_warning()
* which unsets invariant TSC, and we end up falling back to the slower ocall_gettime() */
init_tsc();
/* We implement a "fast-path" clock that is emulated internally using x86 RDTSC instruction.
* It is possible that the CPU does not support the RDTSC instruction within SGX enclave,
* in this case the SIGILL exception is generated and leads to emulate_rdtsc_and_print_warning()
* which disables the TSC based clock, and we end up falling back to the slower ocall_gettime()
*/
(void)get_tsc(); /* must be after `ready_for_exceptions=1` since it may generate SIGILL */

ret = init_cpuid();
Expand Down
Loading

0 comments on commit f163c04

Please sign in to comment.