Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP PROOF-OF-CONCEPT: experiment with very strict pointer provenance #95199

Closed
wants to merge 10 commits into from
20 changes: 11 additions & 9 deletions compiler/rustc_arena/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#![feature(decl_macro)]
#![feature(rustc_attrs)]
#![cfg_attr(test, feature(test))]
#![feature(strict_provenance)]

use smallvec::SmallVec;

Expand Down Expand Up @@ -87,7 +88,7 @@ impl<T> ArenaChunk<T> {
unsafe {
if mem::size_of::<T>() == 0 {
// A pointer as large as possible for zero-sized elements.
!0 as *mut T
ptr::invalid_mut(!0)
} else {
self.start().add(self.storage.len())
}
Expand Down Expand Up @@ -199,7 +200,7 @@ impl<T> TypedArena<T> {
unsafe {
if mem::size_of::<T>() == 0 {
self.ptr.set((self.ptr.get() as *mut u8).wrapping_offset(1) as *mut T);
let ptr = mem::align_of::<T>() as *mut T;
let ptr = ptr::NonNull::<T>::dangling().as_ptr();
// Don't drop the object. This `write` is equivalent to `forget`.
ptr::write(ptr, object);
&mut *ptr
Expand All @@ -216,7 +217,7 @@ impl<T> TypedArena<T> {

#[inline]
fn can_allocate(&self, additional: usize) -> bool {
let available_bytes = self.end.get() as usize - self.ptr.get() as usize;
let available_bytes = self.end.get().addr() - self.ptr.get().addr();
let additional_bytes = additional.checked_mul(mem::size_of::<T>()).unwrap();
available_bytes >= additional_bytes
}
Expand Down Expand Up @@ -262,7 +263,7 @@ impl<T> TypedArena<T> {
// If a type is `!needs_drop`, we don't need to keep track of how many elements
// the chunk stores - the field will be ignored anyway.
if mem::needs_drop::<T>() {
let used_bytes = self.ptr.get() as usize - last_chunk.start() as usize;
let used_bytes = self.ptr.get().addr() - last_chunk.start().addr();
last_chunk.entries = used_bytes / mem::size_of::<T>();
}

Expand All @@ -288,9 +289,9 @@ impl<T> TypedArena<T> {
// chunks.
fn clear_last_chunk(&self, last_chunk: &mut ArenaChunk<T>) {
// Determine how much was filled.
let start = last_chunk.start() as usize;
let start = last_chunk.start().addr();
// We obtain the value of the pointer to the first uninitialized element.
let end = self.ptr.get() as usize;
let end = self.ptr.get().addr();
// We then calculate the number of elements to be dropped in the last chunk,
// which is the filled area's length.
let diff = if mem::size_of::<T>() == 0 {
Expand Down Expand Up @@ -395,15 +396,16 @@ impl DroplessArena {
/// request.
#[inline]
fn alloc_raw_without_grow(&self, layout: Layout) -> Option<*mut u8> {
let start = self.start.get() as usize;
let end = self.end.get() as usize;
let start = self.start.get().addr();
let old_end = self.end.get();
let end = old_end.addr();

let align = layout.align();
let bytes = layout.size();

let new_end = end.checked_sub(bytes)? & !(align - 1);
if start <= new_end {
let new_end = new_end as *mut u8;
let new_end = old_end.with_addr(new_end);
self.end.set(new_end);
Some(new_end)
} else {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_codegen_ssa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#![feature(once_cell)]
#![feature(nll)]
#![feature(associated_type_bounds)]
#![feature(strict_provenance)]
#![recursion_limit = "256"]
#![allow(rustc::potential_query_instability)]

Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_codegen_ssa/src/mono_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ impl<'a, 'tcx: 'a> MonoItemExt<'a, 'tcx> for MonoItem<'tcx> {
fn to_raw_string(&self) -> String {
match *self {
MonoItem::Fn(instance) => {
format!("Fn({:?}, {})", instance.def, instance.substs.as_ptr() as usize)
format!("Fn({:?}, {})", instance.def, instance.substs.as_ptr().addr())
}
MonoItem::Static(id) => format!("Static({:?})", id),
MonoItem::GlobalAsm(id) => format!("GlobalAsm({:?})", id),
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_data_structures/src/tagged_ptr.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// FIXME(strict_provenance_magic): rustc is grounded for pointer crimes.
#![cfg_attr(not(bootstrap), allow(fuzzy_provenance_casts))]

//! This module implements tagged pointers.
//!
//! In order to utilize the pointer packing, you must have two types: a pointer,
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_interface/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// FIXME(strict_provenance_magic): rustc is grounded for pointer crimes.
#![cfg_attr(not(bootstrap), allow(fuzzy_provenance_casts))]

use libloading::Library;
use rustc_ast as ast;
use rustc_codegen_ssa::traits::CodegenBackend;
Expand Down
36 changes: 36 additions & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2648,6 +2648,41 @@ declare_lint! {
};
}

declare_lint! {
/// The `fuzzy_provenance_casts` lint detects an `as` cast between an integer
/// and a pointer.
///
/// ### Example
///
/// fn main() {
/// let my_ref = &0;
/// let my_addr = my_ref as usize;
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// Casting a pointer to an integer or an integer to a pointer is a lossy operation,
/// because beyond just an *address* a pointer may be associated with a particular
/// *provenance* and *segment*. This information is required by both the compiler
/// and the hardware to correctly execute your code. If you need to do this kind
/// of operation, use ptr::addr and ptr::with_addr.
///
/// This is a [future-incompatible] lint to transition this to a hard error
/// in the future. See [issue #9999999] for more details.
///
/// [future-incompatible]: ../index.md#future-incompatible-lints
/// [issue #9999999]: https://github.com/rust-lang/rust/issues/9999999
pub FUZZY_PROVENANCE_CASTS,
Allow,
"A lossy pointer-integer integer cast is used",
@future_incompatible = FutureIncompatibleInfo {
reference: "issue #9999999 <https://github.com/rust-lang/rust/issues/9999999>",
};
}

declare_lint! {
/// The `const_evaluatable_unchecked` lint detects a generic constant used
/// in a type.
Expand Down Expand Up @@ -3101,6 +3136,7 @@ declare_lint_pass! {
UNSAFE_OP_IN_UNSAFE_FN,
INCOMPLETE_INCLUDE,
CENUM_IMPL_DROP_CAST,
FUZZY_PROVENANCE_CASTS,
CONST_EVALUATABLE_UNCHECKED,
INEFFECTIVE_UNSTABLE_TRAIT_IMPL,
MUST_NOT_SUSPEND,
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_middle/src/ty/adt.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// FIXME(strict_provenance_magic): rustc is grounded for pointer crimes.
#![cfg_attr(not(bootstrap), allow(fuzzy_provenance_casts))]

use crate::mir::interpret::ErrorHandled;
use crate::ty;
use crate::ty::util::{Discr, IntTypeExt};
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_middle/src/ty/context.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
//! Type context book-keeping.

// FIXME(strict_provenance_magic): rustc is grounded for pointer crimes.
#![cfg_attr(not(bootstrap), allow(fuzzy_provenance_casts))]

use crate::arena::Arena;
use crate::dep_graph::{DepGraph, DepKind, DepKindStruct};
use crate::hir::place::Place as HirPlace;
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_middle/src/ty/impls_ty.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! This module contains `HashStable` implementations for various data types
//! from `rustc_middle::ty` in no particular order.

// FIXME(strict_provenance_magic): rustc is grounded for pointer crimes.
#![cfg_attr(not(bootstrap), allow(fuzzy_provenance_casts))]

use crate::middle::region;
use crate::mir;
use crate::ty;
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_middle/src/ty/list.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// FIXME(strict_provenance_magic): rustc is grounded for pointer crimes.
#![cfg_attr(not(bootstrap), allow(fuzzy_provenance_casts))]

use crate::arena::Arena;
use rustc_serialize::{Encodable, Encoder};
use std::alloc::Layout;
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_middle/src/ty/subst.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// Type substitutions.

// FIXME(strict_provenance_magic): rustc is grounded for pointer crimes.
#![cfg_attr(not(bootstrap), allow(fuzzy_provenance_casts))]

use crate::mir;
use crate::ty::codec::{TyDecoder, TyEncoder};
use crate::ty::fold::{FallibleTypeFolder, TypeFoldable, TypeFolder, TypeVisitor};
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// FIXME(strict_provenance_magic): rustc is grounded for pointer crimes.
#![cfg_attr(not(bootstrap), allow(fuzzy_provenance_casts))]

use super::diagnostics::SnapshotParser;
use super::pat::{CommaRecoveryMode, RecoverColon, RecoverComma, PARAM_EXPECTED};
use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
Expand Down
64 changes: 60 additions & 4 deletions compiler/rustc_typeck/src/check/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -807,11 +807,22 @@ impl<'a, 'tcx> CastCheck<'tcx> {

// ptr -> *
(Ptr(m_e), Ptr(m_c)) => self.check_ptr_ptr_cast(fcx, m_e, m_c), // ptr-ptr-cast
(Ptr(m_expr), Int(_)) => self.check_ptr_addr_cast(fcx, m_expr), // ptr-addr-cast
(FnPtr, Int(_)) => Ok(CastKind::FnPtrAddrCast),

// * -> ptr
(Int(_), Ptr(mt)) => self.check_addr_ptr_cast(fcx, mt), // addr-ptr-cast
// ptr-addr-cast
(Ptr(m_expr), Int(_)) => {
self.fuzzy_provenance_ptr2int_lint(fcx, t_from);
self.check_ptr_addr_cast(fcx, m_expr)
}
(FnPtr, Int(_)) => {
self.fuzzy_provenance_ptr2int_lint(fcx, t_from);
Ok(CastKind::FnPtrAddrCast)
}
// addr-ptr-cast
(Int(_), Ptr(mt)) => {
self.fuzzy_provenance_int2ptr_lint(fcx);
self.check_addr_ptr_cast(fcx, mt)
}
// fn-ptr-cast
(FnPtr, Ptr(mt)) => self.check_fptr_ptr_cast(fcx, mt),

// prim -> prim
Expand Down Expand Up @@ -934,6 +945,7 @@ impl<'a, 'tcx> CastCheck<'tcx> {
fcx: &FnCtxt<'a, 'tcx>,
m_cast: TypeAndMut<'tcx>,
) -> Result<CastKind, CastError> {
self.fuzzy_provenance_int2ptr_lint(fcx);
// ptr-addr cast. pointer must be thin.
match fcx.pointer_kind(m_cast.ty, self.span)? {
None => Err(CastError::UnknownCastPtrKind),
Expand Down Expand Up @@ -973,6 +985,50 @@ impl<'a, 'tcx> CastCheck<'tcx> {
}
}
}

fn fuzzy_provenance_ptr2int_lint(&self, fcx: &FnCtxt<'a, 'tcx>, t_from: CastTy<'tcx>) {
fcx.tcx.struct_span_lint_hir(
lint::builtin::FUZZY_PROVENANCE_CASTS,
self.expr.hir_id,
self.span,
|err| {
let mut err = err.build(&format!(
"strict provenance disallows casting pointer `{}` to integer `{}`",
self.expr_ty, self.cast_ty
));

if let CastTy::FnPtr = t_from {
err.help(
"use `(... as *const u8).addr()` to obtain \
the address of a function pointer",
);
} else {
err.help("use `.addr()` to obtain the address of a pointer");
}

err.emit();
},
);
}

fn fuzzy_provenance_int2ptr_lint(&self, fcx: &FnCtxt<'a, 'tcx>) {
fcx.tcx.struct_span_lint_hir(
lint::builtin::FUZZY_PROVENANCE_CASTS,
self.expr.hir_id,
self.span,
|err| {
err.build(&format!(
"strict provenance disallows casting integer `{}` to pointer `{}`",
self.expr_ty, self.cast_ty
))
.help(
"use `.with_addr(...)` to adjust a valid pointer \
in the same allocation, to this address",
)
.emit();
},
);
}
}

impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Expand Down
1 change: 1 addition & 0 deletions library/alloc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
#![feature(rustc_allow_const_fn_unstable)]
#![feature(rustc_attrs)]
#![feature(staged_api)]
#![feature(strict_provenance)]
#![cfg_attr(test, feature(test))]
#![feature(unboxed_closures)]
#![feature(unsized_fn_params)]
Expand Down
5 changes: 2 additions & 3 deletions library/alloc/src/rc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2115,13 +2115,12 @@ impl<T> Weak<T> {
#[rustc_const_unstable(feature = "const_weak_new", issue = "95091", reason = "recently added")]
#[must_use]
pub const fn new() -> Weak<T> {
Weak { ptr: unsafe { NonNull::new_unchecked(usize::MAX as *mut RcBox<T>) } }
Weak { ptr: unsafe { NonNull::new_unchecked(ptr::invalid_mut::<RcBox<T>>(usize::MAX)) } }
}
}

pub(crate) fn is_dangling<T: ?Sized>(ptr: *mut T) -> bool {
let address = ptr as *mut () as usize;
address == usize::MAX
(ptr as *mut ()).addr() == usize::MAX
}

/// Helper type to allow accessing the reference counts without
Expand Down
2 changes: 1 addition & 1 deletion library/alloc/src/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1044,7 +1044,7 @@ where
impl<T> Drop for MergeHole<T> {
fn drop(&mut self) {
// `T` is not a zero-sized type, so it's okay to divide by its size.
let len = (self.end as usize - self.start as usize) / mem::size_of::<T>();
let len = (self.end.addr() - self.start.addr()) / mem::size_of::<T>();
unsafe {
ptr::copy_nonoverlapping(self.start, self.dest, len);
}
Expand Down
2 changes: 1 addition & 1 deletion library/alloc/src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1745,7 +1745,7 @@ impl<T> Weak<T> {
#[rustc_const_unstable(feature = "const_weak_new", issue = "95091", reason = "recently added")]
#[must_use]
pub const fn new() -> Weak<T> {
Weak { ptr: unsafe { NonNull::new_unchecked(usize::MAX as *mut ArcInner<T>) } }
Weak { ptr: unsafe { NonNull::new_unchecked(ptr::invalid_mut::<ArcInner<T>>(usize::MAX)) } }
}
}

Expand Down
2 changes: 1 addition & 1 deletion library/alloc/src/vec/into_iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ impl<T, A: Allocator> Iterator for IntoIter<T, A> {
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let exact = if mem::size_of::<T>() == 0 {
(self.end as usize).wrapping_sub(self.ptr as usize)
self.end.addr().wrapping_sub(self.ptr.addr())
} else {
unsafe { self.end.offset_from(self.ptr) as usize }
};
Expand Down
2 changes: 1 addition & 1 deletion library/core/src/alloc/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ impl Layout {
#[inline]
pub const fn dangling(&self) -> NonNull<u8> {
// SAFETY: align is guaranteed to be non-zero
unsafe { NonNull::new_unchecked(self.align() as *mut u8) }
unsafe { NonNull::new_unchecked(crate::ptr::invalid_mut::<u8>(self.align())) }
}

/// Creates a layout describing the record that can hold a value
Expand Down
8 changes: 6 additions & 2 deletions library/core/src/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,11 @@ impl<'a> ArgumentV1<'a> {
}

fn as_usize(&self) -> Option<usize> {
if self.formatter as usize == USIZE_MARKER as usize {
// We are type punning a bit here: USIZE_MARKER only takes an &usize but
// formatter takes an &Opaque. Rust understandably doesn't think we should compare
// the function pointers if they don't have the same signature, so we cast to
// pointers to convince it that we know what we're doing.
if self.formatter as *mut u8 == USIZE_MARKER as *mut u8 {
// SAFETY: The `formatter` field is only set to USIZE_MARKER if
// the value is a usize, so this is safe
Some(unsafe { *(self.value as *const _ as *const usize) })
Expand Down Expand Up @@ -2246,7 +2250,7 @@ impl<T: ?Sized> Pointer for *const T {
}
f.flags |= 1 << (FlagV1::Alternate as u32);

let ret = LowerHex::fmt(&(ptr as usize), f);
let ret = LowerHex::fmt(&(ptr.addr()), f);

f.width = old_width;
f.flags = old_flags;
Expand Down
4 changes: 2 additions & 2 deletions library/core/src/hash/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,7 @@ mod impls {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
let (address, metadata) = self.to_raw_parts();
state.write_usize(address as usize);
state.write_usize(address.addr());
metadata.hash(state);
}
}
Expand All @@ -803,7 +803,7 @@ mod impls {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
let (address, metadata) = self.to_raw_parts();
state.write_usize(address as usize);
state.write_usize(address.addr());
metadata.hash(state);
}
}
Expand Down