Skip to content

Commit b53a906

Browse files
committed
Fix soundness issue: make 'Formatter' panic-safe.
Fixes #1534.
1 parent b4fadae commit b53a906

File tree

1 file changed

+85
-19
lines changed

1 file changed

+85
-19
lines changed

core/http/src/uri/formatter.rs

Lines changed: 85 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -334,26 +334,41 @@ impl<'i> Formatter<'i, Query> {
334334
fn with_prefix<F>(&mut self, prefix: &str, f: F) -> fmt::Result
335335
where F: FnOnce(&mut Self) -> fmt::Result
336336
{
337-
// The `prefix` string is pushed in a `StackVec` for use by recursive
338-
// (nested) calls to `write_raw`. The string is pushed here and then
339-
// popped here. `self.prefixes` is modified nowhere else, and no strings
340-
// leak from the the vector. As a result, it is impossible for a
341-
// `prefix` to be accessed incorrectly as:
342-
//
343-
// * Rust _guarantees_ it exists for the lifetime of this method
344-
// * it is only reachable while this method's stack is active because
345-
// it is popped before this method returns
346-
// * thus, at any point that it's reachable, it's valid
347-
//
348-
// Said succinctly: this `prefixes` stack shadows a subset of the
349-
// `with_prefix` stack precisely, making it reachable to other code.
350-
let prefix: &'static str = unsafe { ::std::mem::transmute(prefix) };
351-
352-
self.prefixes.push(prefix);
353-
let result = f(self);
354-
self.prefixes.pop();
337+
struct PrefixGuard<'f, 'i>(&'f mut Formatter<'i, Query>);
355338

356-
result
339+
impl<'f, 'i> PrefixGuard<'f, 'i> {
340+
fn new(prefix: &str, f: &'f mut Formatter<'i, Query>) -> Self {
341+
// SAFETY: The `prefix` string is pushed in a `StackVec` for use
342+
// by recursive (nested) calls to `write_raw`. The string is
343+
// pushed in `PrefixGuard` here and then popped in `Drop`.
344+
// `prefixes` is modified nowhere else, and no concrete-lifetime
345+
// strings leak from the the vector. As a result, it is
346+
// impossible for a `prefix` to be accessed incorrectly as:
347+
//
348+
// * Rust _guarantees_ `prefix` is valid for this method
349+
// * `prefix` is only reachable while this method's stack is
350+
// active because it is unconditionally popped before this
351+
// method returns via `PrefixGuard::drop()`.
352+
// * should a panic occur in `f()`, `PrefixGuard::drop()` is
353+
// still called (or the program aborts), ensuring `prefix`
354+
// is no longer in `prefixes` and thus inaccessible.
355+
// * thus, at any point `prefix` is reachable, it is valid
356+
//
357+
// Said succinctly: `prefixes` shadows a subset of the
358+
// `with_prefix` stack, making it reachable to other code.
359+
let prefix = unsafe { std::mem::transmute(prefix) };
360+
f.prefixes.push(prefix);
361+
PrefixGuard(f)
362+
}
363+
}
364+
365+
impl Drop for PrefixGuard<'_, '_> {
366+
fn drop(&mut self) {
367+
self.0.prefixes.pop();
368+
}
369+
}
370+
371+
f(&mut PrefixGuard::new(prefix, self).0)
357372
}
358373

359374
/// Writes the named value `value` by prefixing `name` followed by `=` to
@@ -465,3 +480,54 @@ impl<'a> UriArguments<'a> {
465480
Origin::new(path, query)
466481
}
467482
}
483+
484+
// See https://github.com/SergioBenitez/Rocket/issues/1534.
485+
#[cfg(test)]
486+
mod prefix_soundness_test {
487+
use crate::uri::{Formatter, Query, UriDisplay};
488+
489+
struct MyValue;
490+
491+
impl UriDisplay<Query> for MyValue {
492+
fn fmt(&self, _f: &mut Formatter<'_, Query>) -> std::fmt::Result {
493+
panic!()
494+
}
495+
}
496+
497+
struct MyDisplay;
498+
499+
impl UriDisplay<Query> for MyDisplay {
500+
fn fmt(&self, formatter: &mut Formatter<'_, Query>) -> std::fmt::Result {
501+
struct Wrapper<'a, 'b>(&'a mut Formatter<'b, Query>);
502+
503+
impl<'a, 'b> Drop for Wrapper<'a, 'b> {
504+
fn drop(&mut self) {
505+
let _overlap = String::from("12345");
506+
self.0.write_raw("world").ok();
507+
assert!(self.0.prefixes.is_empty());
508+
}
509+
}
510+
511+
let wrapper = Wrapper(formatter);
512+
let temporary_string = String::from("hello");
513+
514+
// `write_named_value` will push `temp_string` into a buffer and
515+
// call the formatter for `MyValue`, which panics. At the panic
516+
// point, `formatter` contains an (illegal) static reference to
517+
// `temp_string` in its `prefixes` stack. When unwinding occurs,
518+
// `Wrapper` will be dropped. `Wrapper` holds a reference to
519+
// `Formatter`, thus `Formatter` must be consistent at this point.
520+
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
521+
wrapper.0.write_named_value(&temporary_string, MyValue)
522+
}));
523+
524+
Ok(())
525+
}
526+
}
527+
528+
#[test]
529+
fn check_consistency() {
530+
let string = format!("{}", &MyDisplay as &dyn UriDisplay<Query>);
531+
assert_eq!(string, "world");
532+
}
533+
}

0 commit comments

Comments
 (0)