h5rs tools, browser reader, libhdf5 header checks, plugin filters, concurrency benchmark #14
@@ -313,7 +313,7 @@ impl<'a> Ctx<'a> {
|
|||||||
let dtb = self
|
let dtb = self
|
||||||
.payload(h, MessageType::Datatype)?
|
.payload(h, MessageType::Datatype)?
|
||||||
.ok_or("MissingMessage(Datatype)")?;
|
.ok_or("MissingMessage(Datatype)")?;
|
||||||
Datatype::parse(&dtb).map_err(e)?;
|
Datatype::parse_in_header(&dtb, h.version).map_err(e)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +321,7 @@ impl<'a> Ctx<'a> {
|
|||||||
let dtb = self
|
let dtb = self
|
||||||
.payload(h, MessageType::Datatype)?
|
.payload(h, MessageType::Datatype)?
|
||||||
.ok_or("MissingMessage(Datatype)")?;
|
.ok_or("MissingMessage(Datatype)")?;
|
||||||
let (dt, _) = Datatype::parse(&dtb).map_err(e)?;
|
let (dt, _) = Datatype::parse_in_header(&dtb, h.version).map_err(e)?;
|
||||||
rec.insert("dtype".into(), Value::String(dtype_str(&dt)));
|
rec.insert("dtype".into(), Value::String(dtype_str(&dt)));
|
||||||
let dsb = self
|
let dsb = self
|
||||||
.payload(h, MessageType::Dataspace)?
|
.payload(h, MessageType::Dataspace)?
|
||||||
|
|||||||
@@ -362,6 +362,18 @@ fn extract_name(bytes: &[u8]) -> String {
|
|||||||
String::from_utf8_lossy(&bytes[..end]).into_owned()
|
String::from_utf8_lossy(&bytes[..end]).into_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An attribute's datatype gets libhdf5's extra check for a header without
|
||||||
|
/// a checksum (see [`Datatype::check_unused_bits`]).
|
||||||
|
fn check_in_header(
|
||||||
|
attr: AttributeMessage,
|
||||||
|
header: &ObjectHeader,
|
||||||
|
) -> Result<AttributeMessage, FormatError> {
|
||||||
|
if header.version == 1 {
|
||||||
|
attr.datatype.check_unused_bits()?;
|
||||||
|
}
|
||||||
|
Ok(attr)
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract all attribute messages from an object header.
|
/// Extract all attribute messages from an object header.
|
||||||
pub fn extract_attributes(
|
pub fn extract_attributes(
|
||||||
header: &ObjectHeader,
|
header: &ObjectHeader,
|
||||||
@@ -371,7 +383,7 @@ pub fn extract_attributes(
|
|||||||
for msg in &header.messages {
|
for msg in &header.messages {
|
||||||
if msg.msg_type == MessageType::Attribute {
|
if msg.msg_type == MessageType::Attribute {
|
||||||
let attr = AttributeMessage::parse(&msg.data, length_size)?;
|
let attr = AttributeMessage::parse(&msg.data, length_size)?;
|
||||||
attrs.push(attr);
|
attrs.push(check_in_header(attr, header)?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(attrs)
|
Ok(attrs)
|
||||||
@@ -465,6 +477,7 @@ fn extract_attributes_with(
|
|||||||
} else {
|
} else {
|
||||||
AttributeMessage::parse_in_file(&msg.data, file_data, offset_size, length_size)
|
AttributeMessage::parse_in_file(&msg.data, file_data, offset_size, length_size)
|
||||||
};
|
};
|
||||||
|
let attr = attr.and_then(|a| check_in_header(a, header));
|
||||||
match attr {
|
match attr {
|
||||||
Ok(attr) => attrs.push(attr),
|
Ok(attr) => attrs.push(attr),
|
||||||
Err(e) => on_error(e)?,
|
Err(e) => on_error(e)?,
|
||||||
|
|||||||
@@ -858,6 +858,70 @@ impl Datatype {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [`Self::parse`] for the datatype message of an object whose header
|
||||||
|
/// has version `header_version`: a version-1 header, which has no
|
||||||
|
/// checksum, additionally gets [`Self::check_unused_bits`], as libhdf5
|
||||||
|
/// does. Use this wherever the header is at hand.
|
||||||
|
pub fn parse_in_header(
|
||||||
|
data: &[u8],
|
||||||
|
header_version: u8,
|
||||||
|
) -> Result<(Datatype, usize), FormatError> {
|
||||||
|
let parsed = Self::parse(data)?;
|
||||||
|
if header_version == 1 {
|
||||||
|
parsed.0.check_unused_bits()?;
|
||||||
|
}
|
||||||
|
Ok(parsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// libhdf5's guard against a corrupt numeric type in a header without
|
||||||
|
/// a checksum (`H5T_is_numeric_with_unusual_unused_bits`, HDF5 1.14.4+):
|
||||||
|
/// an integer, float or bit field wider than a byte whose precision and
|
||||||
|
/// offset leave more than half its bits unused is taken for corruption
|
||||||
|
/// (e.g. a 3-bit integer in 4 bytes, `cve-2024-29162`, or a 32-bit float
|
||||||
|
/// in 65525 bytes, `cve-2024-32614`), anywhere in the type. libhdf5
|
||||||
|
/// skips the check for checksummed (version-2) headers and when the
|
||||||
|
/// file is opened with `H5Pset_relax_file_integrity_checks`; so does
|
||||||
|
/// [`Self::parse_in_header`], which has no such option.
|
||||||
|
pub fn check_unused_bits(&self) -> Result<(), FormatError> {
|
||||||
|
match self {
|
||||||
|
Datatype::FixedPoint {
|
||||||
|
size,
|
||||||
|
bit_offset,
|
||||||
|
bit_precision,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| Datatype::FloatingPoint {
|
||||||
|
size,
|
||||||
|
bit_offset,
|
||||||
|
bit_precision,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| Datatype::BitField {
|
||||||
|
size,
|
||||||
|
bit_offset,
|
||||||
|
bit_precision,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let bits = u64::from(*size) * 8;
|
||||||
|
let prec = u64::from(*bit_precision);
|
||||||
|
if *size > 1 && prec < bits && bits > 2 * (prec + u64::from(*bit_offset)) {
|
||||||
|
return Err(invalid(format!(
|
||||||
|
"datatype has unusually large # of unused bits (prec = {prec} bits, \
|
||||||
|
size = {size} bytes), possibly corrupted file"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Datatype::Compound { members, .. } => members
|
||||||
|
.iter()
|
||||||
|
.try_for_each(|m| m.datatype.check_unused_bits()),
|
||||||
|
Datatype::Enumeration { base_type, .. }
|
||||||
|
| Datatype::VariableLength { base_type, .. }
|
||||||
|
| Datatype::Array { base_type, .. } => base_type.check_unused_bits(),
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Serialize datatype to HDF5 message bytes.
|
/// Serialize datatype to HDF5 message bytes.
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
pub fn serialize(&self) -> Vec<u8> {
|
||||||
match self {
|
match self {
|
||||||
@@ -2354,6 +2418,35 @@ mod tests {
|
|||||||
assert_eq!(invalid_reason(&data), "bad byte order for datatype message");
|
assert_eq!(invalid_reason(&data), "bad byte order for datatype message");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unusual_unused_bits_are_refused_in_version_1_headers_only() {
|
||||||
|
// cve-2024-29162: a 3-bit integer in 4 bytes.
|
||||||
|
let data = build_fixed_point(4, false, true, 0, 3);
|
||||||
|
assert!(Datatype::parse_in_header(&data, 2).is_ok());
|
||||||
|
assert_eq!(
|
||||||
|
match Datatype::parse_in_header(&data, 1) {
|
||||||
|
Err(FormatError::InvalidDatatype(why)) => why,
|
||||||
|
other => panic!("{other:?}"),
|
||||||
|
},
|
||||||
|
"datatype has unusually large # of unused bits (prec = 3 bits, size = 4 bytes), \
|
||||||
|
possibly corrupted file"
|
||||||
|
);
|
||||||
|
// Half the bits used (with the offset) is not unusual; nor is a
|
||||||
|
// 1-byte type; nor a full-precision one.
|
||||||
|
for (size, offset, prec) in [(4u32, 0u16, 16u16), (4, 8, 8), (1, 0, 1), (8, 0, 64)] {
|
||||||
|
let data = build_fixed_point(size, false, true, offset, prec);
|
||||||
|
assert!(
|
||||||
|
Datatype::parse_in_header(&data, 1).is_ok(),
|
||||||
|
"{size} {offset} {prec}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Nested: a compound member's type is checked too.
|
||||||
|
let member = build_fixed_point(4, false, true, 0, 15);
|
||||||
|
let data = compound_v3(4, &[("a", 0, member)]);
|
||||||
|
assert!(Datatype::parse_in_header(&data, 2).is_ok());
|
||||||
|
assert!(Datatype::parse_in_header(&data, 1).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn float_bit_6_is_vax_order_only_from_version_3() {
|
fn float_bit_6_is_vax_order_only_from_version_3() {
|
||||||
// h5py opens a v1 float with bit 6 set as an ordinary little-endian
|
// h5py opens a v1 float with bit 6 set as an ordinary little-endian
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ impl AsyncHDF5File {
|
|||||||
|
|
||||||
let dt_msg =
|
let dt_msg =
|
||||||
find_msg(&header, MessageType::Datatype).ok_or(FormatError::DatasetMissingData)?;
|
find_msg(&header, MessageType::Datatype).ok_or(FormatError::DatasetMissingData)?;
|
||||||
let (datatype, _) = Datatype::parse(&dt_msg.data)?;
|
let (datatype, _) = Datatype::parse_in_header(&dt_msg.data, header.version)?;
|
||||||
|
|
||||||
let ds_msg =
|
let ds_msg =
|
||||||
find_msg(&header, MessageType::Dataspace).ok_or(FormatError::DatasetMissingShape)?;
|
find_msg(&header, MessageType::Dataspace).ok_or(FormatError::DatasetMissingShape)?;
|
||||||
|
|||||||
@@ -205,8 +205,8 @@ fn mpi_collective_read(vol: &MpiVol, location: &str, path: &str) -> Result<Vec<u
|
|||||||
.iter()
|
.iter()
|
||||||
.find(|m| m.msg_type == MessageType::Datatype)
|
.find(|m| m.msg_type == MessageType::Datatype)
|
||||||
.ok_or_else(|| VolError::DataError("no datatype".into()))?;
|
.ok_or_else(|| VolError::DataError("no datatype".into()))?;
|
||||||
let (datatype, _) =
|
let (datatype, _) = Datatype::parse_in_header(&dt.data, oh.version)
|
||||||
Datatype::parse(&dt.data).map_err(|e| VolError::DataError(e.to_string()))?;
|
.map_err(|e| VolError::DataError(e.to_string()))?;
|
||||||
let ds = oh
|
let ds = oh
|
||||||
.messages
|
.messages
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -301,8 +301,8 @@ impl VirtualObjectLayer for NativeVol {
|
|||||||
.iter()
|
.iter()
|
||||||
.find(|m| m.msg_type == MessageType::Datatype)
|
.find(|m| m.msg_type == MessageType::Datatype)
|
||||||
.ok_or_else(|| VolError::DataError("missing datatype".into()))?;
|
.ok_or_else(|| VolError::DataError("missing datatype".into()))?;
|
||||||
let (datatype, _) =
|
let (datatype, _) = Datatype::parse_in_header(&dt_msg.data, header.version)
|
||||||
Datatype::parse(&dt_msg.data).map_err(|e| VolError::DataError(e.to_string()))?;
|
.map_err(|e| VolError::DataError(e.to_string()))?;
|
||||||
|
|
||||||
let ds_msg = header
|
let ds_msg = header
|
||||||
.messages
|
.messages
|
||||||
|
|||||||
@@ -479,7 +479,7 @@ impl<'f, R: HDF5Read> LazyDataset<'f, R> {
|
|||||||
|
|
||||||
fn datatype(&self) -> Result<Datatype, Error> {
|
fn datatype(&self) -> Result<Datatype, Error> {
|
||||||
let data = self.required_payload(MessageType::Datatype)?;
|
let data = self.required_payload(MessageType::Datatype)?;
|
||||||
let (dt, _) = Datatype::parse(&data)?;
|
let (dt, _) = Datatype::parse_in_header(&data, self.header.version)?;
|
||||||
Ok(dt)
|
Ok(dt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -426,7 +426,7 @@ impl<'f> MmapDataset<'f> {
|
|||||||
|
|
||||||
fn datatype(&self) -> Result<Datatype, Error> {
|
fn datatype(&self) -> Result<Datatype, Error> {
|
||||||
let data = self.required_payload(MessageType::Datatype)?;
|
let data = self.required_payload(MessageType::Datatype)?;
|
||||||
let (dt, _) = Datatype::parse(&data)?;
|
let (dt, _) = Datatype::parse_in_header(&data, self.header.version)?;
|
||||||
Ok(dt)
|
Ok(dt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -850,7 +850,7 @@ impl<'f> Dataset<'f> {
|
|||||||
|
|
||||||
fn datatype(&self) -> Result<Datatype, Error> {
|
fn datatype(&self) -> Result<Datatype, Error> {
|
||||||
let data = self.required_payload(MessageType::Datatype)?;
|
let data = self.required_payload(MessageType::Datatype)?;
|
||||||
let (dt, _) = Datatype::parse(&data)?;
|
let (dt, _) = Datatype::parse_in_header(&data, self.header.version)?;
|
||||||
Ok(dt)
|
Ok(dt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user