rust-lang / rust-lang/rust

`iter::Rev`'s `TrustedLen` impl is unsound with trait objects

Open
#160,045 10 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

A-dyn-trait A-iterators A-specialization C-bug I-unsound requires-nightly T-libs T-types
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

This unsoundness exploit uses the same construction as #154619, #156917, and #156920.

Tracking issue for TrustedLen: https://github.com/rust-lang/rust/issues/37572

The following code compiles and causes a segfault at run time. (The explanation of the issue is below the code.)

#![feature(trusted_len)]

use std::iter::{self, TrustedLen};

trait Trait: Iterator<Item = i32> + TrustedLen {}
impl DoubleEndedIterator for dyn Trait + '_ {
    fn next_back(&mut self) -> Option<i32> {
        Some(1)
    }
}

impl Trait for iter::Empty<i32> {}

fn main() {
    let mut iter = iter::empty::<i32>();
    let dyn_trait: &mut dyn Trait = &mut iter;
    let _v: Vec<i32> = dyn_trait.rev().collect();
    println!("done");
}

The way I see this issue, the root of the unsoundness starts from an ambiguity in the documentation of TrustedLen, which says:

The iterator must produce exactly the number of elements it reported or diverge before reaching the end.

It is not clear what "produce" means, when DoubleEndedIterator is involved. There are two possible interpretations of this:

  • Interpretation 1: implementing TrustedLen does not impose any requirements on the behavior of DoubleEndedIterator. That is, calling .next_back() repeatedly is not guaranteed to produce the number of elements specified by .size_hint().
  • Interpretation 2: implementing TrustedLen means that the number of elements produced, by .next() or .next_back() combined, must total to the number specified by .size_hint().

The iter::Rev iterator has the following implementation for TrustedLen:

unsafe impl<I> TrustedLen for Rev<I> where I: TrustedLen + DoubleEndedIterator {}

This implementation implicitly assumes that TrustedLen's safety requirement is to be interpreted as per interpretation 2, since Rev's .next() method delegates to the .next_back() method of the iterator inside. So, Rev<I> being a well-behaved TrustedLen requires I to have its DoubleEndedIterator impl to be well-behaved as per TrustedLen's interpretation 2.

Therefore, if we can create a type which implements TrustedLen correctly according to interpretation 1, but incorrectly according to interpretation 2, we can cause unsoundness.


To concretely produce unsoundness, we have a Trait which is a subtrait of TrustedLen. This essentially results in an implicit impl TrustedLen for dyn Trait + '_, generated by the compiler.

As TrustedLen is a subtrait of Iterator, this means that dyn Trait's Iterator impl has to inherit the behavior of the concrete type behind the trait object. That is, this compiler-generated TrustedLen impl is correct according to interpretation 1.

We then implement DoubleEndedIterator on dyn Trait, such that its length disagrees with .size_hint(). This causes the compiler-generated TrustedLen impl to be incorrect according to interpretation 2.

We then create a Rev<&mut dyn Trait>, which "transfers" the incorrectness according to interpretation 2, into incorrectness according to interpretation 1. That is, Rev<&mut dyn Trait> implements TrustedLen incorrectly according to both interpretation 1 and 2. (Note that this also involves the implementation of TrustedLen on &mut)

We then collect the iterator into a Vec. Vec's specialization sees that Rev<&mut dyn Trait> is TrustedLen, and therefore trusts the .size_hint() method of the iterator. This size_hint() returns 0, since it's from iter::Empty's Iterator impl, so the Vec reserves space for zero elements. However, calling .next() on the Rev<&mut dyn Trait> delegates to .next_back() on dyn Trait, which produces infinitely many elements. This writes out of bounds and eventually causes a segfault.

Meta

Reproducible on the playground with version 1.99.0-nightly (2026-07-27 09ee43b2d6055539771b)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the example with the nightly compiler, then read the TrustedLen and Rev documentation and the linked implementations for &mut and Vec collection. Trace how dyn Trait, Rev, and collect combine their length assumptions; done means the unsound behavior is resolved without allowing the reported out-of-bounds write.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers, security
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.