Any way to prevent "fake statics" in my code?

Today I revisited Implied bounds on nested references + variance = soundness hole · Issue #25860 · rust-lang/rust · GitHub and understood that some of my code looks eerily similar to some of exploits there. Is there any universal way to check whether my code uses that bug, or should I just be squinting my eyes hard enough?

1 Like

It's extremely unlikely that you end up hitting this bug by accident. You need a combination of:

  1. The type must be !Sync due to some library invariant.
  2. The type must not contain an UnsafeCell and it must not be a ZST.
  3. It must be possible to construct the value in const context.

Even coming up with a type that satisfies just 1 and 2 is going to be extremely hard. I mean, sure, there are contrived examples in the thread, but I can't think of even a single Rust crate or project out there that does something like this in practice.

What factors are there in your code that are similar to this bug?

2 Likes

Hmm… if you don’t actually need the variance, you could consider using some kind of function-pointer type that’s not actually covariant. I do believe this particular soundness whole does need the full combination of higher-ranked and variance. Only function pointers and trait objects are higher-ranked, only the former have the needed variance.

So for instance, if your code does use Box<dyn Fn…> kinds of types, it’s already not really possible to run into this; similarly, if you have a reason to prefer using function pointers instead, you could probably ensure invariance in a number of other ways… like wrapping some things in invariant wrapper types, including extra PhantomData arguments to the function pointers… etc.

I’d be curious to see a bit more concretely what your actual code looks like to get a feeling for if there’s anything to worry about in the first place, and also to be able to give more concrete/actionable ideas on how to safeguard things.

If you do need the variance of function pointer types… then maybe “squinting hard enough” may be the last remaining option? :sweat_smile:


@alice

I’m not quite sure what this is referencing – are you talking rvalue static promotion? This issue #25860 has nothing to do with that (though the title of this URLO thread did make me, too, first think about this might be about static promotions; but reading the OP, I think they don’t mean anything like that by “fake statics” but instead just meant unsoundly achieved 'static lifetimes ^^)

2 Likes

Are you referring to the link posted? You probably meant a bug with rvalue promotion? I was talking about another bug that allows extending lifetimes.

Yes sorry I mistakenly assumed you were talking about Constants can contain references that are not Sync · Issue #49206 · rust-lang/rust · GitHub based on the title. I even clicked your link and looked at it. Not sure how I confused them.

2 Likes

I suppose, it can probably be done to define an invariant wrapper type around the whole function pointer – so you’d use something like Wrapped<fn(_, &'a T, &()) -> &'static T> instead of fn(_, &'a T, &()) -> &'static T, that’s like struct Wrapped<T>(T, PhantomData<fn(T) -> T>); or something like that. To make this most convenient, you could even give it some compilation flag to switch between that and type Wrapped<T> = T; – add some (also cfg’d) helper functions for wrapping and unwrapping, and use nightly features to delegate-implement all Fn traits for Wrapped<F> (and also implement Copy of course). (The cfg is so that you can use your crate normally on stable rust, and only use the struct-based invariant wrapper for testing purposes, which can reasonably make use of nightly). Having a wrapper has the added benefit of marking all places where you create the fn pointers in the first place, so that you can manually pay attention that you aren’t changing up your function signatures through implicit conversions before the value was wrapped in the first place.[1]


  1. I’d probably experiment a bit with how reliable this safe-guard is at presenting the test cases in the issue, and also whether or not it’s possible to accidentally run into coercions on constructing a Wrapped function pointer in the first place—I’m not quite sure off the top of my head if it would even allow the implicit coercion there or not. ↩︎

So forcing lifetimes to be invariant… To be honest I've always struggled in the opposite direction :sweat_smile:. And btw you don't necessarily need function pointers, traits are enough: GitHub · Where software is built .