Skip to content

Commit bbe68c4

Browse files
committed
Add TBI helpers for AArch64
Adds a new `TBIBox` type in `core_arch`, which allows for modifying the top byte of the address that the allocation lives at. Modifying the top byte reallocates the data, thereby invalidating any existing pointers and avoiding aliasing.
1 parent 5dfe757 commit bbe68c4

File tree

3 files changed

+236
-1
lines changed

3 files changed

+236
-1
lines changed

crates/core_arch/src/aarch64/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ mod mte;
1010
#[unstable(feature = "stdarch_aarch64_mte", issue = "129010")]
1111
pub use self::mte::*;
1212

13+
mod tbi;
14+
#[unstable(feature = "stdarch_aarch64_tbi", issue = "none")]
15+
pub use self::tbi::*;
16+
1317
// NEON intrinsics are currently broken on big-endian, so don't expose them. (#1484)
1418
#[cfg(target_endian = "little")]
1519
mod neon;

crates/core_arch/src/aarch64/tbi.rs

+229
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
extern crate alloc;
2+
use crate::arch::asm;
3+
use alloc::boxed::Box;
4+
use core::convert::{AsMut, AsRef};
5+
use core::ops::{Deref, DerefMut, Drop};
6+
7+
/// A TBI-enabled [Box].
8+
///
9+
/// This allows setting the top byte to arbitrary values via top-byte ignore (TBI).
10+
#[derive(Debug)]
11+
#[unstable(feature = "stdarch_aarch64_tbi", issue = "none")]
12+
pub struct TBIBox<T>(
13+
Option<Box<T>>,
14+
/* original pointer for deallocation: */ *mut T,
15+
// Without keeping a copy of the top byte around, it is difficult to cleanly extract the top byte from the
16+
// underlying pointer on-demand since `from_raw` requires consuming `self.0`.
17+
/* top byte for easy retrieval: */
18+
u8,
19+
);
20+
21+
impl<T> TBIBox<T> {
22+
fn construct_new_tbibox(b: Box<T>, top_byte: u8) -> TBIBox<T> {
23+
// Pointer modificaton: this `Box` hasn't been exposed to the user yet, so the user cannot have a copy of the pointer with
24+
// an unset top byte; we use this chance to set the top byte before they can see it.
25+
let original_ptr: *mut T = Box::into_raw(b);
26+
let ptr =
27+
original_ptr.map_addr(|addr| (addr & 0x00ffffffffffffff) | ((top_byte as usize) << 56));
28+
unsafe {
29+
asm!(
30+
"/* pretend that we're memcpy'ing from {original_ptr} to {ptr}... */",
31+
original_ptr = in(reg) original_ptr,
32+
ptr = in(reg) ptr
33+
)
34+
};
35+
// Reconstruct the `Box` using the address with the new top byte and return that, wrapped as a TBIBox
36+
Self(Some(unsafe { Box::from_raw(ptr) }), original_ptr, top_byte)
37+
}
38+
39+
fn interior_box(&self) -> &Box<T> {
40+
self.0.as_ref().expect("Interior Box can only be missing if this TBIBox has already returned it, in which case this TBIBox should not exist anymore!")
41+
}
42+
43+
fn interior_box_mut(&mut self) -> &mut Box<T> {
44+
self.0.as_mut().expect("Interior Box can only be missing if this TBIBox has already returned it, in which case this TBIBox should not exist anymore!")
45+
}
46+
47+
/// Construct a new [TBIBox] with the given value allocated on the heap, and the top byte of the allocation's address set to a given
48+
/// 8-bit value.
49+
#[unstable(feature = "stdarch_aarch64_tbi", issue = "none")]
50+
pub fn new(value: T, top_byte: u8) -> Self {
51+
Self::construct_new_tbibox(Box::new(value), top_byte)
52+
}
53+
54+
/// Returns a new [TBIBox] with the same contents as the provided [Box], with the top byte of the address it points to set to a given value.
55+
///
56+
/// Note that the original [Box] and the returned [TBIBox] do not alias.
57+
#[unstable(feature = "stdarch_aarch64_tbi", issue = "none")]
58+
pub fn new_from_box(b: &Box<T>, top_byte: u8) -> TBIBox<T>
59+
where
60+
T: Clone,
61+
{
62+
// Use a new Box with the same data; we can't reuse the provided Box as its address may have been exposed, so it may
63+
// result in aliasing pointers.
64+
Self::construct_new_tbibox(b.clone(), top_byte)
65+
}
66+
67+
/// Construct a new [TBIBox] with cloned data, but with a new top byte set on the address.
68+
#[unstable(feature = "stdarch_aarch64_tbi", issue = "none")]
69+
pub fn realloc_with_top_byte(&self, top_byte: u8) -> TBIBox<T>
70+
where
71+
T: Clone,
72+
{
73+
// Switch to a new `Box` with the same data - the address of the old allocation should be invalidated.
74+
Self::construct_new_tbibox(self.interior_box().clone(), top_byte)
75+
}
76+
77+
/// Upgrades the provided [Box] to a [TBIBox].
78+
///
79+
/// This reuses the existing allocation, and will inspect the address of the allocation to determine the top byte.
80+
#[unstable(feature = "stdarch_aarch64_tbi", issue = "none")]
81+
pub fn from_box(b: Box<T>) -> TBIBox<T> {
82+
// Extract the pointer for inspection...
83+
let ptr: *mut T = Box::into_raw(b);
84+
// Reconstruct the `Box` using the address.
85+
// SAFETY: This is fine as we're reconstructing the Box with the exact same pointer.
86+
TBIBox(
87+
Some(unsafe { Box::from_raw(ptr) }),
88+
ptr,
89+
(ptr.addr() >> 56) as u8,
90+
)
91+
}
92+
93+
/// Deconstructs the [TBIBox] to a raw pointer to its allocation,
94+
/// along with a `u8` value representing the top byte of the address
95+
/// as it was initially allocated with (see safety information).
96+
///
97+
/// # Safety
98+
///
99+
/// Care must be taken when handling the allocated memory if the top
100+
/// byte has been set using the [TBIBox]:
101+
/// - You may dereference the pointer following the usual rules.
102+
/// - You may **not** free the allocation directly, including via FFI.
103+
/// - If you use the pointer to construct a [Box], that [Box] is not suitable for upgrading back to a [TBIBox], and you must ensure that the [Box] is not dropped whilst Rust is managing its memory.
104+
///
105+
/// To reconstruct the [TBIBox] for safely deallocating the memory, you **must** use `from_raw_ptr` with both values returned from this function.
106+
#[unstable(feature = "stdarch_aarch64_tbi", issue = "none")]
107+
pub unsafe fn to_raw_ptr(mut self) -> (*mut T, u8) {
108+
(
109+
Box::into_raw(self.0.take().expect("Interior Box can only be missing if this TBIBox has already returned it, in which case this TBIBox should not exist anymore!")),
110+
(self.1.addr() >> 56) as u8
111+
)
112+
}
113+
114+
/// Construct a [TBIBox] with a given pointer and provided original top byte,
115+
/// such as reconstructing one which was previous deconstructed with `to_raw_ptr`.
116+
///
117+
/// # Safety
118+
///
119+
/// This operation is only safe if the pointer has not been modified, and the original
120+
/// top byte is correct. Failure of either constraint will lead to Undefined Behavior
121+
/// when deallocating the memory, or when changing the top byte.
122+
#[unstable(feature = "stdarch_aarch64_tbi", issue = "none")]
123+
pub unsafe fn from_raw_ptr(ptr: *mut T, original_top_byte: u8) -> Self {
124+
TBIBox(
125+
Some(unsafe { Box::from_raw(ptr) }),
126+
ptr.map_addr(|addr| (addr & 0x00ffffffffffffff) | ((original_top_byte as usize) << 56)),
127+
(ptr.addr() >> 56) as u8,
128+
)
129+
}
130+
131+
/// Returns the top byte that is currently set on the address of the `TBIBox`.
132+
/// No introspection is performed on the actual value of the internal pointer.
133+
#[unstable(feature = "stdarch_aarch64_tbi", issue = "none")]
134+
pub fn top_byte(&self) -> u8 {
135+
self.2
136+
}
137+
}
138+
139+
#[unstable(feature = "stdarch_aarch64_tbi", issue = "none")]
140+
impl<T> Drop for TBIBox<T> {
141+
fn drop(&mut self) {
142+
if let Some(old_b) = self.0.as_mut() {
143+
// It is undefined behaviour since C11 to call `free` on a pointer which was not returned by a call to `malloc` et al.
144+
// Thus, reset the `Box` back to the original pointer, so that its destruction is well-defined.
145+
// First, construct the corrected `Box`...
146+
let mut b = unsafe { Box::from_raw(self.1) };
147+
// We **do not want Rust to free the current Box in self.0**. Thus, we first swap it out for the new Box, but keep it around...
148+
core::mem::swap(&mut b, old_b);
149+
// ... and then consume the old Box with `into_raw` so that Rust does not manage it anymore. This should avoid an invalid `free` call.
150+
let ptr = Box::into_raw(b);
151+
unsafe {
152+
asm!(
153+
"/* pretend that we're memcpy'ing from {ptr} to {original_ptr}... */",
154+
ptr = in(reg) ptr,
155+
original_ptr = in(reg) self.1
156+
)
157+
};
158+
}
159+
// It is now safe to destroy self.
160+
}
161+
}
162+
163+
#[unstable(feature = "stdarch_aarch64_tbi", issue = "none")]
164+
impl<T> Deref for TBIBox<T> {
165+
type Target = Box<T>;
166+
fn deref(&self) -> &Self::Target {
167+
&self.interior_box()
168+
}
169+
}
170+
171+
#[unstable(feature = "stdarch_aarch64_tbi", issue = "none")]
172+
impl<T> DerefMut for TBIBox<T> {
173+
fn deref_mut(&mut self) -> &mut Self::Target {
174+
self.interior_box_mut()
175+
}
176+
}
177+
178+
#[unstable(feature = "stdarch_aarch64_tbi", issue = "none")]
179+
impl<T> AsMut<T> for TBIBox<T> {
180+
fn as_mut(&mut self) -> &mut T {
181+
&mut *self.interior_box_mut()
182+
}
183+
}
184+
185+
#[unstable(feature = "stdarch_aarch64_tbi", issue = "none")]
186+
impl<T> AsRef<T> for TBIBox<T> {
187+
fn as_ref(&self) -> &T {
188+
&*self.interior_box()
189+
}
190+
}
191+
192+
#[cfg(test)]
193+
mod test {
194+
use super::*;
195+
196+
fn test_tbi(b: u8) {
197+
let mut value = TBIBox::new(10u32, b);
198+
let top_byte = value.top_byte();
199+
assert_eq!(top_byte, b);
200+
assert_eq!(**value, 10);
201+
**value = 255;
202+
assert_eq!(**value, 255);
203+
}
204+
205+
fn test_tbi_array(b: u8) {
206+
let mut value: TBIBox<[u32; 4]> = TBIBox::new([10, 255, 65535, 0xffffffff], b);
207+
let top_byte = value.top_byte();
208+
assert_eq!(top_byte, b);
209+
assert_eq!(**value, [10, 255, 65535, 0xffffffff]);
210+
value[0] = 25;
211+
assert_eq!(**value, [25, 255, 65535, 0xffffffff]);
212+
}
213+
214+
#[test]
215+
fn tbi() {
216+
for i in 0x00..=0xff {
217+
println!("Top byte: {i:#x}");
218+
test_tbi(i);
219+
}
220+
}
221+
222+
#[test]
223+
fn tbi_array() {
224+
for i in 0x00..=0xff {
225+
println!("Top byte: {i:#x}");
226+
test_tbi_array(i);
227+
}
228+
}
229+
}

crates/core_arch/src/lib.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@
3636
asm_experimental_arch,
3737
sha512_sm_x86,
3838
x86_amx_intrinsics,
39-
f16
39+
f16,
40+
ptr_metadata,
41+
strict_provenance
4042
)]
4143
#![cfg_attr(test, feature(test, abi_vectorcall, stdarch_internal))]
4244
#![deny(clippy::missing_inline_in_public_items)]

0 commit comments

Comments
 (0)