Skip to content

trait_upcasting unsoundness due to reordered super traits #131813

Closed
@lrh2000

Description

@lrh2000

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-coercionsArea: implicit and explicit `expr as Type` coercionsA-dyn-traitArea: trait objects, vtable layoutC-bugCategory: This is a bug.F-trait_upcasting`#![feature(trait_upcasting)]`I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-typesRelevant to the types team, which will review and decide on the PR/issue.requires-nightlyThis issue requires a nightly compiler in some way.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions