Skip to content

Lifetime bounds of Drop aren't checked properly #148854

@theemathas

Description

@theemathas

This unsoundness is an exploitation of the weirdness in #115175.

The below code causes use-after-free in safe code. (Prints garbage data in my testing.)

In the function extend, rust should have prohibited constructing the type Outer::<T, Inner<T>>, since Inner has a T: 'static bound, while T doesn't have such a lifetime bound in extend. As a result, rust runs the destructor of Outer with incorrect lifetimes, which runs the into_dyn function of Inner with incorrect lifetimes, which then allows unsound lifetime extension.

(Trying to do anything with the Outer::<T, Inner<T>> value other than implicitly running its destructor seems to cause a compile error.)

(Edit: Simplified the code to remove one Drop impl.)

use std::cell::OnceCell;
use std::fmt::Display;
use std::marker::PhantomData;
use std::rc::Rc;

type Storage = Rc<OnceCell<Box<dyn Display + 'static>>>;

trait IntoDyn<T> {
    fn into_dyn(input: T, output: Storage);
}

struct Inner<T: Display + 'static>(PhantomData<T>);
impl<T: Display> IntoDyn<T> for Inner<T> {
    fn into_dyn(input: T, output: Storage) {
        output
            .set(Box::new(input))
            .ok()
            .unwrap();
    }
}

struct Outer<T, U: IntoDyn<T>> {
    input: Option<T>,
    output: Storage,
    _phantom: PhantomData<U>,
}
impl<T, U: IntoDyn<T>> Drop for Outer<T, U> {
    fn drop(&mut self) {
        U::into_dyn(self.input.take().unwrap(), self.output.clone());
    }
}

fn extend<T: Display>(x: T) -> Box<dyn Display + 'static> {
    let storage = Rc::new(OnceCell::new());
    {
        let _ = Outer::<T, Inner<T>> {
            input: Some(x),
            output: storage.clone(),
            _phantom: PhantomData,
        };
    }
    Rc::into_inner(storage).unwrap().into_inner().unwrap()
}

fn main() {
    let wrong = {
        let data = String::from("abc");
        extend::<&String>(&data)
    };
    println!("{wrong}");
}

Meta

Reproducible on the playground with stable rust 1.91.1, and with nightly rust 1.93.0-nightly (2025-11-10 29a69716f2c0f19b5f91)

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-NLLArea: Non-lexical lifetimes (NLL)A-borrow-checkerArea: The borrow checkerA-destructorsArea: Destructors (`Drop`, …)A-lifetimesArea: Lifetimes / regionsC-bugCategory: This is a bug.I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-highHigh 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

    Status

    open/unblocked

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions