Skip to content

Commit 664de3d

Browse files
committed
[PAL/Linux-SGX] Add new implementation for clock emulation without relying on cpuid and steady clock frequency heuristics (fast-clock)
Signed-off-by: Jonathan Shamir <[email protected]>
1 parent afb8a35 commit 664de3d

File tree

12 files changed

+504
-249
lines changed

12 files changed

+504
-249
lines changed

common/include/arch/x86_64/cpu.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ enum extended_state_sub_leaf {
4545
#define PROC_FREQ_LEAF 0x16
4646
#define AMX_TILE_INFO_LEAF 0x1D
4747
#define AMX_TMUL_INFO_LEAF 0x1E
48-
#define HYPERVISOR_INFO_LEAF 0x40000000
49-
#define HYPERVISOR_VMWARE_TIME_LEAF 0x40000010
5048
#define MAX_INPUT_EXT_VALUE_LEAF 0x80000000
5149
#define EXT_SIGNATURE_AND_FEATURES_LEAF 0x80000001
5250
#define CPU_BRAND_LEAF 0x80000002

pal/src/host/linux-sgx/enclave_ocalls.c

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1766,47 +1766,85 @@ int ocall_shutdown(int sockfd, int how) {
17661766
return retval;
17671767
}
17681768

1769-
int ocall_gettime(uint64_t* microsec_ptr) {
1769+
int ocall_gettime(uint64_t* microsec_ptr, uint64_t* tsc_ptr) {
17701770
int retval = 0;
1771-
struct ocall_gettime* ocall_gettime_args;
1771+
struct ocall_gettime* ocall_gettime_args = NULL;
17721772

17731773
void* old_ustack = sgx_prepare_ustack();
17741774
ocall_gettime_args = sgx_alloc_on_ustack_aligned(sizeof(*ocall_gettime_args),
17751775
alignof(*ocall_gettime_args));
17761776
if (!ocall_gettime_args) {
1777-
sgx_reset_ustack(old_ustack);
1778-
return -EPERM;
1777+
retval = -EPERM;
1778+
goto out;
17791779
}
17801780

17811781
/* Last seen time value. This guards against time rewinding. */
1782-
static uint64_t last_microsec = 0;
1783-
uint64_t last_microsec_before_ocall = __atomic_load_n(&last_microsec, __ATOMIC_ACQUIRE);
1782+
struct gettime_guard
1783+
{
1784+
spinlock_t lock;
1785+
uint64_t microsec;
1786+
uint64_t tsc;
1787+
};
1788+
static struct gettime_guard last_value = {
1789+
.lock = INIT_SPINLOCK_UNLOCKED,
1790+
.microsec = 0,
1791+
.tsc = 0,
1792+
};
1793+
1794+
spinlock_lock(&last_value.lock);
1795+
uint64_t last_microsec_before_ocall = last_value.microsec;
1796+
uint64_t last_tsc_before_ocall = last_value.tsc;
1797+
spinlock_unlock(&last_value.lock);
1798+
1799+
uint64_t tsc_before_ocall = 0;
1800+
uint64_t tsc_after_ocall = 0;
17841801
do {
1802+
tsc_before_ocall = get_tsc();
17851803
retval = sgx_exitless_ocall(OCALL_GETTIME, ocall_gettime_args);
1804+
tsc_after_ocall = get_tsc();
17861805
} while (retval == -EINTR);
17871806

17881807
if (retval < 0 && retval != -EINVAL && retval != -EPERM) {
17891808
retval = -EPERM;
17901809
}
1810+
if (retval != 0) {
1811+
goto out;
1812+
}
17911813

1792-
if (!retval) {
1793-
uint64_t microsec = COPY_UNTRUSTED_VALUE(&ocall_gettime_args->microsec);
1794-
if (microsec < last_microsec_before_ocall) {
1795-
/* Probably a malicious host. */
1796-
log_error("OCALL_GETTIME returned time value smaller than in the previous call");
1797-
_PalProcessExit(1);
1798-
}
1799-
/* Update `last_microsec`. */
1800-
uint64_t expected_microsec = last_microsec_before_ocall;
1801-
while (expected_microsec < microsec) {
1802-
if (__atomic_compare_exchange_n(&last_microsec, &expected_microsec, microsec,
1803-
/*weak=*/true, __ATOMIC_RELEASE, __ATOMIC_ACQUIRE)) {
1804-
break;
1805-
}
1806-
}
1807-
*microsec_ptr = MAX(microsec, expected_microsec);
1814+
/* detect malicious host - time and tsc must monotonically increase */
1815+
uint64_t new_microsec = COPY_UNTRUSTED_VALUE(&ocall_gettime_args->microsec);
1816+
uint64_t new_tsc = COPY_UNTRUSTED_VALUE(&ocall_gettime_args->tsc);
1817+
if (new_microsec < last_microsec_before_ocall) {
1818+
log_error("OCALL_GETTIME returned time value smaller than in the previous call");
1819+
_PalProcessExit(1);
18081820
}
1821+
if (new_tsc <= last_tsc_before_ocall) {
1822+
log_error("OCALL_GETTIME returned TSC value smaller than in previous call");
1823+
_PalProcessExit(1);
1824+
}
1825+
if (!((tsc_before_ocall < new_tsc) && (new_tsc < tsc_after_ocall))) {
1826+
log_error("OCALL_GETTIME returned TSC value inconsistent with values taken within the enclave");
1827+
_PalProcessExit(1);
1828+
}
1829+
1830+
/* Update `last_value` guard. */
1831+
spinlock_lock(&last_value.lock);
1832+
if (last_value.tsc < new_tsc) {
1833+
last_value.microsec = new_microsec;
1834+
last_value.tsc = new_tsc;
1835+
} else {
1836+
/* there was a more recent ocall */
1837+
new_microsec = last_value.microsec;
1838+
new_tsc = last_value.tsc;
1839+
}
1840+
spinlock_unlock(&last_value.lock);
18091841

1842+
*microsec_ptr = new_microsec;
1843+
if (tsc_ptr != NULL) {
1844+
*tsc_ptr = new_tsc;
1845+
}
1846+
1847+
out:
18101848
sgx_reset_ustack(old_ustack);
18111849
return retval;
18121850
}

pal/src/host/linux-sgx/enclave_ocalls.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ int ocall_create_process(size_t nargs, const char** args, uintptr_t (*reserved_m
8989

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

92-
int ocall_gettime(uint64_t* microsec);
92+
int ocall_gettime(uint64_t* microsec, uint64_t* tsc);
9393

9494
void ocall_sched_yield(void);
9595

pal/src/host/linux-sgx/host_ocalls.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -603,8 +603,10 @@ static long sgx_ocall_shutdown(void* args) {
603603
static long sgx_ocall_gettime(void* args) {
604604
struct ocall_gettime* ocall_gettime_args = args;
605605
struct timeval tv;
606+
uint64_t tsc = get_tsc();
606607
DO_SYSCALL(gettimeofday, &tv, NULL);
607-
ocall_gettime_args->microsec = tv.tv_sec * (uint64_t)1000000 + tv.tv_usec;
608+
ocall_gettime_args->microsec = tv.tv_sec * (uint64_t)1000000UL + tv.tv_usec;
609+
ocall_gettime_args->tsc = tsc;
608610
return 0;
609611
}
610612

pal/src/host/linux-sgx/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ libpal_sgx = shared_library('pal',
8686
'pal_sockets.c',
8787
'pal_streams.c',
8888
'pal_threading.c',
89+
'utils/fast_clock.c',
8990
pal_sgx_asm_offsets_h,
9091
pal_common_sources,
9192
pal_linux_common_sources_enclave,

pal/src/host/linux-sgx/pal_exception.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,14 @@ static void save_pal_context(PAL_CONTEXT* ctx, sgx_cpu_context_t* uc,
111111
}
112112
}
113113

114+
#include "utils/fast_clock.h"
115+
116+
int g_atomic_is_rdtsc_emulated = 0;
114117
static void emulate_rdtsc_and_print_warning(sgx_cpu_context_t* uc) {
115118
if (FIRST_TIME()) {
116-
/* if we end up emulating RDTSC/RDTSCP instruction, we cannot use invariant TSC */
117-
extern uint64_t g_tsc_hz;
118-
g_tsc_hz = 0;
119+
/* if we end up emulating RDTSC/RDTSCP instruction, we cannot use TSC-based clock emulation */
120+
__atomic_store_n(&g_atomic_is_rdtsc_emulated, 1, __ATOMIC_SEQ_CST);
121+
fast_clock_disable(&g_fast_clock);
119122
log_warning("all RDTSC/RDTSCP instructions are emulated (imprecisely) via gettime() "
120123
"syscall.");
121124
}

pal/src/host/linux-sgx/pal_linux.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,6 @@ void _PalExceptionHandler(uint32_t trusted_exit_info_,
9999
uint32_t untrusted_external_event, sgx_cpu_context_t* uc,
100100
PAL_XREGS_STATE* xregs_state, sgx_arch_exinfo_t* exinfo);
101101

102-
void init_tsc(void);
103-
104102
int init_cpuid(void);
105103

106104
int init_enclave(void);

pal/src/host/linux-sgx/pal_main.c

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "pal_topology.h"
3232
#include "toml.h"
3333
#include "toml_utils.h"
34+
#include "utils/fast_clock.h"
3435

3536
struct pal_linuxsgx_state g_pal_linuxsgx_state;
3637

@@ -407,7 +408,6 @@ static int import_and_init_extra_runtime_domain_names(struct pal_dns_host_conf*
407408
extern void* g_enclave_base;
408409
extern void* g_enclave_top;
409410
extern bool g_allowed_files_warn;
410-
extern uint64_t g_tsc_hz;
411411
extern size_t g_unused_tcs_pages_num;
412412

413413
static int print_warnings_on_insecure_configs(PAL_HANDLE parent_process) {
@@ -552,11 +552,17 @@ static int print_warnings_on_insecure_configs(PAL_HANDLE parent_process) {
552552
return ret;
553553
}
554554

555-
static void print_warning_on_invariant_tsc(PAL_HANDLE parent_process) {
556-
if (!parent_process && !g_tsc_hz) {
557-
/* Warn only in the first process. */
558-
log_warning("Could not set up Invariant TSC (CPU is too old or you run on a VM that does "
559-
"not expose corresponding CPUID leaves). This degrades performance.");
555+
static void print_warnings_on_disabled_clock_emulation(PAL_HANDLE parent_process) {
556+
if (parent_process) {
557+
return; /* Warn only in the first process */
558+
}
559+
560+
/* We call get_tsc() early in pal_linux_main -
561+
* if rdtsc opcode is emulated, the error handler disables fast-clock
562+
*/
563+
if (!fast_clock_is_enabled(&g_fast_clock)) {
564+
log_warning("Could not enable fast clock emulation (CPU is too old or VM does "
565+
"not support TSC within SGX enclave). This degrades performance.");
560566
}
561567
}
562568

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

584-
print_warning_on_invariant_tsc(g_pal_common_state.parent_process);
585-
590+
print_warnings_on_disabled_clock_emulation(g_pal_common_state.parent_process);
586591
print_warnings_on_invalid_dns_host_conf(g_pal_common_state.parent_process);
587592
}
588593

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

726731
SET_ENCLAVE_TCB(ready_for_exceptions, 1UL);
727732

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

736740
ret = init_cpuid();

0 commit comments

Comments
 (0)