3
3
use crate :: arch:: asm;
4
4
use crate :: cell:: UnsafeCell ;
5
5
use crate :: cmp;
6
+ use crate :: convert:: TryInto ;
7
+ use crate :: intrinsics;
6
8
use crate :: mem;
7
9
use crate :: ops:: { CoerceUnsized , Deref , DerefMut , Index , IndexMut } ;
8
10
use crate :: ptr:: { self , NonNull } ;
@@ -306,20 +308,35 @@ where
306
308
}
307
309
}
308
310
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)
323
340
}
324
341
325
342
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) {
352
369
/// - https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00615.html
353
370
/// - 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
354
371
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
+
356
379
unsafe {
357
380
let mut seg_sel: u16 = 0 ;
358
381
for off in 0 ..len {
@@ -380,41 +403,15 @@ pub(crate) unsafe fn copy_to_userspace(src: *const u8, dst: *mut u8, len: usize)
380
403
assert ! ( !src. addr( ) . overflowing_add( len) . 1 ) ;
381
404
assert ! ( !dst. addr( ) . overflowing_add( len) . 1 ) ;
382
405
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) ) ;
403
411
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) ;
418
415
}
419
416
}
420
417
@@ -434,87 +431,53 @@ pub(crate) unsafe fn copy_to_userspace(src: *const u8, dst: *mut u8, len: usize)
434
431
/// - https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00657.html
435
432
/// - https://www.intel.com/content/www/us/en/developer/articles/technical/software-security-guidance/advisory-guidance/stale-data-read-from-xapic.html
436
433
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
+ }
443
442
444
443
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) ;
476
461
}
477
462
}
478
463
479
464
assert ! ( !src. is_null( ) ) ;
480
465
assert ! ( !dst. is_null( ) ) ;
481
466
assert ! ( is_user_range( src, len) ) ;
482
467
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 ) ;
485
471
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) ) ;
503
477
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) ;
518
481
}
519
482
}
520
483
@@ -609,9 +572,9 @@ where
609
572
/// Copies the value from user memory into enclave memory.
610
573
pub fn to_enclave ( & self ) -> T {
611
574
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 ( )
615
578
}
616
579
}
617
580
}
0 commit comments