Skip to content

Commit b684a21

Browse files
committed
fix(encrypt): cap the reservation for a Ciphertext read as a sequence
1 parent 302aaf9 commit b684a21

2 files changed

Lines changed: 43 additions & 1 deletion

File tree

‎packages/stack-encrypt/src/scalar.rs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,10 @@ impl<'de> serde::Deserialize<'de> for Ciphertext {
123123
self,
124124
mut seq: A,
125125
) -> Result<Ciphertext, A::Error> {
126-
let mut bytes = Vec::with_capacity(seq.size_hint().unwrap_or(0));
126+
// The hint is a length the input declares, unchecked: a few
127+
// bytes can declare billions. Reserve at most a typical
128+
// envelope's worth and let a longer one grow as it is read.
129+
let mut bytes = Vec::with_capacity(seq.size_hint().unwrap_or(0).min(256));
127130
while let Some(byte) = seq.next_element::<u8>()? {
128131
bytes.push(byte);
129132
}

‎packages/stack-encrypt/tests/scalar.rs‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,11 @@ async fn serde_stores_base64_text_or_raw_bytes() {
364364
assert_eq!(strict.to_bytes(), bytes);
365365

366366
assert!(serde_json::from_str::<Ciphertext>("\"not base64!\"").is_err());
367+
// A sequence declaring usize::MAX elements over three is an envelope
368+
// error, not an allocation of the declared length.
369+
let lying: Result<Ciphertext, _> = serde::Deserialize::deserialize(LyingSeq(3));
370+
assert!(lying.is_err());
371+
367372
// A value of the wrong type names the form it should have had.
368373
let wrong = serde_json::from_str::<Ciphertext>("42").unwrap_err();
369374
assert!(
@@ -475,3 +480,37 @@ async fn opens_pinned_bytes_from_the_structural_writer() {
475480
);
476481
assert!(matches!(keyset.decrypt::<i32>(ct).await, Err(Error::Aead)));
477482
}
483+
484+
/// A binary deserializer whose sequence of `n` bytes claims `usize::MAX`.
485+
struct LyingSeq(u8);
486+
impl<'de> serde::de::SeqAccess<'de> for LyingSeq {
487+
type Error = serde::de::value::Error;
488+
fn next_element_seed<T: serde::de::DeserializeSeed<'de>>(
489+
&mut self,
490+
seed: T,
491+
) -> Result<Option<T::Value>, Self::Error> {
492+
if self.0 == 0 {
493+
return Ok(None);
494+
}
495+
self.0 -= 1;
496+
seed.deserialize(serde::de::value::U8Deserializer::new(0))
497+
.map(Some)
498+
}
499+
fn size_hint(&self) -> Option<usize> {
500+
Some(usize::MAX)
501+
}
502+
}
503+
impl<'de> serde::Deserializer<'de> for LyingSeq {
504+
type Error = serde::de::value::Error;
505+
fn is_human_readable(&self) -> bool {
506+
false
507+
}
508+
fn deserialize_any<V: serde::de::Visitor<'de>>(self, v: V) -> Result<V::Value, Self::Error> {
509+
v.visit_seq(self)
510+
}
511+
serde::forward_to_deserialize_any! {
512+
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
513+
bytes byte_buf option unit unit_struct newtype_struct seq tuple
514+
tuple_struct map struct enum identifier ignored_any
515+
}
516+
}

0 commit comments

Comments
 (0)