rust-lang / unsafe-code-guidelines

Forum for discussion about what unsafe code can and can't do
https://rust-lang.github.io/unsafe-code-guidelines
Apache License 2.0
653 stars 57 forks source link

A really sad solution to extern type: special case reference-to-ZST #305

Open CAD97 opened 3 years ago

CAD97 commented 3 years ago

The topical issue is that it is common for FFI libraries (and even pure-Rust libraries, sometimes) to use &(), &[Align; 0], &c_void, or some newtype wrappers of such as an opaque shared pointer to "some opaque object" which otherwise fits with Rust's & semantics.

The rustnomicon recommends this ZST approach (with ?Sync, ?Send, ?Unpin markers) for opaque objects, and bindgen uses it, but both manage to only ever mention using it with raw pointers, never with references. However, they also don't warn off using references, and I know it's not uncommon to see in practice (e.g. cxx will freely emit references to extern FFI types).

The problem comes from SB's reborrowing: this reduces the provenance of the reference to just what the ZST type indicates – zero bytes – even though it's trying to represent a type of unknown size rather than one of known zero size.

The proper solution is extern type (and SB to accommodate it), no questions asked. But extern type has been unstable for a long time, and the longer it's unstable, the more code which is using &ZST will emerge.

So I thought of a really sad way of handling this without invasively changing a lot of SB's provenance handling: relax reborrowing for just reference-to-ZST. This explicitly ignores the &Header-to-VLA use-case in favor of minimally patching SB to support &OpaqueZST.

I don't think it'd be as simple as just not emitting reborrow guards for reference-to-ZST, but since real reference-to-ZST can't be used to access memory locations anyway, it should be sound to allow them to carry around (potentially invalidated) provenance, which could be then used when cast back to a pointer.

More formally, with the snippet

let a: *const [u8; 8];
let b = &*(a as *const [u8; 0]);
let c = a as *const [u8; 0] as *const [u8; 8]; 

No matter a's provenance, current SB (AIUI) would give b Shr provenance over 0 bytes, and thus c also has provenance over 0 bytes. Under this "solution", because b is a reference-to-ZST, it would retain Raw provenance equal to that of a (note that gaining Shr provenance to more than 0 bytes would be potentially an unsound race introduction), which would then be inherited by c as well, allowing c to be validly used.

I describe this as a sad solution, because the full solution would relax the requirements to handle &Header-to-VLA as well, and this is an ugly special case in an otherwise very uniform model.

CAD97 commented 3 years ago

Heavily related: #276, #256, and #134.