Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

trait_upcasting unsoundness due to reordered super traits #131813

Closed
lrh2000 opened this issue Oct 17, 2024 · 7 comments · Fixed by #131864
Closed

trait_upcasting unsoundness due to reordered super traits #131813

lrh2000 opened this issue Oct 17, 2024 · 7 comments · Fixed by #131864
Labels
A-coercions Area: implicit and explicit `expr as Type` coercions A-trait-objects Area: trait objects, vtable layout C-bug Category: This is a bug. F-trait_upcasting `#![feature(trait_upcasting)]` I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness requires-nightly This issue requires a nightly compiler in some way. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@lrh2000
Copy link
Contributor

lrh2000 commented Oct 17, 2024

The following program will trigger a segmentation fault. (playground link)

#![feature(trait_upcasting)]

trait Pollable {
    #[allow(unused)]
    fn poll(&self) {}
}
trait FileIo: Pollable + Send + Sync {
    fn read(&self) {}
}
trait Terminal: Send + Sync + FileIo {}

struct A;

impl Pollable for A {}
impl FileIo for A {}
impl Terminal for A {}

fn main() {
    let a = A;

    let b = &a as &dyn Terminal;
    let c = b as &dyn FileIo;
 
    c.read();
}

This is because the vtable layout of Send + Sync + FileIo (i.e., the supertrait of Terminal) is different from that of FileIo, even though (i) Send and Sync are auto traits and (ii) FileIo is Send and Sync. However, it seems that the upcast coerion thinks they are the same, so it reuses the vtable, causing segmentation faults.

vtables
.L__unnamed_1:    # `dyn Terminal`'s vtable
	.asciz	"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
	.quad	playground::FileIo::read    # `Pollable::poll`
	.quad	playground::FileIo::read    # `FileIo::read`

.L__unnamed_2:    # `dyn FileIo`'s vtable
	.asciz	"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
	.quad	playground::FileIo::read    # `Pollable::poll`
	.quad	.L__unnamed_3               # `Send`'s vptr
	.quad	.L__unnamed_3               # `Sync`'s vptr
	.quad	playground::FileIo::read    # `FileIo::read`

I checked how the vtable layout is generated:

segment_visitor(VtblSegment::TraitOwnEntries {
trait_ref: inner_most_trait_ref,
emit_vptr: emit_vptr && !tcx.sess.opts.unstable_opts.no_trait_vptr,
})?;
// If we've emitted (fed to `segment_visitor`) a trait that has methods present in the vtable,
// we'll need to emit vptrs from now on.
if !emit_vptr_on_new_entry
&& has_own_existential_vtable_entries(tcx, inner_most_trait_ref.def_id())
{
emit_vptr_on_new_entry = true;
}

By the current implementation, Send + Sync + FileIo will not have vptrs for Send and Sync because Send and Sync are iterated at the very first during the prepare_vtable_segments_inner method. So emit_vptr_on_new_entry is false when Send and Sync are iterated.

  • However, FileIo will have vptrs for Send and Sync because Send and Sync are iterated after Pollable is iterated. This means that prepare_vtable_segments_inner is true when Send and Sync are iterated.

This sounds a little strange to me.

Patch

I tried the following patch, which makes both Send + Sync + FileIo and FileIo not have vptrs for Send and Sync. I confirmed that the reported problem disappears, but I don't know if I'm on the right track since I'm not familiar with the code. (I can open a PR if it sounds right).

