diff --git a/library/std/src/sys/pal/sgx/abi/usercalls/mod.rs b/library/std/src/sys/pal/sgx/abi/usercalls/mod.rs index dea44124f458b..5ae32a44fb5ac 100644 --- a/library/std/src/sys/pal/sgx/abi/usercalls/mod.rs +++ b/library/std/src/sys/pal/sgx/abi/usercalls/mod.rs @@ -2,6 +2,7 @@ use crate::cmp; use crate::io::{ BorrowedCursor, Error as IoError, ErrorKind, IoSlice, IoSliceMut, Result as IoResult, }; +use crate::ops::RangeInclusive; use crate::random::random; use crate::time::{Duration, Instant}; @@ -166,26 +167,60 @@ pub fn exit(panic: bool) -> ! { unsafe { raw::exit(panic) } } -/// Usercall `wait`. See the ABI documentation for more information. -#[unstable(feature = "sgx_platform", issue = "56975")] -pub fn wait(event_mask: u64, mut timeout: u64) -> IoResult { - if timeout != WAIT_NO && timeout != WAIT_INDEFINITE { - // We don't want people to rely on accuracy of timeouts to make - // security decisions in an SGX enclave. That's why we add a random - // amount not exceeding +/- 10% to the timeout value to discourage - // people from relying on accuracy of timeouts while providing a way - // to make things work in other cases. Note that in the SGX threat - // model the enclave runner which is serving the wait usercall is not - // trusted to ensure accurate timeouts. - if let Ok(timeout_signed) = i64::try_from(timeout) { - let tenth = timeout_signed / 10; - let deviation = random::(..).checked_rem(tenth).unwrap_or(0); - timeout = timeout_signed.saturating_add(deviation) as _; +/// Pick a random `timeout` within the specified range (in percentages) +fn pick_timeout(timeout: u64, rand_perc: RangeInclusive) -> u64 { + fn calc_perc(value: i64, perc: i8) -> Option { + value.checked_mul(i64::from(perc)).map(|v| v / 100) + } + + fn pick_random(range: &RangeInclusive) -> i64 { + let abs_range = range.end().abs_diff(*range.start()).try_into().unwrap_or(0); + let r: i64 = + random::(..).checked_rem(abs_range).and_then(|v| v.try_into().ok()).unwrap_or(0); + r.saturating_add(*range.start()) + } + + if timeout == WAIT_NO || timeout == WAIT_INDEFINITE { + return timeout; + } + + if let Ok(timeout_signed) = i64::try_from(timeout) { + let timeout_range = RangeInclusive::new( + calc_perc(timeout_signed, *rand_perc.start()).unwrap_or(timeout_signed), + calc_perc(timeout_signed, *rand_perc.end()).unwrap_or(timeout_signed), + ); + + if !timeout_range.is_empty() { + let deviation = pick_random(&timeout_range); + return timeout_signed.saturating_add(deviation).try_into().unwrap_or(timeout); } } + timeout +} + +/// Wait for a specific event and/or timeout. Also see the ABI documentation. +/// +/// We don't want people to rely on accuracy of timeouts to make security +/// decisions in an SGX enclave. We add a random amount to discourage people +/// from relying on accuracy of timeouts while providing a way to make +/// things work in other cases. +/// Some functions require a minimum waiting period (e.g., std::thread::sleep +/// must not return before a period of time has passed). Others can deal with +/// some randomness in both directions. In this private function we allow the +/// user to specify a range of randomness to add/subtract to the timeout. +/// Note that in the SGX threat model the enclave runner which is serving the +/// wait usercall is not trusted to ensure accurate timeouts. +fn wait_ex(event_mask: u64, timeout: u64, rand_perc: RangeInclusive) -> IoResult { + let timeout = pick_timeout(timeout, rand_perc); unsafe { raw::wait(event_mask, timeout).from_sgx_result() } } +/// Usercall `wait`. See the ABI documentation for more information. +#[unstable(feature = "sgx_platform", issue = "56975")] +pub fn wait(event_mask: u64, timeout: u64) -> IoResult { + wait_ex(event_mask, timeout, -10..=10) +} + /// Makes an effort to wait for a non-spurious event at least as long as /// `duration`. /// @@ -207,7 +242,7 @@ where let timeout = duration.map_or(raw::WAIT_NO, |duration| { cmp::min((u64::MAX - 1) as u128, duration.as_nanos()) as u64 }); - match wait(event_mask, timeout) { + match wait_ex(event_mask, timeout, 0..=10) { Ok(eventset) => { if event_mask == 0 { rtabort!("expected wait() to return Err, found Ok."); diff --git a/library/std/tests/thread.rs b/library/std/tests/thread.rs index 32561dd6ab6a3..b67430f8fdff9 100644 --- a/library/std/tests/thread.rs +++ b/library/std/tests/thread.rs @@ -18,6 +18,16 @@ fn sleep_very_long() { assert_eq!(*finished.lock().unwrap(), false); } +#[test] +fn sleep() { + let now = Instant::now(); + let period = Duration::from_millis(100); + thread::sleep(period); + + let elapsed = now.elapsed(); + assert!(elapsed >= period); +} + #[test] fn sleep_until() { let now = Instant::now();