Skip to content

HRTB on subtrait unsoundly provides HTRB on supertrait with weaker implied bounds #84591

@steffahn

Description

@steffahn
Member

I’m giving an exploitation below at the end of this description. This is my interpretation of where exactly the unsoundness lies:

If I have a trait hierarchy

trait Subtrait<'a, 'b, R>: Supertrait<'a, 'b> {}
trait Supertrait<'a, 'b> {}

then a higher ranked trait bound (HRTB) like this

for<'a, 'b> Subtrait<'a, 'b, &'b &'a ()>

does/should only apply to such lifetimes 'a, 'b that fulfill the outlives-relation 'a: 'b.
('a: 'b is needed for &'b &'a () to be a valid type.)

However, the bound

for<'a, 'b> Subtrait<'a, 'b, &'b &'a ()>

appears to imply the bound

for<'a, 'b> Supertrait<'a, 'b>,

and in this one, the lifetimes 'a and 'b are universally quantified without any implicit outlives-relation.

This implication is demonstrated below:

fn need_hrtb_subtrait<S>()
where
    S: for<'a, 'b> Subtrait<'a, 'b, &'b &'a ()>,
{
    need_hrtb_supertrait::<S>() // <- this call works
}

fn need_hrtb_supertrait<S>()
where
    S: for<'a, 'b> Supertrait<'a, 'b>,
{
}

(playground)

One could of course debate whether for<'a, 'b> Subtrait<'a, 'b, &'b &'a ()> should perhaps in-fact include a for<'a, 'b> Supertrait<'a, 'b> bound, but that seems kind-of weird IMO, and also the following code demonstrates that you only need Supertrait<'a, 'b> with 'a: 'b for a fully generic Subtrait<'a, 'b, &'b &'a ()> implementation:

struct MyStruct;
impl<'a: 'b, 'b> Supertrait<'a, 'b> for MyStruct {}
impl<'a, 'b> Subtrait<'a, 'b, &'b &'a ()> for MyStruct {}

fn main() {
    need_hrtb_subtrait::<MyStruct>();
}

(playground)


Finally, here’s how to turn this into actual UB:

