std::sys::pal::sgx: fix mismatched alloc/free alignment - #161895
Conversation
|
Thanks for the pull request, and welcome! The Rust Project has assigned @JohnTitor (or someone else) to review your changes, you should hear from them (or someone else) within the next two weeks. Please see the contribution instructions and our LLM policy for more information. Why was this reviewer chosen?The reviewer was selected based on:
|
This comment has been minimized.
This comment has been minimized.
f5080fe to
3e5c0fa
Compare
|
Thanks for the detailed report! I can't approve this, but FWIW the fix looks fine to me. Naively fixing alignment in I think making the runner responsible for this optimization is a good idea, since it's not security-critical. |
… r=JohnTitor std::sys::pal::sgx: fix mismatched alloc/free alignment ### Why the PR? I've got a local `miri` branch that's able to test `x86_64-fortanix-unknown-sgx`, so I can get better assurance about our enclaves. It's now complaining about a bunch of stuff in std :sweat_smile: ### Context 1. `x86_64-fortanix-unknown-sgx` enclaves can request the untrusted host enclave runner to allocate/free memory in userspace and get a pointer to it in return. 2. There's a userspace/enclave space memory split for `x86_64-fortanix-unknown-sgx` enclaves. It's a bit like the userspace/kernel space split, where the kernel doesn't trust pointers from userspace and is very paranoid about copying data to/from userspace. ### Problem In the enclave, `User::new_uninit_bytes` and `User::drop` are requesting the host to alloc/dealloc memory with potentially mismatched alignment, as the enclave side is unconditionally over-aligning on allocation but not doing the same on free. - Ex: `User::<ByteBuffer>` -> `alloc(_, align=8)` -> `drop()` -> `free(_, align=1)` See: <https://github.com/rust-lang/rust/blob/main/library/std/src/sys/pal/sgx/abi/usercalls/alloc.rs> ```rust // Enclave-side impl<T: ?Sized> User<T> where T: UserSafe, { // This function returns memory that is practically uninitialized, but is // not considered "unspecified" or "undefined" for purposes of an // optimizing compiler. This is achieved by returning a pointer from // from outside as obtained by `super::alloc`. fn new_uninit_bytes(size: usize) -> Self { unsafe { // Mustn't call alloc with size 0. let ptr = if size > 0 { // `copy_to_userspace` is more efficient when data is 8-byte aligned let alignment = cmp::max(T::align_of(), 8); // <------------------------- HERE rtunwrap!(Ok, super::alloc(size, alignment)) as _ } else { T::align_of() as _ // dangling pointer ok for size 0 }; if let Ok(v) = crate::panic::catch_unwind(|| T::from_raw_sized(ptr, size)) { User(NonNull::new_userref(v)) } else { rtabort!("Got invalid pointer from alloc() usercall") } } } // ... } // ... impl<T: ?Sized> Drop for User<T> where T: UserSafe, { fn drop(&mut self) { unsafe { let ptr = (*self.0.as_ptr()).0.get(); // vvvvvvvvvvvvv------------------ HERE super::free(ptr as _, size_of_val(&mut *ptr), T::align_of()); } } } ``` This min. alignment optimization was introduced in rust-lang@6f7d193. See below for more details on why. The two usercalls, `super::alloc` and `super::free`, are eventually handled by the host runner. They just delegate to the `System` allocator: See: <https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/mod.rs> ```rust // Host-side / userspace impl<'tcs> IOHandlerInput<'tcs> { // ... #[inline(always)] fn alloc(&self, size: usize, alignment: usize) -> IoResult<*mut u8> { unsafe { // vvvvvvvvv--------------- UNCHANGED let layout = Layout::from_size_align(size, alignment) .map_err(|_| IoErrorKind::InvalidInput)?; if layout.size() == 0 { return Err(IoErrorKind::InvalidInput.into()); } let ptr = System.alloc(layout); if ptr.is_null() { Err(IoErrorKind::Other.into()) } else { Ok(ptr) } } } #[inline(always)] fn free(&self, ptr: *mut u8, size: usize, alignment: usize) -> IoResult<()> { unsafe { // vvvvvvvvv--------------- UNCHANGED let layout = Layout::from_size_align(size, alignment) .map_err(|_| IoErrorKind::InvalidInput)?; if size == 0 { return Ok(()); } Ok(System.dealloc(ptr, layout)) } } // ... } ``` It also appears that `enclave-runner-sgx` assumes that there's no `#[global_allocator]` override (<https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/interface.rs#L333>). For most enclave hosts running stock x86_64-unknown-linux-gnu (glibc malloc), I don't believe this mismatch is currently an issue, since posix `free` ignores the alignment anyway. If you did swap in jemalloc, which does care about the dealloc alignment, then something would definitely go wrong elsewhere, as the you'd have mismatched allocators (`System` above vs `Box<_>`/`Vec<_>` using `Global`). ### Solutions It's not clear that we can round-up the alignment on `free`, since `User::from_raw` exists, and there's various places that call it outside std. We should probably just remove the in-enclave min. alignment until we come up with a more satisfactory solution. My guess is that the right place to do the min. alignment optimization is on enclave-runner-sgx side: <https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/mod.rs#L1596> and other places that hand memory to the SGX enclave. ### Why over-align in the first place? The min. alignment exists for performance reasons (see: `copy_from_userspace`). It's highly preferable if all memory copied from userspace is at least 8 byte aligned, otherwise we have to fallback to a super slow copy routine for the unaligned prefix (and suffix).
… r=JohnTitor std::sys::pal::sgx: fix mismatched alloc/free alignment ### Why the PR? I've got a local `miri` branch that's able to test `x86_64-fortanix-unknown-sgx`, so I can get better assurance about our enclaves. It's now complaining about a bunch of stuff in std :sweat_smile: ### Context 1. `x86_64-fortanix-unknown-sgx` enclaves can request the untrusted host enclave runner to allocate/free memory in userspace and get a pointer to it in return. 2. There's a userspace/enclave space memory split for `x86_64-fortanix-unknown-sgx` enclaves. It's a bit like the userspace/kernel space split, where the kernel doesn't trust pointers from userspace and is very paranoid about copying data to/from userspace. ### Problem In the enclave, `User::new_uninit_bytes` and `User::drop` are requesting the host to alloc/dealloc memory with potentially mismatched alignment, as the enclave side is unconditionally over-aligning on allocation but not doing the same on free. - Ex: `User::<ByteBuffer>` -> `alloc(_, align=8)` -> `drop()` -> `free(_, align=1)` See: <https://github.com/rust-lang/rust/blob/main/library/std/src/sys/pal/sgx/abi/usercalls/alloc.rs> ```rust // Enclave-side impl<T: ?Sized> User<T> where T: UserSafe, { // This function returns memory that is practically uninitialized, but is // not considered "unspecified" or "undefined" for purposes of an // optimizing compiler. This is achieved by returning a pointer from // from outside as obtained by `super::alloc`. fn new_uninit_bytes(size: usize) -> Self { unsafe { // Mustn't call alloc with size 0. let ptr = if size > 0 { // `copy_to_userspace` is more efficient when data is 8-byte aligned let alignment = cmp::max(T::align_of(), 8); // <------------------------- HERE rtunwrap!(Ok, super::alloc(size, alignment)) as _ } else { T::align_of() as _ // dangling pointer ok for size 0 }; if let Ok(v) = crate::panic::catch_unwind(|| T::from_raw_sized(ptr, size)) { User(NonNull::new_userref(v)) } else { rtabort!("Got invalid pointer from alloc() usercall") } } } // ... } // ... impl<T: ?Sized> Drop for User<T> where T: UserSafe, { fn drop(&mut self) { unsafe { let ptr = (*self.0.as_ptr()).0.get(); // vvvvvvvvvvvvv------------------ HERE super::free(ptr as _, size_of_val(&mut *ptr), T::align_of()); } } } ``` This min. alignment optimization was introduced in rust-lang@6f7d193. See below for more details on why. The two usercalls, `super::alloc` and `super::free`, are eventually handled by the host runner. They just delegate to the `System` allocator: See: <https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/mod.rs> ```rust // Host-side / userspace impl<'tcs> IOHandlerInput<'tcs> { // ... #[inline(always)] fn alloc(&self, size: usize, alignment: usize) -> IoResult<*mut u8> { unsafe { // vvvvvvvvv--------------- UNCHANGED let layout = Layout::from_size_align(size, alignment) .map_err(|_| IoErrorKind::InvalidInput)?; if layout.size() == 0 { return Err(IoErrorKind::InvalidInput.into()); } let ptr = System.alloc(layout); if ptr.is_null() { Err(IoErrorKind::Other.into()) } else { Ok(ptr) } } } #[inline(always)] fn free(&self, ptr: *mut u8, size: usize, alignment: usize) -> IoResult<()> { unsafe { // vvvvvvvvv--------------- UNCHANGED let layout = Layout::from_size_align(size, alignment) .map_err(|_| IoErrorKind::InvalidInput)?; if size == 0 { return Ok(()); } Ok(System.dealloc(ptr, layout)) } } // ... } ``` It also appears that `enclave-runner-sgx` assumes that there's no `#[global_allocator]` override (<https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/interface.rs#L333>). For most enclave hosts running stock x86_64-unknown-linux-gnu (glibc malloc), I don't believe this mismatch is currently an issue, since posix `free` ignores the alignment anyway. If you did swap in jemalloc, which does care about the dealloc alignment, then something would definitely go wrong elsewhere, as the you'd have mismatched allocators (`System` above vs `Box<_>`/`Vec<_>` using `Global`). ### Solutions It's not clear that we can round-up the alignment on `free`, since `User::from_raw` exists, and there's various places that call it outside std. We should probably just remove the in-enclave min. alignment until we come up with a more satisfactory solution. My guess is that the right place to do the min. alignment optimization is on enclave-runner-sgx side: <https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/mod.rs#L1596> and other places that hand memory to the SGX enclave. ### Why over-align in the first place? The min. alignment exists for performance reasons (see: `copy_from_userspace`). It's highly preferable if all memory copied from userspace is at least 8 byte aligned, otherwise we have to fallback to a super slow copy routine for the unaligned prefix (and suffix).
… r=JohnTitor std::sys::pal::sgx: fix mismatched alloc/free alignment ### Why the PR? I've got a local `miri` branch that's able to test `x86_64-fortanix-unknown-sgx`, so I can get better assurance about our enclaves. It's now complaining about a bunch of stuff in std :sweat_smile: ### Context 1. `x86_64-fortanix-unknown-sgx` enclaves can request the untrusted host enclave runner to allocate/free memory in userspace and get a pointer to it in return. 2. There's a userspace/enclave space memory split for `x86_64-fortanix-unknown-sgx` enclaves. It's a bit like the userspace/kernel space split, where the kernel doesn't trust pointers from userspace and is very paranoid about copying data to/from userspace. ### Problem In the enclave, `User::new_uninit_bytes` and `User::drop` are requesting the host to alloc/dealloc memory with potentially mismatched alignment, as the enclave side is unconditionally over-aligning on allocation but not doing the same on free. - Ex: `User::<ByteBuffer>` -> `alloc(_, align=8)` -> `drop()` -> `free(_, align=1)` See: <https://github.com/rust-lang/rust/blob/main/library/std/src/sys/pal/sgx/abi/usercalls/alloc.rs> ```rust // Enclave-side impl<T: ?Sized> User<T> where T: UserSafe, { // This function returns memory that is practically uninitialized, but is // not considered "unspecified" or "undefined" for purposes of an // optimizing compiler. This is achieved by returning a pointer from // from outside as obtained by `super::alloc`. fn new_uninit_bytes(size: usize) -> Self { unsafe { // Mustn't call alloc with size 0. let ptr = if size > 0 { // `copy_to_userspace` is more efficient when data is 8-byte aligned let alignment = cmp::max(T::align_of(), 8); // <------------------------- HERE rtunwrap!(Ok, super::alloc(size, alignment)) as _ } else { T::align_of() as _ // dangling pointer ok for size 0 }; if let Ok(v) = crate::panic::catch_unwind(|| T::from_raw_sized(ptr, size)) { User(NonNull::new_userref(v)) } else { rtabort!("Got invalid pointer from alloc() usercall") } } } // ... } // ... impl<T: ?Sized> Drop for User<T> where T: UserSafe, { fn drop(&mut self) { unsafe { let ptr = (*self.0.as_ptr()).0.get(); // vvvvvvvvvvvvv------------------ HERE super::free(ptr as _, size_of_val(&mut *ptr), T::align_of()); } } } ``` This min. alignment optimization was introduced in rust-lang@6f7d193. See below for more details on why. The two usercalls, `super::alloc` and `super::free`, are eventually handled by the host runner. They just delegate to the `System` allocator: See: <https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/mod.rs> ```rust // Host-side / userspace impl<'tcs> IOHandlerInput<'tcs> { // ... #[inline(always)] fn alloc(&self, size: usize, alignment: usize) -> IoResult<*mut u8> { unsafe { // vvvvvvvvv--------------- UNCHANGED let layout = Layout::from_size_align(size, alignment) .map_err(|_| IoErrorKind::InvalidInput)?; if layout.size() == 0 { return Err(IoErrorKind::InvalidInput.into()); } let ptr = System.alloc(layout); if ptr.is_null() { Err(IoErrorKind::Other.into()) } else { Ok(ptr) } } } #[inline(always)] fn free(&self, ptr: *mut u8, size: usize, alignment: usize) -> IoResult<()> { unsafe { // vvvvvvvvv--------------- UNCHANGED let layout = Layout::from_size_align(size, alignment) .map_err(|_| IoErrorKind::InvalidInput)?; if size == 0 { return Ok(()); } Ok(System.dealloc(ptr, layout)) } } // ... } ``` It also appears that `enclave-runner-sgx` assumes that there's no `#[global_allocator]` override (<https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/interface.rs#L333>). For most enclave hosts running stock x86_64-unknown-linux-gnu (glibc malloc), I don't believe this mismatch is currently an issue, since posix `free` ignores the alignment anyway. If you did swap in jemalloc, which does care about the dealloc alignment, then something would definitely go wrong elsewhere, as the you'd have mismatched allocators (`System` above vs `Box<_>`/`Vec<_>` using `Global`). ### Solutions It's not clear that we can round-up the alignment on `free`, since `User::from_raw` exists, and there's various places that call it outside std. We should probably just remove the in-enclave min. alignment until we come up with a more satisfactory solution. My guess is that the right place to do the min. alignment optimization is on enclave-runner-sgx side: <https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/mod.rs#L1596> and other places that hand memory to the SGX enclave. ### Why over-align in the first place? The min. alignment exists for performance reasons (see: `copy_from_userspace`). It's highly preferable if all memory copied from userspace is at least 8 byte aligned, otherwise we have to fallback to a super slow copy routine for the unaligned prefix (and suffix).
Rollup of 25 pull requests Successful merges: - #159074 ([PAC] FnAbi, llvm.ptrauth.resign and Session API change (2/8)) - #159792 (A more readable debug map for IndexMaps) - #161895 (std::sys::pal::sgx: fix mismatched alloc/free alignment) - #161900 (bootstrap: Include feature-gated items in bootstrap tool docs) - #161940 (Promote `wasm32-wasip3` to a tier 2 target) - #162072 (Add new Tier-3 target: `powerpc64-sony-ps3`) - #162179 (type system const items via direct rhs) - #162277 (Introduce `rustc_middle::middel::resolve`) - #162285 (box: fixup map/try_map deallocate calls) - #162286 (string: don't unwind prematurely) - #162289 (alloc: a bunch of safety comments) - #162292 (Update `askama` version to `0.16.1`) - #160509 (Remove `RegionExt`; move methods to `Region` in `rustc_type_ir`) - #160906 (Suggest usize instead of placeholder type for array length constants) - #160936 (traits: Represent live alias arguments as bitsets) - #161400 (Improve diagnostics for references to closures) - #161656 (Suggest mutable references for FnMut closure arguments) - #161711 (Add more splat fn type tests) - #161786 (Make `tcx.def_id_partial_cmp` public) - #161953 (sanitizers: Implicitly disable mutually exclusive sanitizers) - #162155 (add suggestion for `rustc_allowed_through_unstable_modules` attribute) - #162212 (Implement `Rng` for `Box`) - #162246 (Fix incorrect meta span) - #162266 (std: fix typo) - #162291 (Add regression test from 1.98.1)
… r=JohnTitor std::sys::pal::sgx: fix mismatched alloc/free alignment ### Why the PR? I've got a local `miri` branch that's able to test `x86_64-fortanix-unknown-sgx`, so I can get better assurance about our enclaves. It's now complaining about a bunch of stuff in std :sweat_smile: ### Context 1. `x86_64-fortanix-unknown-sgx` enclaves can request the untrusted host enclave runner to allocate/free memory in userspace and get a pointer to it in return. 2. There's a userspace/enclave space memory split for `x86_64-fortanix-unknown-sgx` enclaves. It's a bit like the userspace/kernel space split, where the kernel doesn't trust pointers from userspace and is very paranoid about copying data to/from userspace. ### Problem In the enclave, `User::new_uninit_bytes` and `User::drop` are requesting the host to alloc/dealloc memory with potentially mismatched alignment, as the enclave side is unconditionally over-aligning on allocation but not doing the same on free. - Ex: `User::<ByteBuffer>` -> `alloc(_, align=8)` -> `drop()` -> `free(_, align=1)` See: <https://github.com/rust-lang/rust/blob/main/library/std/src/sys/pal/sgx/abi/usercalls/alloc.rs> ```rust // Enclave-side impl<T: ?Sized> User<T> where T: UserSafe, { // This function returns memory that is practically uninitialized, but is // not considered "unspecified" or "undefined" for purposes of an // optimizing compiler. This is achieved by returning a pointer from // from outside as obtained by `super::alloc`. fn new_uninit_bytes(size: usize) -> Self { unsafe { // Mustn't call alloc with size 0. let ptr = if size > 0 { // `copy_to_userspace` is more efficient when data is 8-byte aligned let alignment = cmp::max(T::align_of(), 8); // <------------------------- HERE rtunwrap!(Ok, super::alloc(size, alignment)) as _ } else { T::align_of() as _ // dangling pointer ok for size 0 }; if let Ok(v) = crate::panic::catch_unwind(|| T::from_raw_sized(ptr, size)) { User(NonNull::new_userref(v)) } else { rtabort!("Got invalid pointer from alloc() usercall") } } } // ... } // ... impl<T: ?Sized> Drop for User<T> where T: UserSafe, { fn drop(&mut self) { unsafe { let ptr = (*self.0.as_ptr()).0.get(); // vvvvvvvvvvvvv------------------ HERE super::free(ptr as _, size_of_val(&mut *ptr), T::align_of()); } } } ``` This min. alignment optimization was introduced in rust-lang@6f7d193. See below for more details on why. The two usercalls, `super::alloc` and `super::free`, are eventually handled by the host runner. They just delegate to the `System` allocator: See: <https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/mod.rs> ```rust // Host-side / userspace impl<'tcs> IOHandlerInput<'tcs> { // ... #[inline(always)] fn alloc(&self, size: usize, alignment: usize) -> IoResult<*mut u8> { unsafe { // vvvvvvvvv--------------- UNCHANGED let layout = Layout::from_size_align(size, alignment) .map_err(|_| IoErrorKind::InvalidInput)?; if layout.size() == 0 { return Err(IoErrorKind::InvalidInput.into()); } let ptr = System.alloc(layout); if ptr.is_null() { Err(IoErrorKind::Other.into()) } else { Ok(ptr) } } } #[inline(always)] fn free(&self, ptr: *mut u8, size: usize, alignment: usize) -> IoResult<()> { unsafe { // vvvvvvvvv--------------- UNCHANGED let layout = Layout::from_size_align(size, alignment) .map_err(|_| IoErrorKind::InvalidInput)?; if size == 0 { return Ok(()); } Ok(System.dealloc(ptr, layout)) } } // ... } ``` It also appears that `enclave-runner-sgx` assumes that there's no `#[global_allocator]` override (<https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/interface.rs#L333>). For most enclave hosts running stock x86_64-unknown-linux-gnu (glibc malloc), I don't believe this mismatch is currently an issue, since posix `free` ignores the alignment anyway. If you did swap in jemalloc, which does care about the dealloc alignment, then something would definitely go wrong elsewhere, as the you'd have mismatched allocators (`System` above vs `Box<_>`/`Vec<_>` using `Global`). ### Solutions It's not clear that we can round-up the alignment on `free`, since `User::from_raw` exists, and there's various places that call it outside std. We should probably just remove the in-enclave min. alignment until we come up with a more satisfactory solution. My guess is that the right place to do the min. alignment optimization is on enclave-runner-sgx side: <https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/mod.rs#L1596> and other places that hand memory to the SGX enclave. ### Why over-align in the first place? The min. alignment exists for performance reasons (see: `copy_from_userspace`). It's highly preferable if all memory copied from userspace is at least 8 byte aligned, otherwise we have to fallback to a super slow copy routine for the unaligned prefix (and suffix).
Rollup of 25 pull requests Successful merges: - #159074 ([PAC] FnAbi, llvm.ptrauth.resign and Session API change (2/8)) - #159792 (A more readable debug map for IndexMaps) - #160745 (make closures act like MaybeDangling) - #161895 (std::sys::pal::sgx: fix mismatched alloc/free alignment) - #161940 (Promote `wasm32-wasip3` to a tier 2 target) - #162072 (Add new Tier-3 target: `powerpc64-sony-ps3`) - #162179 (type system const items via direct rhs) - #162277 (Introduce `rustc_middle::middel::resolve`) - #162285 (box: fixup map/try_map deallocate calls) - #162286 (string: don't unwind prematurely) - #162289 (alloc: a bunch of safety comments) - #162292 (Update `askama` version to `0.16.1`) - #160509 (Remove `RegionExt`; move methods to `Region` in `rustc_type_ir`) - #160906 (Suggest usize instead of placeholder type for array length constants) - #160936 (traits: Represent live alias arguments as bitsets) - #161400 (Improve diagnostics for references to closures) - #161656 (Suggest mutable references for FnMut closure arguments) - #161711 (Add more splat fn type tests) - #161786 (Make `tcx.def_id_partial_cmp` public) - #161953 (sanitizers: Implicitly disable mutually exclusive sanitizers) - #162155 (add suggestion for `rustc_allowed_through_unstable_modules` attribute) - #162212 (Implement `Rng` for `Box`) - #162246 (Fix incorrect meta span) - #162266 (std: fix typo) - #162291 (Add regression test from 1.98.1)
|
Failed in #162300 (comment). @bors r- |
|
This pull request was unapproved. This PR was contained in a rollup (#162300), which was unapproved. |
`User::new_uninit_bytes` and `User::drop` are asking the host to alloc/dealloc memory with potentially mismatched alignment, as the enclave side is unconditionally over-aligning on allocation but not doing the same on free. - Ex: `User::<ByteBuffer>` -> `alloc(_, align=8)` -> `drop()` -> `free(_, align=1)` For most hosts running stock x86_64-linux + glibc malloc, I don't believe this mismatch is an issue, since posix `free` ignores the alignment anyway. My guess is that if you're using jemalloc, which does care about the dealloc alignment, then something _might_ go wrong. It's also not clear that we can just round-up the alignment on `free`, since `User::from_raw` exists, and there's various places that call it outside std. We should probably just remove the min. alignment until we come up with a more satisfactory solution. My guess is that the right place to do the min. alignment optimization is in the host-side enclave-runner: <https://github.com/fortanix/rust-sgx/blob/be93e7abe92eff4b5610e15fe21b16196ace1e6e/intel-sgx/enclave-runner-sgx/src/usercalls/mod.rs#L1596> and other places that hand memory to the SGX enclave. NB. The min. alignment exists for performance reasons (see: `copy_from_userspace`). It's highly preferable if all memory copied from userspace is at least 8 byte aligned, otherwise we have to fallback to a super slow copy routine for the unaligned prefix (and suffix).
3e5c0fa to
14ac84f
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
|
Ugh, sorry bout that. My bootstrap.toml was messed up it seems... Should be fixed now. |
|
@bors r+ |
… r=JohnTitor std::sys::pal::sgx: fix mismatched alloc/free alignment ### Why the PR? I've got a local `miri` branch that's able to test `x86_64-fortanix-unknown-sgx`, so I can get better assurance about our enclaves. It's now complaining about a bunch of stuff in std :sweat_smile: ### Context 1. `x86_64-fortanix-unknown-sgx` enclaves can request the untrusted host enclave runner to allocate/free memory in userspace and get a pointer to it in return. 2. There's a userspace/enclave space memory split for `x86_64-fortanix-unknown-sgx` enclaves. It's a bit like the userspace/kernel space split, where the kernel doesn't trust pointers from userspace and is very paranoid about copying data to/from userspace. ### Problem In the enclave, `User::new_uninit_bytes` and `User::drop` are requesting the host to alloc/dealloc memory with potentially mismatched alignment, as the enclave side is unconditionally over-aligning on allocation but not doing the same on free. - Ex: `User::<ByteBuffer>` -> `alloc(_, align=8)` -> `drop()` -> `free(_, align=1)` See: <https://github.com/rust-lang/rust/blob/main/library/std/src/sys/pal/sgx/abi/usercalls/alloc.rs> ```rust // Enclave-side impl<T: ?Sized> User<T> where T: UserSafe, { // This function returns memory that is practically uninitialized, but is // not considered "unspecified" or "undefined" for purposes of an // optimizing compiler. This is achieved by returning a pointer from // from outside as obtained by `super::alloc`. fn new_uninit_bytes(size: usize) -> Self { unsafe { // Mustn't call alloc with size 0. let ptr = if size > 0 { // `copy_to_userspace` is more efficient when data is 8-byte aligned let alignment = cmp::max(T::align_of(), 8); // <------------------------- HERE rtunwrap!(Ok, super::alloc(size, alignment)) as _ } else { T::align_of() as _ // dangling pointer ok for size 0 }; if let Ok(v) = crate::panic::catch_unwind(|| T::from_raw_sized(ptr, size)) { User(NonNull::new_userref(v)) } else { rtabort!("Got invalid pointer from alloc() usercall") } } } // ... } // ... impl<T: ?Sized> Drop for User<T> where T: UserSafe, { fn drop(&mut self) { unsafe { let ptr = (*self.0.as_ptr()).0.get(); // vvvvvvvvvvvvv------------------ HERE super::free(ptr as _, size_of_val(&mut *ptr), T::align_of()); } } } ``` This min. alignment optimization was introduced in rust-lang@6f7d193. See below for more details on why. The two usercalls, `super::alloc` and `super::free`, are eventually handled by the host runner. They just delegate to the `System` allocator: See: <https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/mod.rs> ```rust // Host-side / userspace impl<'tcs> IOHandlerInput<'tcs> { // ... #[inline(always)] fn alloc(&self, size: usize, alignment: usize) -> IoResult<*mut u8> { unsafe { // vvvvvvvvv--------------- UNCHANGED let layout = Layout::from_size_align(size, alignment) .map_err(|_| IoErrorKind::InvalidInput)?; if layout.size() == 0 { return Err(IoErrorKind::InvalidInput.into()); } let ptr = System.alloc(layout); if ptr.is_null() { Err(IoErrorKind::Other.into()) } else { Ok(ptr) } } } #[inline(always)] fn free(&self, ptr: *mut u8, size: usize, alignment: usize) -> IoResult<()> { unsafe { // vvvvvvvvv--------------- UNCHANGED let layout = Layout::from_size_align(size, alignment) .map_err(|_| IoErrorKind::InvalidInput)?; if size == 0 { return Ok(()); } Ok(System.dealloc(ptr, layout)) } } // ... } ``` It also appears that `enclave-runner-sgx` assumes that there's no `#[global_allocator]` override (<https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/interface.rs#L333>). For most enclave hosts running stock x86_64-unknown-linux-gnu (glibc malloc), I don't believe this mismatch is currently an issue, since posix `free` ignores the alignment anyway. If you did swap in jemalloc, which does care about the dealloc alignment, then something would definitely go wrong elsewhere, as the you'd have mismatched allocators (`System` above vs `Box<_>`/`Vec<_>` using `Global`). ### Solutions It's not clear that we can round-up the alignment on `free`, since `User::from_raw` exists, and there's various places that call it outside std. We should probably just remove the in-enclave min. alignment until we come up with a more satisfactory solution. My guess is that the right place to do the min. alignment optimization is on enclave-runner-sgx side: <https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/mod.rs#L1596> and other places that hand memory to the SGX enclave. ### Why over-align in the first place? The min. alignment exists for performance reasons (see: `copy_from_userspace`). It's highly preferable if all memory copied from userspace is at least 8 byte aligned, otherwise we have to fallback to a super slow copy routine for the unaligned prefix (and suffix).
… r=JohnTitor std::sys::pal::sgx: fix mismatched alloc/free alignment ### Why the PR? I've got a local `miri` branch that's able to test `x86_64-fortanix-unknown-sgx`, so I can get better assurance about our enclaves. It's now complaining about a bunch of stuff in std :sweat_smile: ### Context 1. `x86_64-fortanix-unknown-sgx` enclaves can request the untrusted host enclave runner to allocate/free memory in userspace and get a pointer to it in return. 2. There's a userspace/enclave space memory split for `x86_64-fortanix-unknown-sgx` enclaves. It's a bit like the userspace/kernel space split, where the kernel doesn't trust pointers from userspace and is very paranoid about copying data to/from userspace. ### Problem In the enclave, `User::new_uninit_bytes` and `User::drop` are requesting the host to alloc/dealloc memory with potentially mismatched alignment, as the enclave side is unconditionally over-aligning on allocation but not doing the same on free. - Ex: `User::<ByteBuffer>` -> `alloc(_, align=8)` -> `drop()` -> `free(_, align=1)` See: <https://github.com/rust-lang/rust/blob/main/library/std/src/sys/pal/sgx/abi/usercalls/alloc.rs> ```rust // Enclave-side impl<T: ?Sized> User<T> where T: UserSafe, { // This function returns memory that is practically uninitialized, but is // not considered "unspecified" or "undefined" for purposes of an // optimizing compiler. This is achieved by returning a pointer from // from outside as obtained by `super::alloc`. fn new_uninit_bytes(size: usize) -> Self { unsafe { // Mustn't call alloc with size 0. let ptr = if size > 0 { // `copy_to_userspace` is more efficient when data is 8-byte aligned let alignment = cmp::max(T::align_of(), 8); // <------------------------- HERE rtunwrap!(Ok, super::alloc(size, alignment)) as _ } else { T::align_of() as _ // dangling pointer ok for size 0 }; if let Ok(v) = crate::panic::catch_unwind(|| T::from_raw_sized(ptr, size)) { User(NonNull::new_userref(v)) } else { rtabort!("Got invalid pointer from alloc() usercall") } } } // ... } // ... impl<T: ?Sized> Drop for User<T> where T: UserSafe, { fn drop(&mut self) { unsafe { let ptr = (*self.0.as_ptr()).0.get(); // vvvvvvvvvvvvv------------------ HERE super::free(ptr as _, size_of_val(&mut *ptr), T::align_of()); } } } ``` This min. alignment optimization was introduced in rust-lang@6f7d193. See below for more details on why. The two usercalls, `super::alloc` and `super::free`, are eventually handled by the host runner. They just delegate to the `System` allocator: See: <https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/mod.rs> ```rust // Host-side / userspace impl<'tcs> IOHandlerInput<'tcs> { // ... #[inline(always)] fn alloc(&self, size: usize, alignment: usize) -> IoResult<*mut u8> { unsafe { // vvvvvvvvv--------------- UNCHANGED let layout = Layout::from_size_align(size, alignment) .map_err(|_| IoErrorKind::InvalidInput)?; if layout.size() == 0 { return Err(IoErrorKind::InvalidInput.into()); } let ptr = System.alloc(layout); if ptr.is_null() { Err(IoErrorKind::Other.into()) } else { Ok(ptr) } } } #[inline(always)] fn free(&self, ptr: *mut u8, size: usize, alignment: usize) -> IoResult<()> { unsafe { // vvvvvvvvv--------------- UNCHANGED let layout = Layout::from_size_align(size, alignment) .map_err(|_| IoErrorKind::InvalidInput)?; if size == 0 { return Ok(()); } Ok(System.dealloc(ptr, layout)) } } // ... } ``` It also appears that `enclave-runner-sgx` assumes that there's no `#[global_allocator]` override (<https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/interface.rs#L333>). For most enclave hosts running stock x86_64-unknown-linux-gnu (glibc malloc), I don't believe this mismatch is currently an issue, since posix `free` ignores the alignment anyway. If you did swap in jemalloc, which does care about the dealloc alignment, then something would definitely go wrong elsewhere, as the you'd have mismatched allocators (`System` above vs `Box<_>`/`Vec<_>` using `Global`). ### Solutions It's not clear that we can round-up the alignment on `free`, since `User::from_raw` exists, and there's various places that call it outside std. We should probably just remove the in-enclave min. alignment until we come up with a more satisfactory solution. My guess is that the right place to do the min. alignment optimization is on enclave-runner-sgx side: <https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/mod.rs#L1596> and other places that hand memory to the SGX enclave. ### Why over-align in the first place? The min. alignment exists for performance reasons (see: `copy_from_userspace`). It's highly preferable if all memory copied from userspace is at least 8 byte aligned, otherwise we have to fallback to a super slow copy routine for the unaligned prefix (and suffix).
… r=JohnTitor std::sys::pal::sgx: fix mismatched alloc/free alignment ### Why the PR? I've got a local `miri` branch that's able to test `x86_64-fortanix-unknown-sgx`, so I can get better assurance about our enclaves. It's now complaining about a bunch of stuff in std :sweat_smile: ### Context 1. `x86_64-fortanix-unknown-sgx` enclaves can request the untrusted host enclave runner to allocate/free memory in userspace and get a pointer to it in return. 2. There's a userspace/enclave space memory split for `x86_64-fortanix-unknown-sgx` enclaves. It's a bit like the userspace/kernel space split, where the kernel doesn't trust pointers from userspace and is very paranoid about copying data to/from userspace. ### Problem In the enclave, `User::new_uninit_bytes` and `User::drop` are requesting the host to alloc/dealloc memory with potentially mismatched alignment, as the enclave side is unconditionally over-aligning on allocation but not doing the same on free. - Ex: `User::<ByteBuffer>` -> `alloc(_, align=8)` -> `drop()` -> `free(_, align=1)` See: <https://github.com/rust-lang/rust/blob/main/library/std/src/sys/pal/sgx/abi/usercalls/alloc.rs> ```rust // Enclave-side impl<T: ?Sized> User<T> where T: UserSafe, { // This function returns memory that is practically uninitialized, but is // not considered "unspecified" or "undefined" for purposes of an // optimizing compiler. This is achieved by returning a pointer from // from outside as obtained by `super::alloc`. fn new_uninit_bytes(size: usize) -> Self { unsafe { // Mustn't call alloc with size 0. let ptr = if size > 0 { // `copy_to_userspace` is more efficient when data is 8-byte aligned let alignment = cmp::max(T::align_of(), 8); // <------------------------- HERE rtunwrap!(Ok, super::alloc(size, alignment)) as _ } else { T::align_of() as _ // dangling pointer ok for size 0 }; if let Ok(v) = crate::panic::catch_unwind(|| T::from_raw_sized(ptr, size)) { User(NonNull::new_userref(v)) } else { rtabort!("Got invalid pointer from alloc() usercall") } } } // ... } // ... impl<T: ?Sized> Drop for User<T> where T: UserSafe, { fn drop(&mut self) { unsafe { let ptr = (*self.0.as_ptr()).0.get(); // vvvvvvvvvvvvv------------------ HERE super::free(ptr as _, size_of_val(&mut *ptr), T::align_of()); } } } ``` This min. alignment optimization was introduced in rust-lang@6f7d193. See below for more details on why. The two usercalls, `super::alloc` and `super::free`, are eventually handled by the host runner. They just delegate to the `System` allocator: See: <https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/mod.rs> ```rust // Host-side / userspace impl<'tcs> IOHandlerInput<'tcs> { // ... #[inline(always)] fn alloc(&self, size: usize, alignment: usize) -> IoResult<*mut u8> { unsafe { // vvvvvvvvv--------------- UNCHANGED let layout = Layout::from_size_align(size, alignment) .map_err(|_| IoErrorKind::InvalidInput)?; if layout.size() == 0 { return Err(IoErrorKind::InvalidInput.into()); } let ptr = System.alloc(layout); if ptr.is_null() { Err(IoErrorKind::Other.into()) } else { Ok(ptr) } } } #[inline(always)] fn free(&self, ptr: *mut u8, size: usize, alignment: usize) -> IoResult<()> { unsafe { // vvvvvvvvv--------------- UNCHANGED let layout = Layout::from_size_align(size, alignment) .map_err(|_| IoErrorKind::InvalidInput)?; if size == 0 { return Ok(()); } Ok(System.dealloc(ptr, layout)) } } // ... } ``` It also appears that `enclave-runner-sgx` assumes that there's no `#[global_allocator]` override (<https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/interface.rs#L333>). For most enclave hosts running stock x86_64-unknown-linux-gnu (glibc malloc), I don't believe this mismatch is currently an issue, since posix `free` ignores the alignment anyway. If you did swap in jemalloc, which does care about the dealloc alignment, then something would definitely go wrong elsewhere, as the you'd have mismatched allocators (`System` above vs `Box<_>`/`Vec<_>` using `Global`). ### Solutions It's not clear that we can round-up the alignment on `free`, since `User::from_raw` exists, and there's various places that call it outside std. We should probably just remove the in-enclave min. alignment until we come up with a more satisfactory solution. My guess is that the right place to do the min. alignment optimization is on enclave-runner-sgx side: <https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/mod.rs#L1596> and other places that hand memory to the SGX enclave. ### Why over-align in the first place? The min. alignment exists for performance reasons (see: `copy_from_userspace`). It's highly preferable if all memory copied from userspace is at least 8 byte aligned, otherwise we have to fallback to a super slow copy routine for the unaligned prefix (and suffix).
Why the PR?
I've got a local
miribranch that's able to testx86_64-fortanix-unknown-sgx, so I can get better assurance about our enclaves. It's now complaining about a bunch of stuff in std 😅Context
x86_64-fortanix-unknown-sgxenclaves can request the untrusted host enclave runner to allocate/free memory in userspace and get a pointer to it in return.There's a userspace/enclave space memory split for
x86_64-fortanix-unknown-sgxenclaves. It's a bit like the userspace/kernel space split, where the kernel doesn't trust pointers from userspace and is very paranoid about copying data to/from userspace.Problem
In the enclave,
User::new_uninit_bytesandUser::dropare requesting the host to alloc/dealloc memory with potentially mismatched alignment, as the enclave side is unconditionally over-aligning on allocation but not doing the same on free.User::<ByteBuffer>->alloc(_, align=8)->drop()->free(_, align=1)See: https://github.com/rust-lang/rust/blob/main/library/std/src/sys/pal/sgx/abi/usercalls/alloc.rs
This min. alignment optimization was introduced in 6f7d193. See below for more details on why.
The two usercalls,
super::allocandsuper::free, are eventually handled by the host runner. They just delegate to theSystemallocator:See: https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/mod.rs
It also appears that
enclave-runner-sgxassumes that there's no#[global_allocator]override (https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/interface.rs#L333).For most enclave hosts running stock x86_64-unknown-linux-gnu (glibc malloc), I don't believe this mismatch is currently an issue, since posix
freeignores the alignment anyway.If you did swap in jemalloc, which does care about the dealloc alignment, then something would definitely go wrong elsewhere, as the you'd have mismatched allocators (
Systemabove vsBox<_>/Vec<_>usingGlobal).Solutions
It's not clear that we can round-up the alignment on
free, sinceUser::from_rawexists, and there's various places that call it outside std.We should probably just remove the in-enclave min. alignment until we come up with a more satisfactory solution. My guess is that the right place to do the min. alignment optimization is on enclave-runner-sgx side: https://github.com/fortanix/rust-sgx/blob/master/intel-sgx/enclave-runner-sgx/src/usercalls/mod.rs#L1596 and other places that hand memory to the SGX enclave.
Why over-align in the first place?
The min. alignment exists for performance reasons (see:
copy_from_userspace). It's highly preferable if all memory copied from userspace is at least 8 byte aligned, otherwise we have to fallback to a super slow copy routine for the unaligned prefix (and suffix).