From bb02809b7bb03e3e6fb1d88a714ebfe4af7cb383 Mon Sep 17 00:00:00 2001 From: Jonathan Shamir Date: Sun, 7 Apr 2024 16:06:37 +0300 Subject: [PATCH] [PAL/LinuxSGX] protect from both time and tsc rewind in ocall_gettime Signed-off-by: Jonathan Shamir --- pal/src/host/linux-sgx/enclave_ocalls.c | 82 +++++++++++++++++-------- 1 file changed, 58 insertions(+), 24 deletions(-) diff --git a/pal/src/host/linux-sgx/enclave_ocalls.c b/pal/src/host/linux-sgx/enclave_ocalls.c index c7a0f67546..a146958733 100644 --- a/pal/src/host/linux-sgx/enclave_ocalls.c +++ b/pal/src/host/linux-sgx/enclave_ocalls.c @@ -1768,49 +1768,83 @@ 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 = { + .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; - } - } + /* 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); + } - *microsec_ptr = MAX(microsec, expected_microsec); - if (tsc_ptr != NULL) { - *tsc_ptr = COPY_UNTRUSTED_VALUE(&ocall_gettime_args->tsc); - } + /* 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; }