Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pal sgx fast clock gettimeofday #1834

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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