Skip to content

SIGSEGV without unsafe when passing &dyn Trait to &dyn ParentTrait #145752

@chenxiaolong

Description

@chenxiaolong

As a workaround for &dyn A + B + C not being available yet, I've been defining my own empty traits with various combinations of parent traits to use for dynamic dispatch. However, in some scenarios, passing a &dyn Trait to &dyn <parent> for one of that trait's parent traits seems to trigger a SIGSEGV. This is a minimal reproducer I came up with that only uses std and has no unsafe blocks:

use std::io::{self, Cursor, Read, Seek, SeekFrom, Write};

pub trait ReadSeek: Read + Seek {}

impl<R: Read + Seek> ReadSeek for R {}

pub trait WriteSeek: Write + Seek {}

impl<W: Write + Seek> WriteSeek for W {}

pub trait ReadWriteSeek: ReadSeek + WriteSeek {}

impl<W: ReadSeek + WriteSeek> ReadWriteSeek for W {}

fn foo(writer: &mut dyn WriteSeek) -> io::Result<u64> {
    writer.seek(SeekFrom::Start(0))
}

fn bar(file: &mut dyn ReadWriteSeek) -> io::Result<u64> {
    foo(file)
}

fn main() {
    let mut file = Cursor::new(Vec::new());
    bar(&mut file).unwrap();
}

I'm on x86_64 Linux (Fedora 42), but this is reproducible in the Rust playground as well: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=ce176135d95ff18440c95782741706c4

(gdb and valgrind backtraces are included below.)

Meta

rustc --version --verbose:

rustc 1.89.0 (29483883e 2025-08-04)
binary: rustc
commit-hash: 29483883eed69d5fb4db01964cdf2af4d86e9cb2
commit-date: 2025-08-04
host: x86_64-unknown-linux-gnu
release: 1.89.0
LLVM version: 20.1.7

Also happens in the latest nightly build:

rustc 1.91.0-nightly (6ba0ce409 2025-08-21)
binary: rustc
commit-hash: 6ba0ce40941eee1ca02e9ba49c791ada5158747a
commit-date: 2025-08-21
host: x86_64-unknown-linux-gnu
release: 1.91.0-nightly
LLVM version: 21.1.0
Backtrace

gdb:

(gdb) bt
#0  0x000055555555ac8d in core::io::borrowed_buf::BorrowedCursor::written (self=0x7fffffffd0e8)
    at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/core/src/io/borrowed_buf.rs:227
#1  std::io::cursor::{impl#5}::read_buf_exact<alloc::vec::Vec<u8, alloc::alloc::Global>> (self=0x7fffffffd1d0, cursor=...)
    at /builddir/build/BUILD/rust-1.89.0-build/rustc-1.89.0-src/library/std/src/io/cursor.rs:373
#2  0x000055555555f030 in avbroot::foo (writer=...) at src/main.rs:16
#3  0x000055555555f053 in avbroot::bar (file=...) at src/main.rs:20
#4  0x000055555555f09b in avbroot::main () at src/main.rs:25

valgrind:

