-
Notifications
You must be signed in to change notification settings - Fork 105
/
transmute.rs
101 lines (88 loc) · 2.68 KB
/
transmute.rs
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
91
92
93
94
95
96
97
98
99
100
101
//! An 100% memory-safe implementation of [`std::mem::transmute`].
/// Interprets a value of type `A` as a value of type `B`. Equivalent of [`std::mem::transmute`], but implemented in safe code.
///
/// # Explanation
///
/// This transmute implementation relies on what I call "Enum abuse" — exploiting the memory representation of enums (+ other UB) in Rust to achieve silly things.
///
/// The core of the exploit is this enum:
///
/// ```ignore
/// enum DummyEnum<A, B> {
/// A(Option<Box<A>>),
/// B(Option<Box<B>>),
/// }
/// ```
///
/// First, we initialize a `DummyEnum::B(None)` and we get a dangling `&mut Option<Box<B>>` from it.
/// Then, we overwrite the dummy variable with a `DummyEnum::A(Some(<...>))` with the object we want to transmute.
/// Finally, we take the `Box<A>`, interpreted as a `Box<B>`, out of the dangling reference and we've transmuted our data!
///
/// # Safety
/// lol
///
pub fn transmute<A, B>(obj: A) -> B {
use std::hint::black_box;
// The layout of `DummyEnum` is approximately
// DummyEnum {
// is_a_or_b: u8,
// data: usize,
// }
// Note that `data` is shared between `DummyEnum::A` and `DummyEnum::B`.
// This should hopefully be more reliable than spamming the stack with a value and hoping the memory
// is placed correctly by the compiler.
#[allow(dead_code)]
enum DummyEnum<A, B> {
A(Option<Box<A>>),
B(Option<Box<B>>),
}
#[inline(never)]
fn transmute_inner<A, B>(dummy: &mut DummyEnum<A, B>, obj: A) -> B {
let DummyEnum::B(ref_to_b) = dummy else {
unreachable!()
};
let ref_to_b = crate::lifetime_expansion::expand_mut(ref_to_b);
*dummy = DummyEnum::A(Some(Box::new(obj)));
black_box(dummy);
*ref_to_b.take().unwrap()
}
transmute_inner(black_box(&mut DummyEnum::B(None)), obj)
}
#[cfg(test)]
mod tests {
// I'll allow it here.
#![allow(unsafe_code)]
#[test]
#[allow(clippy::transmute_float_to_int, clippy::transmute_num_to_bytes)]
fn test_transmute() {
use crate::transmute;
use std::mem;
unsafe {
assert_eq!(
transmute::transmute::<f32, i32>(420.69),
mem::transmute::<f32, i32>(420.69)
);
assert_eq!(
transmute::transmute::<u32, i32>(0xf0000000),
mem::transmute::<u32, i32>(0xf0000000)
);
assert_eq!(
transmute::transmute::<f64, [u8; 8]>(123.456),
mem::transmute::<f64, [u8; 8]>(123.456)
);
let my_ref = &42;
assert_eq!(
transmute::transmute::<&u8, isize>(my_ref),
mem::transmute::<&u8, isize>(my_ref)
);
assert_eq!(
transmute::transmute::<[i32; 5], [u8; 20]>([1, 2, 3, 4, 5]),
mem::transmute::<[i32; 5], [u8; 20]>([1, 2, 3, 4, 5])
);
assert_eq!(
transmute::transmute::<bool, u8>(true),
mem::transmute::<bool, u8>(true)
);
}
}
}