diff --git a/pal/src/host/linux-sgx/enclave_ocalls.c b/pal/src/host/linux-sgx/enclave_ocalls.c index c7a0f67546..a2116a261e 100644 --- a/pal/src/host/linux-sgx/enclave_ocalls.c +++ b/pal/src/host/linux-sgx/enclave_ocalls.c @@ -1768,49 +1768,82 @@ int ocall_shutdown(int sockfd, int how) { 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 = { .microsec = 0, .tsc = 0, }; + if (FIRST_TIME()) { + spinlock_init(&last_value.lock); + } + + 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) { - 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; - } - } + goto out; + } - *microsec_ptr = MAX(microsec, expected_microsec); - if (tsc_ptr != NULL) { - *tsc_ptr = COPY_UNTRUSTED_VALUE(&ocall_gettime_args->tsc); - } + /* 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; }