Stream: t-lang/wg-unsafe-code-guidelines

Topic: Extern static references


DutchGhost (Nov 10 2019 at 13:54, on Zulip):

Hi, I was wondering if it is UB to have references that are 0 in external statics:

pub enum Void {}

extern "C" {
    static V: &'static Void;
}

mod internal {
    #[no_mangle]
    static V: usize = 0;
}

pub fn main() {
    println!("{:p}", unsafe { V });
}

That program wil print 0x00. Now, you might say I had to use unsafe to access the external static, but having invalid references is UB by definition, and I didn't use any unsafe to do that.

DutchGhost (Nov 10 2019 at 14:04, on Zulip):

Oh, related issue seems https://github.com/rust-lang/rust/issues/28179 . Should I add this example there?

simulacrum (Nov 10 2019 at 14:25, on Zulip):

no_mangle can often sort of "sneak in" unsafe, since you're essentially overriding an arbitrary symbol

simulacrum (Nov 10 2019 at 14:26, on Zulip):

but in general, yes, I'd say that program has UB.

gnzlbg (Nov 10 2019 at 14:43, on Zulip):

that's instant UB, and you don't need #[no_mangle] for it

gnzlbg (Nov 10 2019 at 14:44, on Zulip):
extern {
    static V: &'static u8;
}
fn main() { }
gnzlbg (Nov 10 2019 at 14:44, on Zulip):

If V is ever null, that program has UB

DutchGhost (Nov 10 2019 at 14:44, on Zulip):

that's instant UB, and you don't need #[no_mangle] for it

Is it insta UB without accessing the static?

gnzlbg (Nov 10 2019 at 14:45, on Zulip):

Yes, just constructing the static with an invalid value is UB

gnzlbg (Nov 10 2019 at 14:45, on Zulip):

The access happens somewhere on program initialization, when a value is assigned to the static "somehow"

DutchGhost (Nov 10 2019 at 14:46, on Zulip):

Yes, just constructing the static with an invalid value is UB

Is that defined anywere in the reference? Or some place otherwise?

gnzlbg (Nov 10 2019 at 14:46, on Zulip):

In the unsafe code guidelines, that's the validity invariant

gnzlbg (Nov 10 2019 at 14:46, on Zulip):

See here: https://github.com/rust-lang/unsafe-code-guidelines/blob/master/reference/src/glossary.md#validity-and-safety-invariant

gnzlbg (Nov 10 2019 at 14:47, on Zulip):

It's the same kind of UB as a let x: bool = transmute(42);

gnzlbg (Nov 10 2019 at 14:47, on Zulip):

even if you never access the x afterwards, just creating the bool with the invalid value is already UB

DutchGhost (Nov 10 2019 at 14:47, on Zulip):

even if you never access the x afterwards, just creating the bool with the invalid value is already UB

But the difference is that for the transmute, you need to write unsafe

gnzlbg (Nov 10 2019 at 14:48, on Zulip):

sadly, safe Rust != Rust without unsafe

gnzlbg (Nov 10 2019 at 14:48, on Zulip):

extern "C" { ... } declarations are unsound, there are a couple of issues regarding that

Daniel Henry-Mantilla (Nov 10 2019 at 14:48, on Zulip):

That's a bummer.
Could extern static of type T be considered as MaybeUninit<T> until an unsafe access takes place, which under the hoods assume_inits it, hence the unsafety ?

gnzlbg (Nov 10 2019 at 14:49, on Zulip):

https://github.com/rust-lang/unsafe-code-guidelines/issues/198

gnzlbg (Nov 10 2019 at 14:49, on Zulip):
mod a { extern "C" {
    fn foo() -> &'static mut T;
}}
mod b { extern "C" {
      fn foo() -> *mut T;
}
gnzlbg (Nov 10 2019 at 14:50, on Zulip):

There I am providing two incompatible declarations for the foo symbol

gnzlbg (Nov 10 2019 at 14:51, on Zulip):

and b::foo is currently optimized to have the same return type "properties" as a::foo

gnzlbg (Nov 10 2019 at 14:53, on Zulip):

You can cause the same issue with statics, e.g.,

mod a { extern "C" { static V: &'static T; } }
mod b { extern "C" { static V: *const T; } }

where if you access b::V it will be nonnull, dereferenceable, etc. because it gets optimized as a &T

gnzlbg (Nov 10 2019 at 14:54, on Zulip):

even if you never access a::V, accessing b::V with the pointer being null will trigger a miscompilation

Daniel Henry-Mantilla (Nov 10 2019 at 14:54, on Zulip):

We can see extern fn to be a static of the fn(...) / code<ABI> DST type.
Then under my suggestion, these static should all be considered MaybeUninit and only assume_inited on the unsafe access.
In other words, there are no validity invariants applicable on extern declarations until they get used

gnzlbg (Nov 10 2019 at 14:55, on Zulip):

Could extern static of type T be considered as MaybeUninit<T> until an unsafe access takes place, which under the hoods assume_inits it, hence the unsafety ?

@Daniel Henry-Mantilla maybe? I think the simplest thing would be to require unsafe extern "C" { ... } when writing declarations.

Daniel Henry-Mantilla (Nov 10 2019 at 14:55, on Zulip):

Yes, that's a far simpler solution. Could that be done in a future edition ?

gnzlbg (Nov 10 2019 at 14:56, on Zulip):

It's a syntactic change, so I guess so. It's more of a question of whether the churn will be worth it, and whether there are better ways to fix this issue.

gnzlbg (Nov 10 2019 at 14:57, on Zulip):

It's not just extern statics, but extern fn declarations have the same problem.

gnzlbg (Nov 10 2019 at 14:58, on Zulip):

https://github.com/rust-lang/unsafe-code-guidelines/issues/84

Daniel Henry-Mantilla (Nov 10 2019 at 15:09, on Zulip):

extern fns could be seen as a very special cased extern static, in the same fashion as a fn "pointer" could be seen as a &'static code<Abi> reference

gnzlbg (Nov 10 2019 at 15:09, on Zulip):

I've posted and linked this thread here: https://github.com/rust-lang/unsafe-code-guidelines/issues/198#issuecomment-552203032

gnzlbg (Nov 10 2019 at 15:10, on Zulip):

There are maybe two things that are UB: one is the violation of the validity invariant by writing null to FOO. The other one is that for that to happen probably an incompatible definition of FOO exists somewhere.

DutchGhost (Nov 10 2019 at 17:47, on Zulip):

that's instant UB, and you don't need #[no_mangle] for it

extern {
    static V: &'static u8;
}
fn main() { }

Using V there will cause a linker error, so thats what the

mod internal {
    #[no_mangle]
    static V: usize = 0;
}

was for. And that also forces the external static V to be 0 as far as I know.

Last update: Dec 12 2019 at 01:10UTC