1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//! So far, all our bugs are implemented using a single soundness hole in the Rust compiler.
//!
//! The explanation is detailed in the [`lifetime_expansion`] module.

#![deny(unsafe_code)]

// The actual exploit
pub mod lifetime_expansion;

// The bugs we created with the exploit
pub mod buffer_overflow;
pub mod segfault;
pub mod transmute;
pub mod use_after_free;

pub use lifetime_expansion::*;

pub use buffer_overflow::buffer_overflow;
pub use segfault::segfault;
pub use transmute::transmute;
pub use use_after_free::use_after_free;

/// Construct a [`String`] from a pointer, capacity and length, in a completely safe manner.
///
/// [`String`] is a `Vec<u8>` which is a `(RawVec, usize)` which is a `((Unique, usize), usize)`.
///
/// Rust explicitly says that structs are not guaranteed to have members in order,
/// so instead we determine that order at runtime.
///
/// # Safety
///
/// This function is 100% memory-safe.
///
/// Nevetheless, remember to use [`std::mem::forget`] to deallocate the fake [`String`], otherwise Rust
/// will think the pointer has been allocated by the global allocator and free it the wrong way.
///
/// > As they say: *Trust, but Verify.*
#[inline(always)]
pub fn construct_fake_string(ptr: *mut u8, cap: usize, len: usize) -> String {
	let sentinel_string = crate::transmute::<_, String>([0usize, 1usize, 2usize]);

	let fields = [ptr as usize, cap, len];
	let mut actual_buf = [0usize; 3];
	actual_buf[0] = fields[sentinel_string.as_ptr() as usize];
	actual_buf[1] = fields[sentinel_string.capacity()];
	actual_buf[2] = fields[sentinel_string.len()];

	std::mem::forget(sentinel_string);

	crate::transmute::<_, String>(actual_buf)
}

use std::hint::black_box;

/// Returns a static reference to a dropped `Box<Box<T>>`.
#[inline(never)]
#[allow(clippy::borrowed_box, clippy::redundant_allocation)]
fn get_dropped_box<T: Default>() -> &'static mut Box<Box<T>> {
	let mut boxx = black_box(Box::<Box<T>>::default());
	lifetime_expansion::expand_mut(&mut boxx)
}

/// Not allocate an object. The returned reference is always invalid.
pub fn not_alloc<'a, T: Default + 'static>() -> &'a mut T {
	get_dropped_box::<T>().as_mut().as_mut()
}

/// Good for job security.
#[cfg(feature = "give-up")]
pub fn give_up<T: 'static>() -> Box<T> {
	use std::time::SystemTime;

	let size = std::mem::size_of::<T>();

	let mut v = Vec::with_capacity(size);

	let mut rng = {
		let seed = SystemTime::now()
			.duration_since(SystemTime::UNIX_EPOCH)
			.unwrap();

		oorandom::Rand32::new(seed.as_secs())
	};

	for _ in 0..size {
		v.push((rng.rand_u32() % 256) as u8);
	}

	crate::transmute(v.into_boxed_slice())
}