==29216== Memcheck, a memory error detector
==29216== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==29216== Using Valgrind-3.25.1 and LibVEX; rerun with -h for copyright info
==29216== Command: ./target/debug/segfault_test
==29216== 
==29216== Invalid read of size 8
==29216==    at 0x40079FD: written (borrowed_buf.rs:227)
==29216==    by 0x40079FD: <std::io::cursor::Cursor<T> as std::io::Read>::read_buf_exact (cursor.rs:373)
==29216==    by 0x40093CF: segfault_test::foo (main.rs:16)
==29216==    by 0x40093F2: segfault_test::bar (main.rs:20)
==29216==    by 0x400943A: segfault_test::main (main.rs:25)
==29216==    by 0x400A16A: core::ops::function::FnOnce::call_once (function.rs:250)
==29216==    by 0x4005BCD: std::sys::backtrace::__rust_begin_short_backtrace (backtrace.rs:152)
==29216==    by 0x40092D0: std::rt::lang_start::{{closure}} (rt.rs:206)
==29216==    by 0x40239FF: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:284)
==29216==    by 0x40239FF: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:589)
==29216==    by 0x40239FF: catch_unwind<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:552)
==29216==    by 0x40239FF: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:359)
==29216==    by 0x40239FF: {closure#0} (rt.rs:175)
==29216==    by 0x40239FF: do_call<std::rt::lang_start_internal::{closure_env#0}, isize> (panicking.rs:589)
==29216==    by 0x40239FF: catch_unwind<isize, std::rt::lang_start_internal::{closure_env#0}> (panicking.rs:552)
==29216==    by 0x40239FF: catch_unwind<std::rt::lang_start_internal::{closure_env#0}, isize> (panic.rs:359)
==29216==    by 0x40239FF: std::rt::lang_start_internal (rt.rs:171)
==29216==    by 0x40092B6: std::rt::lang_start (rt.rs:205)
==29216==    by 0x400957D: main (in /home/chenxiaolong/git/github/segfault_test/target/debug/segfault_test)
==29216==  Address 0x10 is not stack'd, malloc'd or (recently) free'd
==29216== 
==29216== Invalid read of size 8
==29216==    at 0x400B0CF: capacity (borrowed_buf.rs:76)
==29216==    by 0x400B0CF: capacity (borrowed_buf.rs:218)
==29216==    by 0x400B0CF: std::io::impls::<impl std::io::Read for &[u8]>::read_buf_exact (impls.rs:371)
==29216==    by 0x4007A3B: <std::io::cursor::Cursor<T> as std::io::Read>::read_buf_exact (cursor.rs:375)
==29216==    by 0x40093CF: segfault_test::foo (main.rs:16)
==29216==    by 0x40093F2: segfault_test::bar (main.rs:20)
==29216==    by 0x400943A: segfault_test::main (main.rs:25)
==29216==    by 0x400A16A: core::ops::function::FnOnce::call_once (function.rs:250)
==29216==    by 0x4005BCD: std::sys::backtrace::__rust_begin_short_backtrace (backtrace.rs:152)
==29216==    by 0x40092D0: std::rt::lang_start::{{closure}} (rt.rs:206)
==29216==    by 0x40239FF: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:284)
==29216==    by 0x40239FF: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:589)
==29216==    by 0x40239FF: catch_unwind<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:552)
==29216==    by 0x40239FF: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:359)
==29216==    by 0x40239FF: {closure#0} (rt.rs:175)
==29216==    by 0x40239FF: do_call<std::rt::lang_start_internal::{closure_env#0}, isize> (panicking.rs:589)
==29216==    by 0x40239FF: catch_unwind<isize, std::rt::lang_start_internal::{closure_env#0}> (panicking.rs:552)
==29216==    by 0x40239FF: catch_unwind<std::rt::lang_start_internal::{closure_env#0}, isize> (panic.rs:359)
==29216==    by 0x40239FF: std::rt::lang_start_internal (rt.rs:171)
==29216==    by 0x40092B6: std::rt::lang_start (rt.rs:205)
==29216==    by 0x400957D: main (in /home/chenxiaolong/git/github/segfault_test/target/debug/segfault_test)
==29216==  Address 0x8 is not stack'd, malloc'd or (recently) free'd
==29216== 
==29216== 
==29216== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==29216==  Access not within mapped region at address 0x8
==29216==    at 0x400B0CF: capacity (borrowed_buf.rs:76)
==29216==    by 0x400B0CF: capacity (borrowed_buf.rs:218)
==29216==    by 0x400B0CF: std::io::impls::<impl std::io::Read for &[u8]>::read_buf_exact (impls.rs:371)
==29216==    by 0x4007A3B: <std::io::cursor::Cursor<T> as std::io::Read>::read_buf_exact (cursor.rs:375)
==29216==    by 0x40093CF: segfault_test::foo (main.rs:16)
==29216==    by 0x40093F2: segfault_test::bar (main.rs:20)
==29216==    by 0x400943A: segfault_test::main (main.rs:25)
==29216==    by 0x400A16A: core::ops::function::FnOnce::call_once (function.rs:250)
==29216==    by 0x4005BCD: std::sys::backtrace::__rust_begin_short_backtrace (backtrace.rs:152)
==29216==    by 0x40092D0: std::rt::lang_start::{{closure}} (rt.rs:206)
==29216==    by 0x40239FF: call_once<(), (dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (function.rs:284)
==29216==    by 0x40239FF: do_call<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panicking.rs:589)
==29216==    by 0x40239FF: catch_unwind<i32, &(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe)> (panicking.rs:552)
==29216==    by 0x40239FF: catch_unwind<&(dyn core::ops::function::Fn<(), Output=i32> + core::marker::Sync + core::panic::unwind_safe::RefUnwindSafe), i32> (panic.rs:359)
==29216==    by 0x40239FF: {closure#0} (rt.rs:175)
==29216==    by 0x40239FF: do_call<std::rt::lang_start_internal::{closure_env#0}, isize> (panicking.rs:589)
==29216==    by 0x40239FF: catch_unwind<isize, std::rt::lang_start_internal::{closure_env#0}> (panicking.rs:552)
==29216==    by 0x40239FF: catch_unwind<std::rt::lang_start_internal::{closure_env#0}, isize> (panic.rs:359)
==29216==    by 0x40239FF: std::rt::lang_start_internal (rt.rs:171)
==29216==    by 0x40092B6: std::rt::lang_start (rt.rs:205)
==29216==    by 0x400957D: main (in /home/chenxiaolong/git/github/segfault_test/target/debug/segfault_test)
==29216==  If you believe this happened as a result of a stack
==29216==  overflow in your program's main thread (unlikely but
==29216==  possible), you can try to increase the size of the
==29216==  main thread stack using the --main-stacksize= flag.
==29216==  The main thread stack size used in this run was 8388608.
==29216== 
==29216== HEAP SUMMARY:
==29216==     in use at exit: 460 bytes in 2 blocks
==29216==   total heap usage: 9 allocs, 7 frees, 2,532 bytes allocated
==29216== 
==29216== LEAK SUMMARY:
==29216==    definitely lost: 0 bytes in 0 blocks
==29216==    indirectly lost: 0 bytes in 0 blocks
==29216==      possibly lost: 0 bytes in 0 blocks
==29216==    still reachable: 460 bytes in 2 blocks
==29216==         suppressed: 0 bytes in 0 blocks
==29216== Rerun with --leak-check=full to see details of leaked memory
==29216== 
==29216== For lists of detected and suppressed errors, rerun with: -s
==29216== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-dyn-traitArea: trait objects, vtable layoutC-bugCategory: This is a bug.I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-criticalCritical priorityT-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.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions