CVE-2021-29935
Description
A use-after-free vulnerability in Rocket's uri::Formatter allows memory corruption when a user-provided formatting function panics.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A use-after-free vulnerability in Rocket's `uri::Formatter` allows memory corruption when a user-provided formatting function panics.
The
Vulnerability
The Rocket web framework for Rust, prior to version 0.4.7, contains a use-after-free (UAF) vulnerability in the uri::Formatter type. The issue originates in the with_prefix method, where a raw pointer to a string prefix is created via std::mem::transmute and stored in an internal StackVec. If the user-provided formatting closure f panics, the prefix is not popped from the vector, leaving a dangling reference that can later be accessed when the Formatter is reused [1][2]. This violates Rust's memory safety guarantees and can lead to undefined behavior.
Exploitation
The vulnerability is triggered when a Rocket application uses URI formatting (e.g., via routes, query parameters, or form parsing) and the formatting closure panics. An attacker can remotely cause a panic by supplying input that triggers a failure in user-defined formatting logic. No authentication is required, and the attack can be carried out over the network by sending crafted requests to an affected Rocket endpoint [4]. The CVSS vector is CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L, indicating low attack complexity and no privileges needed [4].
Impact
Successful exploitation of this use-after-free can result in memory corruption, potentially leading to information disclosure, data tampering, or denial of service. The specific impact depends on how the panic is handled and the state of the Formatter after the panic. However, because the vulnerability is in the framework's URI parsing and formatting logic, any application using Rocket 0.4.6 or earlier is at risk [1][2][3].
Mitigation
The vulnerability is fixed in Rocket version 0.4.7 and later. The fix introduces a PrefixGuard struct that ensures the prefix is always popped, even during a panic, by using Rust's drop semantics [1][2]. Users should upgrade to Rocket >= 0.4.7 or apply one of the referenced commits. No known workarounds exist for earlier versions [4].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
rocketcrates.io | < 0.4.7 | 0.4.7 |
Affected products
2- Rust/rocketdescription
Patches
2b53a906a8e17Fix soundness issue: make 'Formatter' panic-safe.
1 file changed · +85 −19
core/http/src/uri/formatter.rs+85 −19 modified@@ -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) }; - - self.prefixes.push(prefix); - let result = f(self); - self.prefixes.pop(); + struct PrefixGuard<'f, 'i>(&'f mut Formatter<'i, Query>); - result + 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) + } + } + + 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"); + } +}
e325e2fce4d9Fix soundness issue: make 'Formatter' panic-safe.
1 file changed · +86 −19
core/http/src/uri/formatter.rs+86 −19 modified@@ -334,26 +334,42 @@ impl Formatter<'_, 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) }; - - self.prefixes.push(prefix); - let result = f(self); - self.prefixes.pop(); - result + struct PrefixGuard<'f, 'i>(&'f mut Formatter<'i, Query>); + + 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) + } + } + + 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 @@ -468,3 +484,54 @@ impl UriArguments<'_> { 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"); + } +}
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-vcw4-8ph6-7vw8ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-29935ghsaADVISORY
- github.com/SergioBenitez/Rocket/commit/b53a906a8e170fe9b151381c66a76a872c419f9eghsaWEB
- github.com/SergioBenitez/Rocket/commit/e325e2fce4d9f9f392761e9fb58b418a48cef8bbghsaWEB
- github.com/SergioBenitez/Rocket/issues/1534ghsaWEB
- rustsec.org/advisories/RUSTSEC-2021-0044.htmlghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.