Skip to content

std::sys::pal::sgx: fix mismatched alloc/free alignment - #161895

Merged
rust-bors[bot] merged 1 commit into
rust-lang:mainfrom
phlip9:phlip9/fix-sgx-alloc-align
Sep 5, 2026
Merged

std::sys::pal::sgx: fix mismatched alloc/free alignment#161895
rust-bors[bot] merged 1 commit into
rust-lang:mainfrom
phlip9:phlip9/fix-sgx-alloc-align

Conversation

@phlip9

@phlip9 phlip9 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

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 😅

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

// 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 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

// 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).

@rustbot rustbot added O-SGX Target: SGX S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Aug 27, 2026
@rustbot

rustbot commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

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:

  • Owners of files modified in this PR: @ChrisDenton, libs
  • @ChrisDenton, libs expanded to 13 candidates
  • Random selection from ChrisDenton, JohnTitor, Mark-Simulacrum, clarfonthey, nia-e

@rustbot

This comment has been minimized.

@phlip9
phlip9 force-pushed the phlip9/fix-sgx-alloc-align branch from f5080fe to 3e5c0fa Compare August 27, 2026 23:06
@phlip9

phlip9 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

cc @jethrogb @raoulstrackx

@tvsfx

tvsfx commented Sep 4, 2026

Copy link
Copy Markdown

Thanks for the detailed report! I can't approve this, but FWIW the fix looks fine to me.

Naively fixing alignment in drop would indeed run into issues with from_raw (and from_raw_parts) . Additionally, there's also into_raw which has the dual issue. For example, it is used here in a batched, similarly non-aligned version of free. We could add an alignment requirement to from_raw and friends but we cannot increase alignment of User with something like #[repr(align(8))], because we support less aligned data.

I think making the runner responsible for this optimization is a good idea, since it's not security-critical.

@raoulstrackx

Copy link
Copy Markdown
Contributor

Thanks for the PR @phlip9 and the additional review @tvsfx. I also approve, but also lack write access.

@JohnTitor JohnTitor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay! @bors r+

View changes since this review

@rust-bors

rust-bors Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📌 Commit 3e5c0fa has been approved by JohnTitor

It is now in the queue for this repository.

@rust-bors rust-bors Bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 4, 2026
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Sep 4, 2026
… 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).
GuillaumeGomez added a commit to GuillaumeGomez/rust that referenced this pull request Sep 4, 2026
… 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).
GuillaumeGomez added a commit to GuillaumeGomez/rust that referenced this pull request Sep 4, 2026
… 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).
rust-bors Bot pushed a commit that referenced this pull request Sep 4, 2026
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)
GuillaumeGomez added a commit to GuillaumeGomez/rust that referenced this pull request Sep 4, 2026
… 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).
rust-bors Bot pushed a commit that referenced this pull request Sep 4, 2026
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)
@GuillaumeGomez

Copy link
Copy Markdown
Member

Failed in #162300 (comment).

@bors r-

@rust-bors rust-bors Bot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Sep 4, 2026
@rust-bors

rust-bors Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

This pull request was unapproved.

This PR was contained in a rollup (#162300), which was unapproved.

View changes since this unapproval

`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).
@phlip9
phlip9 force-pushed the phlip9/fix-sgx-alloc-align branch from 3e5c0fa to 14ac84f Compare September 4, 2026 20:34
@rustbot

rustbot commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

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.

@phlip9

phlip9 commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

Ugh, sorry bout that. My bootstrap.toml was messed up it seems... Should be fixed now.

@JohnTitor

Copy link
Copy Markdown
Member

@bors r+

@rust-bors

rust-bors Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📌 Commit 14ac84f has been approved by JohnTitor

It is now in the queue for this repository.

@rust-bors rust-bors Bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Sep 4, 2026
Zalathar added a commit to Zalathar/rust that referenced this pull request Sep 5, 2026
… 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).
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Sep 5, 2026
… 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).
rust-bors Bot pushed a commit that referenced this pull request Sep 5, 2026
Rollup of 14 pull requests

Successful merges:

 - #162324 (miri subtree update)
 - #162170 (bootstrap: use target's LLVM libdir when cross-compiling)
 - #158312 (Adds support for AArch64 SVE to inline assembly)
 - #159792 (A more readable debug map for IndexMaps)
 - #160745 (make closures act like MaybeDangling)
 - #161263 (break rustc_expand-rustc_middle dependency)
 - #161895 (std::sys::pal::sgx: fix mismatched alloc/free alignment)
 - #161940 (Promote `wasm32-wasip3` to a tier 2 target)
 - #161397 (coverage: Tidy tests and add some new ones)
 - #161616 (Report precondition violation for `<usize as SliceIndex>::get_unchecked` in const-eval)
 - #162248 (Add regression test for unsized const parameter default ICE)
 - #162250 (Fix hashing of span end columns in incremental compilation)
 - #162265 (cargotest: add lockfiles)
 - #162318 (bootstrap: Fix broken path for `./x doc compiler/rustc --open`)
@rust-bors
rust-bors Bot merged commit b2fa854 into rust-lang:main Sep 5, 2026
13 checks passed
@rustbot rustbot added this to the 1.100.0 milestone Sep 5, 2026
rust-bors Bot pushed a commit that referenced this pull request Sep 5, 2026
Rollup merge of #161895 - phlip9:phlip9/fix-sgx-alloc-align, 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 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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

O-SGX Target: SGX S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants