Skip to content

Commit

Permalink
Do not read garbage CacheError markers
Browse files Browse the repository at this point in the history
  • Loading branch information
Swatinem committed Feb 29, 2024
1 parent 78497a3 commit 298183e
Showing 1 changed file with 18 additions and 4 deletions.
22 changes: 18 additions & 4 deletions crates/symbolicator-service/src/caching/cache_error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::io;
use std::time::Duration;

Expand Down Expand Up @@ -116,10 +117,10 @@ impl CacheError {
/// * Otherwise `None` is returned.
fn from_bytes(bytes: &[u8]) -> Option<Self> {
if let Some(raw_message) = bytes.strip_prefix(Self::PERMISSION_DENIED_MARKER) {
let err_msg = String::from_utf8_lossy(raw_message);
let err_msg = utf8_message(raw_message);
Some(Self::PermissionDenied(err_msg.into_owned()))
} else if let Some(raw_duration) = bytes.strip_prefix(Self::TIMEOUT_MARKER) {
let raw_duration = String::from_utf8_lossy(raw_duration);
let raw_duration = utf8_message(raw_duration);
match parse_duration(&raw_duration) {
Ok(duration) => Some(Self::Timeout(duration)),
Err(e) => {
Expand All @@ -128,10 +129,10 @@ impl CacheError {
}
}
} else if let Some(raw_message) = bytes.strip_prefix(Self::DOWNLOAD_ERROR_MARKER) {
let err_msg = String::from_utf8_lossy(raw_message);
let err_msg = utf8_message(raw_message);
Some(Self::DownloadError(err_msg.into_owned()))
} else if let Some(raw_message) = bytes.strip_prefix(Self::MALFORMED_MARKER) {
let err_msg = String::from_utf8_lossy(raw_message);
let err_msg = utf8_message(raw_message);
Some(Self::Malformed(err_msg.into_owned()))
} else if bytes.is_empty() {
Some(Self::NotFound)
Expand All @@ -148,6 +149,19 @@ impl CacheError {
}
}

/// A "safer" [`String::from_utf8_lossy`].
///
/// This reads the string only up to the first NUL-byte.
/// We have observed broken cache files which were not properly truncated.
/// They had a valid `CacheError` prefix, followed by gigabytes worth of NUL-bytes, and some junk at the end.
fn utf8_message(raw_message: &[u8]) -> Cow<'_, str> {
let raw_message = raw_message
.split(|c| *c == b'\0')
.next()
.unwrap_or(raw_message);
String::from_utf8_lossy(raw_message)
}

/// An entry in a cache, containing either `Ok(T)` or an error denoting the reason why an
/// object could not be fetched or is otherwise unusable.
pub type CacheEntry<T = ()> = Result<T, CacheError>;
Expand Down

0 comments on commit 298183e

Please sign in to comment.