Skip to content

Commit 8a09fb1

Browse files
committed
cfi: Store type erasure witness for Argument
CFI/KCFI work by enforcing that indirect function calls are always called at the type they were defined at. The type erasure in Argument works by casting the reference to the value to be formatted to an Opaque and also casting the function to format it to take an Opaque reference. While this is *ABI* safe, which is why we get away with it normally, it does transform the type that the function is called at. This means that at the call-site, CFI expects the type of the function to be `fn(&Opaque, ` even though it is really `fn(&T, ` for some particular `T`. This patch avoids this by adding `cast_stub`, a witness to the type erasure that will cast the `&Opaque` and `fn(&Opaque` back to their original types before invoking the function. This change is guarded by the enablement of CFI as it will require an additional pointer-sized value per `Argument`, and an additional jump during formatting, and we'd prefer not to pay that if we don't need the types to be correct at the indirect call invocation.
1 parent 65ea825 commit 8a09fb1

File tree

1 file changed

+46
-3
lines changed
  • library/core/src/fmt

1 file changed

+46
-3
lines changed

library/core/src/fmt/rt.rs

+46-3
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,28 @@ pub(super) enum Flag {
7474
#[derive(Copy, Clone)]
7575
pub struct Argument<'a> {
7676
value: &'a Opaque,
77-
formatter: fn(&Opaque, &mut Formatter<'_>) -> Result,
77+
formatter: unsafe fn(&Opaque, &mut Formatter<'_>) -> Result,
78+
#[cfg(any(sanitize = "cfi", sanitize = "kcfi"))]
79+
cast_stub: unsafe fn(
80+
fn(&Opaque, &mut Formatter<'_>) -> Result,
81+
&Opaque,
82+
f: &mut Formatter<'_>,
83+
) -> Result,
84+
}
85+
86+
/// This function acts as a witness to an earlier type erasure when constructing an `Argument`.
87+
/// The type parameter `T` should be instantiated to the erased type.
88+
/// SAFETY: This function should be called on a value and formatter which underwent a
89+
/// T->Opaque cast at their definition site.
90+
#[cfg(any(sanitize = "cfi", sanitize = "kcfi"))]
91+
unsafe fn cast_stub<T>(
92+
formatter: unsafe fn(&Opaque, &mut Formatter<'_>) -> Result,
93+
value: &Opaque,
94+
f: &mut Formatter<'_>,
95+
) -> Result {
96+
let value: &T = mem::transmute(value);
97+
let formatter: fn(&T, &mut Formatter<'_>) -> Result = mem::transmute(formatter);
98+
formatter(value, f)
7899
}
79100

80101
#[rustc_diagnostic_item = "ArgumentMethods"]
@@ -89,7 +110,14 @@ impl<'a> Argument<'a> {
89110
// `mem::transmute(f)` is safe since `fn(&T, &mut Formatter<'_>) -> Result`
90111
// and `fn(&Opaque, &mut Formatter<'_>) -> Result` have the same ABI
91112
// (as long as `T` is `Sized`)
92-
unsafe { Argument { formatter: mem::transmute(f), value: mem::transmute(x) } }
113+
unsafe {
114+
Argument {
115+
formatter: mem::transmute(f),
116+
value: mem::transmute(x),
117+
#[cfg(any(sanitize = "cfi", sanitize = "kcfi"))]
118+
cast_stub: cast_stub::<T>,
119+
}
120+
}
93121
}
94122

95123
#[inline(always)]
@@ -135,7 +163,22 @@ impl<'a> Argument<'a> {
135163

136164
#[inline(always)]
137165
pub(super) fn fmt(&self, f: &mut Formatter<'_>) -> Result {
138-
(self.formatter)(self.value, f)
166+
cfg_if! {
167+
if #[cfg(any(sanitize = "cfi", sanitize = "kcfi"))] {
168+
// SAFETY: We're calling cast_stub on the formatter/value pair it was constructed
169+
// with, and we don't allow mutation of any individual members. As a result, this
170+
// cast_stub should be at the T representing the formatter/value pair.
171+
unsafe {
172+
(self.cast_stub)(self.formatter, self.value, f)
173+
}
174+
} else {
175+
// SAFETY: We're calling the formatter on the value that had its type erased
176+
// alongside it.
177+
unsafe {
178+
(self.formatter)(self.value, f)
179+
}
180+
}
181+
}
139182
}
140183

141184
#[inline(always)]

0 commit comments

Comments
 (0)