patch
diff --git a/compiler/rustc_trait_selection/src/traits/vtable.rs b/compiler/rustc_trait_selection/src/traits/vtable.rs
index 6e6f948a2cd..ed221e2a183 100644
--- a/compiler/rustc_trait_selection/src/traits/vtable.rs
+++ b/compiler/rustc_trait_selection/src/traits/vtable.rs
@@ -154,18 +154,17 @@ fn prepare_vtable_segments_inner<'tcx, T>(
 
         // emit innermost item, move to next sibling and stop there if possible, otherwise jump to outer level.
         while let Some((inner_most_trait_ref, emit_vptr, mut siblings)) = stack.pop() {
+            let has_entries =
+                has_own_existential_vtable_entries(tcx, inner_most_trait_ref.def_id());
+
             segment_visitor(VtblSegment::TraitOwnEntries {
                 trait_ref: inner_most_trait_ref,
-                emit_vptr: emit_vptr && !tcx.sess.opts.unstable_opts.no_trait_vptr,
+                emit_vptr: emit_vptr && has_entries && !tcx.sess.opts.unstable_opts.no_trait_vptr,
             })?;
 
             // If we've emitted (fed to `segment_visitor`) a trait that has methods present in the vtable,
             // we'll need to emit vptrs from now on.
-            if !emit_vptr_on_new_entry
-                && has_own_existential_vtable_entries(tcx, inner_most_trait_ref.def_id())
-            {
-                emit_vptr_on_new_entry = true;
-            }
+            emit_vptr_on_new_entry |= has_entries;
 
             if let Some(next_inner_most_trait_ref) =
                 siblings.find(|&sibling| visited.insert(sibling.upcast(tcx)))

Cc #65991 (tracking issue for trait_upcasting)

@rustbot label +F-trait_upcasting +A-coercions +A-trait-objects +I-unsound +T-lang +T-type

@lrh2000 lrh2000 added the C-bug Category: This is a bug. label Oct 17, 2024
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Oct 17, 2024
@workingjubilee
Copy link
Member

hey rustbot:

@rustbot label: +F-trait_upcasting +A-coercions +A-trait-objects +I-unsound +T-lang +T-types

@rustbot rustbot added A-coercions Area: implicit and explicit `expr as Type` coercions A-trait-objects Area: trait objects, vtable layout F-trait_upcasting `#![feature(trait_upcasting)]` I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness T-lang Relevant to the language team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue. I-prioritize Issue: Indicates that prioritization has been requested for this issue. labels Oct 17, 2024
@lcnr lcnr added requires-nightly This issue requires a nightly compiler in some way. and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. I-prioritize Issue: Indicates that prioritization has been requested for this issue. labels Oct 17, 2024
@lcnr
Copy link
Contributor

lcnr commented Oct 17, 2024

I would probably continue emitting vtables for ordinary traits which don't have any entries but instead make sure that we never emit any entries for auto traits. cc @compiler-errors @WaffleLapkin

@lcnr lcnr added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. and removed T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Oct 17, 2024
@lcnr
Copy link
Contributor

lcnr commented Oct 17, 2024

slightly minimized

#![feature(trait_upcasting)]

trait Pollable {
    #[allow(unused)]
    fn poll(&self) {}
}
trait FileIo: Pollable + Send {
    fn read(&self) {}
}
trait Terminal: Send + FileIo {}

struct A;

impl Pollable for A {}
impl FileIo for A {}
impl Terminal for A {}

fn main() {
    let a = A;

    let b = &a as &dyn Terminal;
    let c = b as &dyn FileIo;
 
    c.read();
}

@lrh2000
Copy link
Contributor Author

lrh2000 commented Oct 17, 2024

I would probably continue emitting vtables for ordinary traits which don't have any entries but instead make sure that we never emit any entries for auto traits.

The specific issue has nothing to do with auto traits (although I mentioned them in the title and use it as an example throughout the issue description). Hope this is not misleading.

If you replace the Send trait with another empty trait (say Evil), the segmentation fault is still there, at least. (playground link)

@lcnr lcnr changed the title trait_upcasting unsoundness due to reordered Send/Sync trait_upcasting unsoundness due to reordered super traits Oct 17, 2024
@lcnr
Copy link
Contributor

lcnr commented Oct 17, 2024

Okay, that's very good to know 👍 I thought it only impacted auto traits, moved that concern into a separate issue #131813

@WaffleLapkin
Copy link
Member

@lrh2000 the patch you provided looks right to me, please do open a PR :)

Interestingly, I opened #114942 a while ago, but didn't know it can cause unsoundness. I do wander if there are other cases where the ordering of super traits messes up our layout algorithm...

@lrh2000
Copy link
Contributor Author

lrh2000 commented Oct 18, 2024

the patch you provided looks right to me, please do open a PR :)

@WaffleLapkin Thanks for the confirmation! I opened at #131864.

@bors bors closed this as completed in 765e8c7 Oct 18, 2024
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Oct 18, 2024
Rollup merge of rust-lang#131864 - lrh2000:upcast_reorder, r=WaffleLapkin

Never emit `vptr` for empty/auto traits

Emiting `vptr`s for empty/auto traits is unnecessary (rust-lang#114942) and causes unsoundness in `trait_upcasting` (rust-lang#131813). This PR should ensure that we never emit vtables for such traits. See the linked issues for more details.

I'm not sure if I can add tests for the vtable layout. So this PR only adds tests for the soundness hole (i.e., the segmentation fault will disappear after this PR).

Fixes rust-lang#114942
Fixes rust-lang#131813

Cc rust-lang#65991 (tracking issue for `trait_upcasting`)

r? `@WaffleLapkin`  (per rust-lang#131813 (comment))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-coercions Area: implicit and explicit `expr as Type` coercions A-trait-objects Area: trait objects, vtable layout C-bug Category: This is a bug. F-trait_upcasting `#![feature(trait_upcasting)]` I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness requires-nightly This issue requires a nightly compiler in some way. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants