fix(format): a v1 group with an empty link name fails its listing
libhdf5 refuses to list a symbol-table group that has an entry with an empty name (H5G__ent_to_link: "invalid link name"), so h5py cannot list cve-2021-46244's /BAG_root. We listed it, with an object at "/BAG_root/" (the empty name, pointing at address 0). resolve_v1_group_entries — the listing — now fails with the new FormatError::InvalidLinkName; path lookups still find the group's other names, as libhdf5's by-name lookup does. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -223,6 +223,9 @@ pub enum FormatError {
|
|||||||
/// The file's actual length in bytes.
|
/// The file's actual length in bytes.
|
||||||
actual_len: u64,
|
actual_len: u64,
|
||||||
},
|
},
|
||||||
|
/// A link libhdf5 refuses to list: a symbol-table entry with an empty
|
||||||
|
/// name ("invalid link name"). Listing the group fails, as in libhdf5.
|
||||||
|
InvalidLinkName,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FormatError {
|
impl fmt::Display for FormatError {
|
||||||
@@ -494,6 +497,9 @@ impl fmt::Display for FormatError {
|
|||||||
but the file is {actual_len} bytes"
|
but the file is {actual_len} bytes"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
FormatError::InvalidLinkName => {
|
||||||
|
write!(f, "invalid link name: a group entry has an empty name")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,35 @@ pub struct GroupEntry {
|
|||||||
pub cache_type: u32,
|
pub cache_type: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given a SymbolTableMessage, resolve all group children.
|
/// Given a SymbolTableMessage, resolve all group children: the group's
|
||||||
|
/// listing.
|
||||||
|
///
|
||||||
|
/// An entry with an empty name fails the listing with
|
||||||
|
/// [`FormatError::InvalidLinkName`], as it fails libhdf5's link iteration
|
||||||
|
/// (`H5G__ent_to_link`: "invalid link name"). Looking a name up
|
||||||
|
/// ([`resolve_path`], and the path resolution in
|
||||||
|
/// [`crate::group_v2::resolve_path_any`]) still works in such a group, as it
|
||||||
|
/// does in libhdf5.
|
||||||
pub fn resolve_v1_group_entries(
|
pub fn resolve_v1_group_entries(
|
||||||
file_data: &[u8],
|
file_data: &[u8],
|
||||||
sym_table_msg: &SymbolTableMessage,
|
sym_table_msg: &SymbolTableMessage,
|
||||||
offset_size: u8,
|
offset_size: u8,
|
||||||
length_size: u8,
|
length_size: u8,
|
||||||
|
) -> Result<Vec<GroupEntry>, FormatError> {
|
||||||
|
let entries = v1_group_entries(file_data, sym_table_msg, offset_size, length_size)?;
|
||||||
|
if entries.iter().any(|e| e.name.is_empty()) {
|
||||||
|
return Err(FormatError::InvalidLinkName);
|
||||||
|
}
|
||||||
|
Ok(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Every entry of a v1 group, empty names included — for looking a name up,
|
||||||
|
/// which never matches an empty name.
|
||||||
|
pub(crate) fn v1_group_entries(
|
||||||
|
file_data: &[u8],
|
||||||
|
sym_table_msg: &SymbolTableMessage,
|
||||||
|
offset_size: u8,
|
||||||
|
length_size: u8,
|
||||||
) -> Result<Vec<GroupEntry>, FormatError> {
|
) -> Result<Vec<GroupEntry>, FormatError> {
|
||||||
// Parse local heap
|
// Parse local heap
|
||||||
let heap = LocalHeap::parse(
|
let heap = LocalHeap::parse(
|
||||||
@@ -207,8 +230,7 @@ pub fn resolve_path(
|
|||||||
let mut current_sym_table = root_sym_table.clone();
|
let mut current_sym_table = root_sym_table.clone();
|
||||||
|
|
||||||
for (i, component) in components.iter().enumerate() {
|
for (i, component) in components.iter().enumerate() {
|
||||||
let entries =
|
let entries = v1_group_entries(file_data, ¤t_sym_table, offset_size, length_size)?;
|
||||||
resolve_v1_group_entries(file_data, ¤t_sym_table, offset_size, length_size)?;
|
|
||||||
|
|
||||||
let found = entries.iter().find(|e| e.name == *component);
|
let found = entries.iter().find(|e| e.name == *component);
|
||||||
match found {
|
match found {
|
||||||
@@ -425,6 +447,22 @@ mod tests {
|
|||||||
assert_eq!(entries[1].object_header_address, 0x2000);
|
assert_eq!(entries[1].object_header_address, 0x2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// cve-2021-46244 `/BAG_root`: a symbol-table entry with an empty name.
|
||||||
|
/// libhdf5 fails the group's listing ("invalid link name"); a lookup of
|
||||||
|
/// the other names still works.
|
||||||
|
#[test]
|
||||||
|
fn empty_entry_name_fails_the_listing_not_a_lookup() {
|
||||||
|
let (file, msg) = build_synthetic_group(&[("", 0x1000, 0), ("elevation", 0x2000, 0)], 8, 8);
|
||||||
|
assert_eq!(
|
||||||
|
resolve_v1_group_entries(&file, &msg, 8, 8).unwrap_err(),
|
||||||
|
FormatError::InvalidLinkName
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resolve_path(&file, &msg, "elevation", 8, 8).unwrap(),
|
||||||
|
0x2000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_path_single_level() {
|
fn resolve_path_single_level() {
|
||||||
let (file, msg) =
|
let (file, msg) =
|
||||||
|
|||||||
@@ -450,7 +450,9 @@ fn resolve_group_entries(
|
|||||||
.find(|m| m.msg_type == MessageType::SymbolTable)
|
.find(|m| m.msg_type == MessageType::SymbolTable)
|
||||||
.ok_or_else(|| FormatError::PathNotFound(String::from("no symbol table message")))?;
|
.ok_or_else(|| FormatError::PathNotFound(String::from("no symbol table message")))?;
|
||||||
let stm = SymbolTableMessage::parse(&sym_msg.data, offset_size)?;
|
let stm = SymbolTableMessage::parse(&sym_msg.data, offset_size)?;
|
||||||
group_v1::resolve_v1_group_entries(file_data, &stm, offset_size, length_size)
|
// A lookup: an entry with an empty name (which fails a listing) is
|
||||||
|
// skipped by the name comparison, as in libhdf5.
|
||||||
|
group_v1::v1_group_entries(file_data, &stm, offset_size, length_size)
|
||||||
} else if is_v2_group(object_header) {
|
} else if is_v2_group(object_header) {
|
||||||
resolve_v2_group_entries(file_data, object_header, offset_size, length_size)
|
resolve_v2_group_entries(file_data, object_header, offset_size, length_size)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user