Fix soundness issue: make 'Formatter' panic-safe.

Fixes #1534.
This commit is contained in:
Sergio Benitez 2021-02-09 16:58:34 -08:00
parent b4fadae53d
commit b53a906a8e
1 changed files with 84 additions and 18 deletions

View File

@ -334,26 +334,41 @@ impl<'i> Formatter<'i, Query> {
fn with_prefix<F>(&mut self, prefix: &str, f: F) -> fmt::Result
where F: FnOnce(&mut Self) -> fmt::Result
{
// The `prefix` string is pushed in a `StackVec` for use by recursive
// (nested) calls to `write_raw`. The string is pushed here and then
// popped here. `self.prefixes` is modified nowhere else, and no strings
// leak from the the vector. As a result, it is impossible for a
// `prefix` to be accessed incorrectly as:
//
// * Rust _guarantees_ it exists for the lifetime of this method
// * it is only reachable while this method's stack is active because
// it is popped before this method returns
// * thus, at any point that it's reachable, it's valid
//
// Said succinctly: this `prefixes` stack shadows a subset of the
// `with_prefix` stack precisely, making it reachable to other code.
let prefix: &'static str = unsafe { ::std::mem::transmute(prefix) };
struct PrefixGuard<'f, 'i>(&'f mut Formatter<'i, Query>);
self.prefixes.push(prefix);
let result = f(self);
self.prefixes.pop();
impl<'f, 'i> PrefixGuard<'f, 'i> {
fn new(prefix: &str, f: &'f mut Formatter<'i, Query>) -> Self {
// SAFETY: The `prefix` string is pushed in a `StackVec` for use
// by recursive (nested) calls to `write_raw`. The string is
// pushed in `PrefixGuard` here and then popped in `Drop`.
// `prefixes` is modified nowhere else, and no concrete-lifetime
// strings leak from the the vector. As a result, it is
// impossible for a `prefix` to be accessed incorrectly as:
//
// * Rust _guarantees_ `prefix` is valid for this method
// * `prefix` is only reachable while this method's stack is
// active because it is unconditionally popped before this
// method returns via `PrefixGuard::drop()`.
// * should a panic occur in `f()`, `PrefixGuard::drop()` is
// still called (or the program aborts), ensuring `prefix`
// is no longer in `prefixes` and thus inaccessible.
// * thus, at any point `prefix` is reachable, it is valid
//
// Said succinctly: `prefixes` shadows a subset of the
// `with_prefix` stack, making it reachable to other code.
let prefix = unsafe { std::mem::transmute(prefix) };
f.prefixes.push(prefix);
PrefixGuard(f)
}
}
result
impl Drop for PrefixGuard<'_, '_> {
fn drop(&mut self) {
self.0.prefixes.pop();
}
}
f(&mut PrefixGuard::new(prefix, self).0)
}
/// Writes the named value `value` by prefixing `name` followed by `=` to
@ -465,3 +480,54 @@ impl<'a> UriArguments<'a> {
Origin::new(path, query)
}
}
// See https://github.com/SergioBenitez/Rocket/issues/1534.
#[cfg(test)]
mod prefix_soundness_test {
use crate::uri::{Formatter, Query, UriDisplay};
struct MyValue;
impl UriDisplay<Query> for MyValue {
fn fmt(&self, _f: &mut Formatter<'_, Query>) -> std::fmt::Result {
panic!()
}
}
struct MyDisplay;
impl UriDisplay<Query> for MyDisplay {
fn fmt(&self, formatter: &mut Formatter<'_, Query>) -> std::fmt::Result {
struct Wrapper<'a, 'b>(&'a mut Formatter<'b, Query>);
impl<'a, 'b> Drop for Wrapper<'a, 'b> {
fn drop(&mut self) {
let _overlap = String::from("12345");
self.0.write_raw("world").ok();
assert!(self.0.prefixes.is_empty());
}
}
let wrapper = Wrapper(formatter);
let temporary_string = String::from("hello");
// `write_named_value` will push `temp_string` into a buffer and
// call the formatter for `MyValue`, which panics. At the panic
// point, `formatter` contains an (illegal) static reference to
// `temp_string` in its `prefixes` stack. When unwinding occurs,
// `Wrapper` will be dropped. `Wrapper` holds a reference to
// `Formatter`, thus `Formatter` must be consistent at this point.
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
wrapper.0.write_named_value(&temporary_string, MyValue)
}));
Ok(())
}
}
#[test]
fn check_consistency() {
let string = format!("{}", &MyDisplay as &dyn UriDisplay<Query>);
assert_eq!(string, "world");
}
}