33use crate::arch::asm;
44use crate::cell::UnsafeCell;
55use crate::cmp;
6+ use crate::convert::TryInto;
7+ use crate::intrinsics;
68use crate::mem;
79use crate::ops::{CoerceUnsized, Deref, DerefMut, Index, IndexMut};
810use 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
325342unsafe 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
354371pub(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
436433pub(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