Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 54 additions & 8 deletions gix-pack/src/data/entry/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ use crate::data;
pub enum Error {
#[error("Object type {type_id} is unsupported")]
UnsupportedType { type_id: u8 },
#[error("Pack entry uses unsupported object hash length {len}")]
UnsupportedHashLength { len: usize },
#[error("Pack entry is truncated: {message}")]
Corrupt { message: &'static str },
#[error("Pack entry header size {size} does not fit into u16")]
HeaderSizeOverflow { size: usize },
#[error("Pack entry header value overflowed while decoding")]
Overflow,
}
Expand All @@ -38,12 +42,15 @@ impl data::Entry {
delta
}
REF_DELTA => {
let hash = d
.get(consumed..)
.and_then(|d| d.get(..hash_len))
.ok_or(Error::Corrupt {
message: "ref-delta base object id",
})?;
let delta = RefDelta {
base_id: gix_hash::ObjectId::from_bytes_or_panic(d.get(consumed..consumed + hash_len).ok_or(
Error::Corrupt {
message: "ref-delta base object id",
},
)?),
base_id: gix_hash::ObjectId::try_from(hash)
.map_err(|_| Error::UnsupportedHashLength { len: hash_len })?,
};
consumed += hash_len;
delta
Expand All @@ -58,7 +65,7 @@ impl data::Entry {
header: object,
decompressed_size: size,
data_offset: pack_offset + consumed as u64,
encoded_header_size: consumed.try_into().expect("pack entry headers fit into u16"),
encoded_header_size: encoded_header_size(consumed)?,
})
}

Expand All @@ -78,11 +85,14 @@ impl data::Entry {
}
REF_DELTA => {
let mut buf = gix_hash::Kind::buf();
if hash_len > buf.len() {
return Err(unsupported_hash_len(hash_len));
}
let hash = &mut buf[..hash_len];
r.read_exact(hash)?;
#[allow(clippy::redundant_slicing)]
let delta = RefDelta {
base_id: gix_hash::ObjectId::from_bytes_or_panic(&hash[..]),
base_id: gix_hash::ObjectId::try_from(&hash[..]).map_err(|_| unsupported_hash_len(hash_len))?,
};
consumed += hash_len;
delta
Expand All @@ -97,11 +107,22 @@ impl data::Entry {
header: object,
decompressed_size: size,
data_offset: pack_offset + consumed as u64,
encoded_header_size: consumed.try_into().expect("pack entry headers fit into u16"),
encoded_header_size: encoded_header_size(consumed)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?,
})
}
}

fn encoded_header_size(consumed: usize) -> Result<u16, Error> {
consumed
.try_into()
.map_err(|_| Error::HeaderSizeOverflow { size: consumed })
}

fn unsupported_hash_len(len: usize) -> io::Error {
io::Error::new(io::ErrorKind::InvalidInput, Error::UnsupportedHashLength { len })
}

#[inline]
fn streaming_parse_header_info(read: &mut dyn io::Read) -> Result<(u8, u64, usize), io::Error> {
let mut byte = [0u8; 1];
Expand Down Expand Up @@ -236,4 +257,29 @@ mod tests {
"ofs-delta base distances are relative to the entry start, so preserving `pack_offset()` keeps the base lookup correct"
);
}

#[test]
fn unsupported_ref_delta_hash_len_is_reported_without_panicking() {
let result = std::panic::catch_unwind(|| {
data::Entry::from_read(&mut &[0x70][..], 0, gix_hash::Kind::longest().len_in_bytes() + 1)
});

assert!(
result
.expect("unsupported ref-delta hash lengths must not panic while decoding")
.is_err(),
"unsupported ref-delta hash lengths should be reported as invalid input"
);
}

#[test]
fn oversized_encoded_header_size_is_rejected() {
assert!(
matches!(
encoded_header_size(usize::from(u16::MAX) + 1),
Err(Error::HeaderSizeOverflow { size }) if size == usize::from(u16::MAX) + 1
),
"entry header lengths that cannot be stored in the Entry metadata must be rejected"
);
}
}
Loading