feat(format): ObjectHeader::object_class, libhdf5's object classification
libhdf5 decides what an object header is in a fixed order (H5O__obj_class_real): a group if it has a Symbol Table or Link Info message, a dataset if it has a Datatype *and* a Dataspace message, a named datatype if it has a Datatype message. The conformance probe called any header with a Data Layout message a dataset, so cve-2024-33874's /Dset1 (a datatype and a layout, no dataspace), which h5py opens as a named datatype, was reported as a dataset we failed to read (MissingMessage(Dataspace)). The probe now classifies with object_class(). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -75,7 +75,40 @@ fn read_offset(data: &[u8], pos: usize, size: u8) -> Result<u64, FormatError> {
|
||||
})
|
||||
}
|
||||
|
||||
/// The kind of object an object header describes.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ObjectClass {
|
||||
/// A group: the header has a Symbol Table or a Link Info message.
|
||||
Group,
|
||||
/// A dataset: the header has a Datatype and a Dataspace message.
|
||||
Dataset,
|
||||
/// A committed (named) datatype: a Datatype message, no Dataspace.
|
||||
NamedDatatype,
|
||||
}
|
||||
|
||||
impl ObjectHeader {
|
||||
/// The kind of object this header describes, decided as libhdf5 decides
|
||||
/// it (`H5O__obj_class_real`): group first (a Symbol Table or Link Info
|
||||
/// message), then dataset (a Datatype *and* a Dataspace message — not a
|
||||
/// Data Layout message), then named datatype (a Datatype message).
|
||||
/// `None` when none applies; libhdf5 then cannot open the object
|
||||
/// ("unable to determine object type").
|
||||
///
|
||||
/// A header with a Datatype and a Data Layout message but no Dataspace
|
||||
/// is a named datatype to libhdf5, not a dataset.
|
||||
pub fn object_class(&self) -> Option<ObjectClass> {
|
||||
let has = |t: MessageType| self.messages.iter().any(|m| m.msg_type == t);
|
||||
if has(MessageType::SymbolTable) || has(MessageType::LinkInfo) {
|
||||
Some(ObjectClass::Group)
|
||||
} else if has(MessageType::Datatype) && has(MessageType::Dataspace) {
|
||||
Some(ObjectClass::Dataset)
|
||||
} else if has(MessageType::Datatype) {
|
||||
Some(ObjectClass::NamedDatatype)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse an object header at the given offset in the data buffer.
|
||||
///
|
||||
/// `offset_size` and `length_size` come from the superblock.
|
||||
@@ -662,6 +695,54 @@ fn check_message(
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn header_with(types: &[MessageType]) -> ObjectHeader {
|
||||
ObjectHeader {
|
||||
version: 2,
|
||||
messages: types
|
||||
.iter()
|
||||
.map(|&msg_type| HeaderMessage {
|
||||
msg_type,
|
||||
size: 0,
|
||||
flags: 0,
|
||||
creation_order: None,
|
||||
data: Vec::new(),
|
||||
})
|
||||
.collect(),
|
||||
reference_count: None,
|
||||
flags: 0,
|
||||
access_time: None,
|
||||
modification_time: None,
|
||||
change_time: None,
|
||||
birth_time: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_class_follows_libhdf5() {
|
||||
use MessageType::*;
|
||||
let class = |t: &[MessageType]| header_with(t).object_class();
|
||||
assert_eq!(
|
||||
class(&[Datatype, Dataspace, DataLayout]),
|
||||
Some(ObjectClass::Dataset)
|
||||
);
|
||||
// A Data Layout message does not make a dataset without a dataspace
|
||||
// (cve-2024-33874 `/Dset1`: h5py opens it as a named datatype).
|
||||
assert_eq!(
|
||||
class(&[Datatype, DataLayout]),
|
||||
Some(ObjectClass::NamedDatatype)
|
||||
);
|
||||
assert_eq!(class(&[Datatype]), Some(ObjectClass::NamedDatatype));
|
||||
// Group messages win over dataset messages.
|
||||
assert_eq!(
|
||||
class(&[Datatype, Dataspace, SymbolTable]),
|
||||
Some(ObjectClass::Group)
|
||||
);
|
||||
assert_eq!(class(&[LinkInfo]), Some(ObjectClass::Group));
|
||||
// Link messages alone are not a group; nothing is not an object.
|
||||
assert_eq!(class(&[Link]), None);
|
||||
assert_eq!(class(&[]), None);
|
||||
}
|
||||
|
||||
// Helper: build a v1 object header with given messages
|
||||
fn build_v1_header(
|
||||
messages: &[(u16, &[u8], u8)], // (type, data, flags)
|
||||
|
||||
Reference in New Issue
Block a user