Skip to content

Commit

Permalink
[PAL/LinuxSGX] protect from both time and tsc rewind in ocall_gettime
Browse files Browse the repository at this point in the history
Signed-off-by: Jonathan Shamir <[email protected]>
  • Loading branch information
jonathan-sha committed Apr 8, 2024
1 parent e8f263a commit bb02809
Showing 1 changed file with 58 additions and 24 deletions.
82 changes: 58 additions & 24 deletions pal/src/host/linux-sgx/enclave_ocalls.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down

0 comments on commit bb02809

Please sign in to comment.