VYPR
High severityNVD Advisory· Published Apr 1, 2021· Updated Aug 3, 2024

CVE-2021-29935

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.

PackageAffected versionsPatched versions
rocketcrates.io
< 0.4.70.4.7

Affected products

2

Patches

2
b53a906a8e17

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

https://github.com/SergioBenitez/RocketSergio BenitezFeb 10, 2021via ghsa
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");
    +    }
    +}
    
e325e2fce4d9

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

https://github.com/SergioBenitez/RocketSergio BenitezFeb 10, 2021via ghsa
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

News mentions

0

No linked articles in our index yet.