fix(format): refuse variable-length and reference VDS data from another file

Their elements are global-heap IDs and object addresses in the source
file. The VDS reader copied them raw, so anything decoding them against
the virtual dataset's file got another object's data with no error.
Same-file sources are unaffected. Found by the adversarial review.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-09-25 22:42:57 -05:00
co-authored by Claude Opus 5.5
parent f2e704abf3
commit 256e7b89e4
2 changed files with 63 additions and 0 deletions
+25
View File
@@ -831,6 +831,16 @@ impl<'a, 'r> Sources<'a, 'r> {
path: &str,
datatype: &Datatype,
) -> Result<Option<SourceData>, FormatError> {
// Variable-length and reference elements are addresses into the file
// that holds them (global-heap IDs, object addresses). Copied out of
// another file they would be decoded against the virtual dataset's
// file and name some other object, so refuse rather than return them.
if file != "." && holds_file_addresses(datatype) {
return Err(vds_err(format!(
"VDS source {path} in {file}: variable-length and reference data \
from another file is not supported"
)));
}
let Some(bytes) = self.file(file)? else {
return Ok(None);
};
@@ -841,6 +851,21 @@ impl<'a, 'r> Sources<'a, 'r> {
}
}
/// Whether elements of `dt` contain addresses into their own file:
/// variable-length data (global-heap IDs) or references.
fn holds_file_addresses(dt: &Datatype) -> bool {
match dt {
Datatype::VariableLength { .. } | Datatype::Reference { .. } => true,
Datatype::Compound { members, .. } => {
members.iter().any(|m| holds_file_addresses(&m.datatype))
}
Datatype::Array { base_type, .. } | Datatype::Enumeration { base_type, .. } => {
holds_file_addresses(base_type)
}
_ => false,
}
}
/// An opened source dataset's object header.
struct OpenSource {
offset_size: u8,