Skip to content

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

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

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
@@ -18,6 +18,7 @@
#![feature(decl_macro)]
#![feature(rustc_attrs)]
#![cfg_attr(test, feature(test))]
#![feature(strict_provenance)]

use smallvec::SmallVec;

@@ -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())
}
@@ -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
@@ -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
}
@@ -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>();
}

@@ -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 {
@@ -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 {
1 change: 1 addition & 0 deletions compiler/rustc_codegen_ssa/src/lib.rs
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
#![feature(once_cell)]
#![feature(nll)]
#![feature(associated_type_bounds)]
#![feature(strict_provenance)]
#![recursion_limit = "256"]
#![allow(rustc::potential_query_instability)]

2 changes: 1 addition & 1 deletion compiler/rustc_codegen_ssa/src/mono_item.rs
Original file line number Diff line number Diff line change
@@ -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),
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,
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;
36 changes: 36 additions & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
@@ -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.
@@ -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,
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};
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;
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;
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;
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};
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};
64 changes: 60 additions & 4 deletions compiler/rustc_typeck/src/check/cast.rs
Original file line number Diff line number Diff line change
@@ -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
@@ -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),
@@ -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> {
1 change: 1 addition & 0 deletions library/alloc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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)]
5 changes: 2 additions & 3 deletions library/alloc/src/rc.rs
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion library/alloc/src/slice.rs
Original file line number Diff line number Diff line change
@@ -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);
}
2 changes: 1 addition & 1 deletion library/alloc/src/sync.rs
Original file line number Diff line number Diff line change
@@ -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)) } }
}
}

2 changes: 1 addition & 1 deletion library/alloc/src/vec/into_iter.rs
Original file line number Diff line number Diff line change
@@ -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 }
};
2 changes: 1 addition & 1 deletion library/core/src/alloc/layout.rs
Original file line number Diff line number Diff line change
@@ -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
8 changes: 6 additions & 2 deletions library/core/src/fmt/mod.rs
Original file line number Diff line number Diff line change
@@ -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) })
@@ -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;
4 changes: 2 additions & 2 deletions library/core/src/hash/mod.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
@@ -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);
}
}
Loading