CVE-2020-35858
Description
In prost before 0.6.1, a crafted message causes stack consumption leading to denial of service or potential remote code execution.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
In prost before 0.6.1, a crafted message causes stack consumption leading to denial of service or potential remote code execution.
The vulnerability in the prost crate (versions prior to 0.6.1) arises from unconstrained recursion when skipping unknown fields during message decoding. The skip_field function, used when a field tag is not recognized, does not enforce a recursion limit, allowing deeply nested crafted messages to exhaust the call stack [3]. This bug affects the Rust implementation of Protocol Buffers, which is widely used for serialization in Rust applications [1].
Exploitation requires no authentication or special privileges; an attacker only needs to send a specially crafted Protocol Buffers message to an application using an affected prost version. The attack vector is network-based, with low complexity, as the vulnerable code is triggered automatically during parsing [4]. On x86 platforms, the primary impact is a denial of service via stack overflow. However, on ARM platforms, the advisory notes the possibility of remote code execution due to differences in memory corruption behavior [2].
The vulnerability has been addressed in prost version 0.6.1 by introducing a recursion limit parameter ctx to the skip_field function [3]. Users are strongly advised to update to 0.6.1 or later. The RustSec advisory (RUSTSEC-2020-0002) assigns a CVSS v3.1 score of 9.8 (Critical) due to the potential for complete compromise of confidentiality, integrity, and availability [4]. No workarounds were published; upgrading is the recommended mitigation.
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 |
|---|---|---|
prostcrates.io | < 0.6.1 | 0.6.1 |
Affected products
2- rust/prostdescription
Patches
28 files changed · +12 −12
Cargo.toml+2 −2 modified@@ -1,6 +1,6 @@ [package] name = "prost" -version = "0.6.0" +version = "0.6.1" authors = ["Dan Burkert <dan@danburkert.com>"] license = "Apache-2.0" repository = "https://github.com/danburkert/prost" @@ -41,7 +41,7 @@ no-recursion-limit = [] [dependencies] bytes = "0.5" -prost-derive = { version = "0.6.0", path = "prost-derive", optional = true } +prost-derive = { version = "0.6.1", path = "prost-derive", optional = true } [dev-dependencies] criterion = "0.3"
prost-build/Cargo.toml+3 −3 modified@@ -1,6 +1,6 @@ [package] name = "prost-build" -version = "0.6.0" +version = "0.6.1" authors = ["Dan Burkert <dan@danburkert.com>"] license = "Apache-2.0" repository = "https://github.com/danburkert/prost" @@ -16,8 +16,8 @@ itertools = "0.8" log = "0.4" multimap = { version = "0.8", default-features = false } petgraph = { version = "0.5", default-features = false } -prost = { version = "0.6.0", path = ".." } -prost-types = { version = "0.6.0", path = "../prost-types" } +prost = { version = "0.6.1", path = ".." } +prost-types = { version = "0.6.1", path = "../prost-types" } tempfile = "3" [build-dependencies]
prost-build/src/lib.rs+1 −1 modified@@ -1,4 +1,4 @@ -#![doc(html_root_url = "https://docs.rs/prost-build/0.6.0")] +#![doc(html_root_url = "https://docs.rs/prost-build/0.6.1")] //! `prost-build` compiles `.proto` files into Rust. //!
prost-derive/Cargo.toml+1 −1 modified@@ -1,6 +1,6 @@ [package] name = "prost-derive" -version = "0.6.0" +version = "0.6.1" authors = ["Dan Burkert <dan@danburkert.com>"] license = "Apache-2.0" repository = "https://github.com/danburkert/prost"
prost-derive/src/lib.rs+1 −1 modified@@ -1,4 +1,4 @@ -#![doc(html_root_url = "https://docs.rs/prost-derive/0.6.0")] +#![doc(html_root_url = "https://docs.rs/prost-derive/0.6.1")] // The `quote!` macro requires deep recursion. #![recursion_limit = "4096"]
prost-types/Cargo.toml+2 −2 modified@@ -1,6 +1,6 @@ [package] name = "prost-types" -version = "0.6.0" +version = "0.6.1" authors = ["Dan Burkert <dan@danburkert.com>"] license = "Apache-2.0" repository = "https://github.com/danburkert/prost" @@ -15,4 +15,4 @@ test = false [dependencies] bytes = "0.5" -prost = { version = "0.6.0", path = ".." } +prost = { version = "0.6.1", path = ".." }
prost-types/src/lib.rs+1 −1 modified@@ -1,4 +1,4 @@ -#![doc(html_root_url = "https://docs.rs/prost-types/0.6.0")] +#![doc(html_root_url = "https://docs.rs/prost-types/0.6.1")] //! Protocol Buffers well-known types. //!
src/lib.rs+1 −1 modified@@ -1,4 +1,4 @@ -#![doc(html_root_url = "https://docs.rs/prost/0.6.0")] +#![doc(html_root_url = "https://docs.rs/prost/0.6.1")] mod error; mod message;
04091d3e745capply recursion limit when skipping fields
4 files changed · +26 −15
prost-derive/src/lib.rs+1 −1 modified@@ -194,7 +194,7 @@ fn try_message(input: TokenStream) -> Result<TokenStream, Error> { #struct_name match tag { #(#merge)* - _ => ::prost::encoding::skip_field(wire_type, tag, buf), + _ => ::prost::encoding::skip_field(wire_type, tag, buf, ctx), } }
src/encoding.rs+4 −3 modified@@ -381,10 +381,11 @@ where Ok(()) } -pub fn skip_field<B>(wire_type: WireType, tag: u32, buf: &mut B) -> Result<(), DecodeError> +pub fn skip_field<B>(wire_type: WireType, tag: u32, buf: &mut B, ctx: DecodeContext) -> Result<(), DecodeError> where B: Buf, { + ctx.limit_reached()?; let len = match wire_type { WireType::Varint => decode_varint(buf).map(|_| 0)?, WireType::ThirtyTwoBit => 4, @@ -399,7 +400,7 @@ where } break 0; } - _ => skip_field(inner_wire_type, inner_tag, buf)?, + _ => skip_field(inner_wire_type, inner_tag, buf, ctx.enter_recursion())?, } }, WireType::EndGroup => return Err(DecodeError::new("unexpected end group tag")), @@ -1219,7 +1220,7 @@ macro_rules! map { match tag { 1 => key_merge(wire_type, key, buf, ctx), 2 => val_merge(wire_type, val, buf, ctx), - _ => skip_field(wire_type, tag, buf), + _ => skip_field(wire_type, tag, buf, ctx), } }, )?;
src/types.rs+11 −11 modified@@ -38,7 +38,7 @@ impl Message for bool { if tag == 1 { bool::merge(wire_type, self, buf, ctx) } else { - skip_field(wire_type, tag, buf) + skip_field(wire_type, tag, buf, ctx) } } fn encoded_len(&self) -> usize { @@ -76,7 +76,7 @@ impl Message for u32 { if tag == 1 { uint32::merge(wire_type, self, buf, ctx) } else { - skip_field(wire_type, tag, buf) + skip_field(wire_type, tag, buf, ctx) } } fn encoded_len(&self) -> usize { @@ -114,7 +114,7 @@ impl Message for u64 { if tag == 1 { uint64::merge(wire_type, self, buf, ctx) } else { - skip_field(wire_type, tag, buf) + skip_field(wire_type, tag, buf, ctx) } } fn encoded_len(&self) -> usize { @@ -152,7 +152,7 @@ impl Message for i32 { if tag == 1 { int32::merge(wire_type, self, buf, ctx) } else { - skip_field(wire_type, tag, buf) + skip_field(wire_type, tag, buf, ctx) } } fn encoded_len(&self) -> usize { @@ -190,7 +190,7 @@ impl Message for i64 { if tag == 1 { int64::merge(wire_type, self, buf, ctx) } else { - skip_field(wire_type, tag, buf) + skip_field(wire_type, tag, buf, ctx) } } fn encoded_len(&self) -> usize { @@ -228,7 +228,7 @@ impl Message for f32 { if tag == 1 { float::merge(wire_type, self, buf, ctx) } else { - skip_field(wire_type, tag, buf) + skip_field(wire_type, tag, buf, ctx) } } fn encoded_len(&self) -> usize { @@ -266,7 +266,7 @@ impl Message for f64 { if tag == 1 { double::merge(wire_type, self, buf, ctx) } else { - skip_field(wire_type, tag, buf) + skip_field(wire_type, tag, buf, ctx) } } fn encoded_len(&self) -> usize { @@ -304,7 +304,7 @@ impl Message for String { if tag == 1 { string::merge(wire_type, self, buf, ctx) } else { - skip_field(wire_type, tag, buf) + skip_field(wire_type, tag, buf, ctx) } } fn encoded_len(&self) -> usize { @@ -342,7 +342,7 @@ impl Message for Vec<u8> { if tag == 1 { bytes::merge(wire_type, self, buf, ctx) } else { - skip_field(wire_type, tag, buf) + skip_field(wire_type, tag, buf, ctx) } } fn encoded_len(&self) -> usize { @@ -369,12 +369,12 @@ impl Message for () { tag: u32, wire_type: WireType, buf: &mut B, - _ctx: DecodeContext, + ctx: DecodeContext, ) -> Result<(), DecodeError> where B: Buf, { - skip_field(wire_type, tag, buf) + skip_field(wire_type, tag, buf, ctx) } fn encoded_len(&self) -> usize { 0
tests/src/lib.rs+10 −0 modified@@ -462,6 +462,16 @@ mod tests { }; } + #[test] + fn test_267_regression() { + // Checks that skip_field will error appropriately when given a big stack of StartGroup + // tags. + // + // https://github.com/danburkert/prost/issues/267 + let buf = vec![b'C'; 1 << 20]; + <() as Message>::decode(&buf[..]).err().unwrap(); + } + #[test] fn test_default_enum() { let msg = default_enum_value::Test::default();
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-gv73-9mwv-fwgqghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-35858ghsaADVISORY
- github.com/danburkert/prost/commit/04091d3e745c27590a5f1b7f581793e4159486b5ghsaWEB
- github.com/danburkert/prost/issues/267ghsaWEB
- rustsec.org/advisories/RUSTSEC-2020-0002.htmlghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.