Skip to content

Commit 905f49a

Browse files
author
Jethro Beekman
committed
Clean up SGX user memory copies
1 parent 5082281 commit 905f49a

File tree

1 file changed

+85
-122
lines changed
  • library/std/src/sys/sgx/abi/usercalls

1 file changed

+85
-122
lines changed

Diff for: library/std/src/sys/sgx/abi/usercalls/alloc.rs

+85-122
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
use crate::arch::asm;
44
use crate::cell::UnsafeCell;
55
use crate::cmp;
6+
use crate::convert::TryInto;
7+
use crate::intrinsics;
68
use crate::mem;
79
use crate::ops::{CoerceUnsized, Deref, DerefMut, Index, IndexMut};
810
use crate::ptr::{self, NonNull};
@@ -306,20 +308,35 @@ where
306308
}
307309
}
308310

309-
// Split a memory region ptr..ptr + len into three parts:
310-
// +--------+
311-
// | small0 | Chunk smaller than 8 bytes
312-
// +--------+
313-
// | big | Chunk 8-byte aligned, and size a multiple of 8 bytes
314-
// +--------+
315-
// | small1 | Chunk smaller than 8 bytes
316-
// +--------+
317-
fn region_as_aligned_chunks(ptr: *const u8, len: usize) -> (usize, usize, usize) {
318-
let small0_size = if ptr.is_aligned_to(8) { 0 } else { 8 - ptr.addr() % 8 };
319-
let small1_size = (len - small0_size) % 8;
320-
let big_size = len - small0_size - small1_size;
321-
322-
(small0_size, big_size, small1_size)
311+
/// Divide the slice `(ptr, len)` into three parts, where the middle part is
312+
/// aligned to `u64`.
313+
///
314+
/// The return values `(prefix_len, mid_len, suffix_len)` add back up to `len`.
315+
/// The return values are such that the memory region `(ptr + prefix_len,
316+
/// mid_len)` is the largest possible region where `ptr + prefix_len` is aligned
317+
/// to `u64` and `mid_len` is a multiple of the byte size of `u64`. This means
318+
/// that `prefix_len` and `suffix_len` are guaranteed to be less than the byte
319+
/// size of `u64`, and that `(ptr, prefix_len)` and `(ptr + prefix_len +
320+
/// mid_len, suffix_len)` don't straddle an alignment boundary.
321+
// Standard Rust functions such as `<[u8]>::align_to::<u64>` and
322+
// `<*const u8>::align_offset` aren't _guaranteed_ to compute the largest
323+
// possible middle region, and as such can't be used.
324+
fn u64_align_to_guaranteed(ptr: *const u8, mut len: usize) -> (usize, usize, usize) {
325+
const QWORD_SIZE: usize = mem::size_of::<u64>();
326+
327+
let offset = ptr as usize % QWORD_SIZE;
328+
329+
let prefix_len = if intrinsics::unlikely(offset > 0) { QWORD_SIZE - offset } else { 0 };
330+
331+
len = match len.checked_sub(prefix_len) {
332+
Some(remaining_len) => remaining_len,
333+
None => return (len, 0, 0),
334+
};
335+
336+
let suffix_len = len % QWORD_SIZE;
337+
len -= suffix_len;
338+
339+
(prefix_len, len, suffix_len)
323340
}
324341

325342
unsafe fn copy_quadwords(src: *const u8, dst: *mut u8, len: usize) {
@@ -352,7 +369,13 @@ unsafe fn copy_quadwords(src: *const u8, dst: *mut u8, len: usize) {
352369
/// - https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00615.html
353370
/// - https://www.intel.com/content/www/us/en/developer/articles/technical/software-security-guidance/technical-documentation/processor-mmio-stale-data-vulnerabilities.html#inpage-nav-3-2-2
354371
pub(crate) unsafe fn copy_to_userspace(src: *const u8, dst: *mut u8, len: usize) {
355-
unsafe fn copy_bytewise_to_userspace(src: *const u8, dst: *mut u8, len: usize) {
372+
/// Like `ptr::copy(src, dst, len)`, except it uses the Intel-recommended
373+
/// instruction sequence for unaligned writes.
374+
unsafe fn write_bytewise_to_userspace(src: *const u8, dst: *mut u8, len: usize) {
375+
if intrinsics::likely(len == 0) {
376+
return;
377+
}
378+
356379
unsafe {
357380
let mut seg_sel: u16 = 0;
358381
for off in 0..len {
@@ -380,41 +403,15 @@ pub(crate) unsafe fn copy_to_userspace(src: *const u8, dst: *mut u8, len: usize)
380403
assert!(!src.addr().overflowing_add(len).1);
381404
assert!(!dst.addr().overflowing_add(len).1);
382405

383-
if len < 8 {
384-
// Can't align on 8 byte boundary: copy safely byte per byte
385-
unsafe {
386-
copy_bytewise_to_userspace(src, dst, len);
387-
}
388-
} else if len % 8 == 0 && dst.is_aligned_to(8) {
389-
// Copying 8-byte aligned quadwords: copy quad word per quad word
390-
unsafe {
391-
copy_quadwords(src, dst, len);
392-
}
393-
} else {
394-
// Split copies into three parts:
395-
// +--------+
396-
// | small0 | Chunk smaller than 8 bytes
397-
// +--------+
398-
// | big | Chunk 8-byte aligned, and size a multiple of 8 bytes
399-
// +--------+
400-
// | small1 | Chunk smaller than 8 bytes
401-
// +--------+
402-
let (small0_size, big_size, small1_size) = region_as_aligned_chunks(dst, len);
406+
unsafe {
407+
let (len1, len2, len3) = u64_align_to_guaranteed(dst, len);
408+
let (src1, dst1) = (src, dst);
409+
let (src2, dst2) = (src1.add(len1), dst1.add(len1));
410+
let (src3, dst3) = (src2.add(len2), dst2.add(len2));
403411

404-
unsafe {
405-
// Copy small0
406-
copy_bytewise_to_userspace(src, dst, small0_size);
407-
408-
// Copy big
409-
let big_src = src.add(small0_size);
410-
let big_dst = dst.add(small0_size);
411-
copy_quadwords(big_src, big_dst, big_size);
412-
413-
// Copy small1
414-
let small1_src = src.add(big_size + small0_size);
415-
let small1_dst = dst.add(big_size + small0_size);
416-
copy_bytewise_to_userspace(small1_src, small1_dst, small1_size);
417-
}
412+
write_bytewise_to_userspace(src1, dst1, len1);
413+
copy_quadwords(src2, dst2, len2);
414+
write_bytewise_to_userspace(src3, dst3, len3);
418415
}
419416
}
420417

@@ -434,87 +431,53 @@ pub(crate) unsafe fn copy_to_userspace(src: *const u8, dst: *mut u8, len: usize)
434431
/// - https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00657.html
435432
/// - https://www.intel.com/content/www/us/en/developer/articles/technical/software-security-guidance/advisory-guidance/stale-data-read-from-xapic.html
436433
pub(crate) unsafe fn copy_from_userspace(src: *const u8, dst: *mut u8, len: usize) {
437-
// Copies memory region `src..src + len` to the enclave at `dst`. The source memory region
438-
// is:
439-
// - strictly less than 8 bytes in size and may be
440-
// - located at a misaligned memory location
441-
fn copy_misaligned_chunk_to_enclave(src: *const u8, dst: *mut u8, len: usize) {
442-
let mut tmp_buff = [0u8; 16];
434+
/// Like `ptr::copy(src, dst, len)`, except it uses only u64-aligned reads.
435+
///
436+
/// # Safety
437+
/// The source memory region must not straddle an alignment boundary.
438+
unsafe fn read_misaligned_from_userspace(src: *const u8, dst: *mut u8, len: usize) {
439+
if intrinsics::likely(len == 0) {
440+
return;
441+
}
443442

444443
unsafe {
445-
// Compute an aligned memory region to read from
446-
// +--------+ <-- aligned_src + aligned_len (8B-aligned)
447-
// | pad1 |
448-
// +--------+ <-- src + len (misaligned)
449-
// | |
450-
// | |
451-
// | |
452-
// +--------+ <-- src (misaligned)
453-
// | pad0 |
454-
// +--------+ <-- aligned_src (8B-aligned)
455-
let pad0_size = src as usize % 8;
456-
let aligned_src = src.sub(pad0_size);
457-
458-
let pad1_size = 8 - (src.add(len) as usize % 8);
459-
let aligned_len = pad0_size + len + pad1_size;
460-
461-
debug_assert!(len < 8);
462-
debug_assert_eq!(aligned_src as usize % 8, 0);
463-
debug_assert_eq!(aligned_len % 8, 0);
464-
debug_assert!(aligned_len <= 16);
465-
466-
// Copy the aligned buffer to a temporary buffer
467-
// Note: copying from a slightly different memory location is a bit odd. In this case it
468-
// can't lead to page faults or inadvertent copying from the enclave as we only ensured
469-
// that the `src` pointer is aligned at an 8 byte boundary. As pages are 4096 bytes
470-
// aligned, `aligned_src` must be on the same page as `src`. A similar argument can be made
471-
// for `src + len`
472-
copy_quadwords(aligned_src as _, tmp_buff.as_mut_ptr(), aligned_len);
473-
474-
// Copy the correct parts of the temporary buffer to the destination
475-
ptr::copy(tmp_buff.as_ptr().add(pad0_size), dst, len);
444+
let offset: usize;
445+
let data: u64;
446+
// doing a memory read that's potentially out of bounds for `src`,
447+
// this isn't supported by Rust, so have to use assembly
448+
asm!("
449+
movl {src:e}, {offset:e}
450+
andl $7, {offset:e}
451+
andq $-8, {src}
452+
movq ({src}), {dst}
453+
",
454+
src = inout(reg) src => _,
455+
offset = out(reg) offset,
456+
dst = out(reg) data,
457+
options(nostack, att_syntax, readonly, pure)
458+
);
459+
let data = data.to_le_bytes();
460+
ptr::copy_nonoverlapping(data.as_ptr().add(offset), dst, len);
476461
}
477462
}
478463

479464
assert!(!src.is_null());
480465
assert!(!dst.is_null());
481466
assert!(is_user_range(src, len));
482467
assert!(is_enclave_range(dst, len));
483-
assert!(!(src as usize).overflowing_add(len + 8).1);
484-
assert!(!(dst as usize).overflowing_add(len + 8).1);
468+
assert!(len < isize::MAX as usize);
469+
assert!(!(src as usize).overflowing_add(len).1);
470+
assert!(!(dst as usize).overflowing_add(len).1);
485471

486-
if len < 8 {
487-
copy_misaligned_chunk_to_enclave(src, dst, len);
488-
} else if len % 8 == 0 && src as usize % 8 == 0 {
489-
// Copying 8-byte aligned quadwords: copy quad word per quad word
490-
unsafe {
491-
copy_quadwords(src, dst, len);
492-
}
493-
} else {
494-
// Split copies into three parts:
495-
// +--------+
496-
// | small0 | Chunk smaller than 8 bytes
497-
// +--------+
498-
// | big | Chunk 8-byte aligned, and size a multiple of 8 bytes
499-
// +--------+
500-
// | small1 | Chunk smaller than 8 bytes
501-
// +--------+
502-
let (small0_size, big_size, small1_size) = region_as_aligned_chunks(dst, len);
472+
unsafe {
473+
let (len1, len2, len3) = u64_align_to_guaranteed(src, len);
474+
let (src1, dst1) = (src, dst);
475+
let (src2, dst2) = (src1.add(len1), dst1.add(len1));
476+
let (src3, dst3) = (src2.add(len2), dst2.add(len2));
503477

504-
unsafe {
505-
// Copy small0
506-
copy_misaligned_chunk_to_enclave(src, dst, small0_size);
507-
508-
// Copy big
509-
let big_src = src.add(small0_size);
510-
let big_dst = dst.add(small0_size);
511-
copy_quadwords(big_src, big_dst, big_size);
512-
513-
// Copy small1
514-
let small1_src = src.add(big_size + small0_size);
515-
let small1_dst = dst.add(big_size + small0_size);
516-
copy_misaligned_chunk_to_enclave(small1_src, small1_dst, small1_size);
517-
}
478+
read_misaligned_from_userspace(src1, dst1, len1);
479+
copy_quadwords(src2, dst2, len2);
480+
read_misaligned_from_userspace(src3, dst3, len3);
518481
}
519482
}
520483

@@ -609,9 +572,9 @@ where
609572
/// Copies the value from user memory into enclave memory.
610573
pub fn to_enclave(&self) -> T {
611574
unsafe {
612-
let mut data: T = mem::MaybeUninit::uninit().assume_init();
613-
copy_from_userspace(self.0.get() as _, &mut data as *mut T as _, mem::size_of::<T>());
614-
data
575+
let mut data = mem::MaybeUninit::uninit();
576+
copy_from_userspace(self.0.get() as _, data.as_mut_ptr() as _, mem::size_of::<T>());
577+
data.assume_init()
615578
}
616579
}
617580
}

0 commit comments

Comments
 (0)