trait Subtrait<'a, 'b, R>: Supertrait<'a, 'b> {}
trait Supertrait<'a, 'b> {
    fn convert<T: ?Sized>(x: &'a T) -> &'b T;
}

fn need_hrtb_subtrait<'a_, 'b_, S, T: ?Sized>(x: &'a_ T) -> &'b_ T
where
    S: for<'a, 'b> Subtrait<'a, 'b, &'b &'a ()>,
{
    need_hrtb_supertrait::<S, T>(x) // <- this call works
}

fn need_hrtb_supertrait<'a_, 'b_, S, T: ?Sized>(x: &'a_ T) -> &'b_ T
where
    S: for<'a, 'b> Supertrait<'a, 'b>,
{
    S::convert(x)
}

struct MyStruct;
impl<'a: 'b, 'b> Supertrait<'a, 'b> for MyStruct {
    fn convert<T: ?Sized>(x: &'a T) -> &'b T {
        x
    }
}
impl<'a, 'b> Subtrait<'a, 'b, &'b &'a ()> for MyStruct {}

fn extend_lifetime<'a, 'b, T: ?Sized>(x: &'a T) -> &'b T {
    need_hrtb_subtrait::<MyStruct, T>(x)
}

fn main() {
    let d;
    {
        let x = "Hello World".to_string();
        d = extend_lifetime(&x);
    }
    println!("{}", d);
}
��~��U�

(playground)

This demonstration compiles since Rust 1.7.

@rustbot modify labels: T-compiler, A-traits, A-lifetimes, A-typesystem
and someone please add “I-unsound 💥”.

Activity

added
A-lifetimesArea: Lifetimes / regions
T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
on Apr 26, 2021
changed the title [-]HRTBs are unsound: HRTB transfers to subtraits with weaker implied bounds.[/-] [+]HRTBs are unsound: HRTB on supertrait provides HTRB on subtrait with weaker implied bounds.[/+] on Apr 26, 2021
added
I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
on Apr 26, 2021
added
I-prioritizeIssue: Indicates that prioritization has been requested for this issue.
on Apr 26, 2021
jackh726

jackh726 commented on Apr 26, 2021

@jackh726
Member

Just wanted to write a couple thoughts:
First,

trait Supertrait<'a, 'b, R>: Subtrait<'a, 'b> {}

is kind of the wrong naming. Here Subtrait is actually a supertrait of Supertrait. Naming is backwards.

Second, I wonder if this might just be solved to make this fn convert<T: ?Sized>(x: &'a T) -> &'b T; not allowed as-is. Theoretically, that shouldn't be WF unless 'a: 'b.

steffahn

steffahn commented on Apr 26, 2021

@steffahn
MemberAuthor

Here Subtrait is actually a supertrait of Supertrait. Naming is backwards.

How did I miss this… One moment, let me fix this…… Edit: Done.

I wonder if this might just be solved to make this fn convert<T: ?Sized>(x: &'a T) -> &'b T; not allowed as-is. Theoretically, that shouldn't be WF unless 'a: 'b.

Why should it not be WF? The implementation could just panic!() unconditionally. The only bounds necessary for the type are T: 'a and T: 'b. By the way are you referring to the declaration of the implementation of this method that should be rejected? Edit: Probably referring to the declaration, since the implementation does come with a 'a: 'b bound anyways.

changed the title [-]HRTBs are unsound: HRTB on supertrait provides HTRB on subtrait with weaker implied bounds.[/-] [+]HRTBs are unsound: HRTB on subtrait provides HTRB on supertrait with weaker implied bounds.[/+] on Apr 26, 2021
JohnTitor

JohnTitor commented on Apr 28, 2021

@JohnTitor
Member

Assigning P-critical as discussed as part of the Prioritization Working Group procedure and removing I-prioritize.

added and removed
I-prioritizeIssue: Indicates that prioritization has been requested for this issue.
on Apr 28, 2021

24 remaining items

added a commit that references this issue on Apr 24, 2023
42467d5
added
S-bug-has-testStatus: This bug is tracked inside the repo by a `known-bug` test.
on Apr 25, 2023
moved this to blocked on new solver in T-types unsound issueson Nov 15, 2023
added
A-implied-boundsArea: Implied bounds / inferred outlives-bounds
A-higher-rankedArea: Higher-ranked things (e.g., lifetimes, types, trait bounds aka HRTBs)
on Sep 24, 2024
lcnr

lcnr commented on Aug 19, 2025

@lcnr
Contributor

Talking about implied bounds with @BoxyUwU, she noted that implied bounds on binders may be used to add constraints to free regions. So when projecting from a subtrait to a super trait, we need to check whether this weakens implied bounds of the subtrait even if the super trait does not reference any bound regions:

trait Super<T, U> {
    fn cast(x: T) -> U;
}
impl<'a, 'b: 'a, T> Super<&'b str, &'a str> for T {
    fn cast(x: &'b str) -> &'a str {
        x
    }
}

trait Trait<L, R, T, U>: Super<T, U> {}

impl<'a, 'b, 'c, T> Trait<&'a &'c (), &'c &'b (), &'b str, &'a str> for T {}

fn cast<'a, 'b, T>(x: &'b str) -> &'a str
where
    T: for<'c> Trait<&'a &'c (), &'c &'b (), &'b str, &'a str>,
{
    T::cast(x) // `T: Super<&'b str, &'a str>` is not higher ranked
}

fn main() {
    fn inner() -> &'static str {
        cast::<()>(&format!("hello"))
    }

    let x = inner();
    println!("x: {}", x);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-higher-rankedArea: Higher-ranked things (e.g., lifetimes, types, trait bounds aka HRTBs)A-implied-boundsArea: Implied bounds / inferred outlives-boundsA-lifetimesArea: Lifetimes / regionsA-trait-systemArea: Trait systemA-type-systemArea: Type systemC-bugCategory: This is a bug.I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-highHigh priorityS-bug-has-testStatus: This bug is tracked inside the repo by a `known-bug` test.T-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

    new solver everywhere

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @nikomatsakis@pnkfelix@oli-obk@jonas-schievink@steffahn

        Issue actions

          HRTB on subtrait unsoundly provides HTRB on supertrait with weaker implied bounds · Issue #84591 · rust-lang/rust