1use super::{
2 Error, Guid, Result, SpecValidator, StandardItems, ValidationIssue, is_known_metadata_guid,
3};
4
5impl SpecValidator {
6 pub fn validate_metadata(&self) -> Result<Vec<ValidationIssue>> {
23 let mut issues = Vec::new();
24 let Some(meta_data) = self.metadata_region() else {
25 return Ok(issues);
26 };
27
28 let meta = crate::metadata::Metadata::new(meta_data)?;
29 let table = meta.table();
30
31 Self::validate_metadata_header_checks(&table, &mut issues)?;
32 let mut ranges: Vec<(u32, u32, Guid)> = Vec::new();
33 for entry in table.entries() {
34 self.validate_metadata_entry(&entry, meta_data.len(), &mut ranges, &mut issues)?;
35 }
36 Self::validate_metadata_ranges_overlap(&ranges, &mut issues)?;
37 Self::push_corrupted_known_metadata_items(&table, &mut issues);
38
39 Ok(issues)
40 }
41
42 fn validate_metadata_header_checks(
43 table: &crate::metadata::MetadataTable<'_>, issues: &mut Vec<ValidationIssue>,
44 ) -> Result<()> {
45 if let Err(e) = table.header().validate_signature() {
46 Self::push_issue(
47 issues,
48 ValidationIssue::new(
49 "metadata",
50 "METADATA_TABLE_SIGNATURE_INVALID",
51 format!("{e}"),
52 "MS-VHDX/2.6.1.1",
53 ),
54 );
55 return Err(e);
56 }
57 let entry_count = table.header().entry_count();
58 if entry_count > 2047 {
59 Self::push_issue(
60 issues,
61 ValidationIssue::new(
62 "metadata",
63 "METADATA_ENTRY_INVALID",
64 format!("entry count {entry_count} > 2047"),
65 "MS-VHDX/2.6.1.2",
66 ),
67 );
68 return Err(Error::InvalidMetadata(format!(
69 "METADATA_ENTRY_INVALID: entry count {entry_count} > 2047"
70 )));
71 }
72 Ok(())
73 }
74
75 fn validate_metadata_entry(
76 &self, entry: &crate::metadata::TableEntry<'_>, region_len: usize,
77 ranges: &mut Vec<(u32, u32, Guid)>, issues: &mut Vec<ValidationIssue>,
78 ) -> Result<()> {
79 let offset = entry.offset() as usize;
80 let length = entry.length() as usize;
81 Self::validate_metadata_offset_and_length(
82 entry, offset, length, region_len, ranges, issues,
83 )?;
84 Self::validate_metadata_entry_reserved_flags(entry, issues)?;
85 Self::validate_metadata_entry_reserved_field(entry, issues)?;
86 self.validate_metadata_unknown_guid_policy(entry, issues)
87 }
88
89 fn validate_metadata_offset_and_length(
90 entry: &crate::metadata::TableEntry<'_>, offset: usize, length: usize, region_len: usize,
91 ranges: &mut Vec<(u32, u32, Guid)>, issues: &mut Vec<ValidationIssue>,
92 ) -> Result<()> {
93 if length == 0 && offset != 0 {
94 Self::push_issue(
95 issues,
96 ValidationIssue::new(
97 "metadata",
98 "METADATA_ENTRY_INVALID",
99 format!("length=0 but offset={offset} (expected 0)"),
100 "MS-VHDX/2.6.1.2",
101 ),
102 );
103 return Err(Error::InvalidMetadata(format!(
104 "METADATA_ENTRY_INVALID: length=0 but offset={offset} (expected 0)"
105 )));
106 }
107 if length > 0 {
108 if offset < 65536 {
109 Self::push_issue(
110 issues,
111 ValidationIssue::new(
112 "metadata",
113 "METADATA_ENTRY_OFFSET_MINIMUM",
114 format!("metadata entry offset {offset} < 64KB minimum"),
115 "MS-VHDX/2.6.1.2",
116 ),
117 );
118 return Err(Error::InvalidMetadata(format!(
119 "METADATA_ENTRY_OFFSET_MINIMUM: metadata entry offset {offset} < 64KB minimum"
120 )));
121 }
122 let Some(end) = offset.checked_add(length) else {
123 Self::push_issue(
124 issues,
125 ValidationIssue::new(
126 "metadata",
127 "METADATA_ENTRY_INVALID",
128 "offset+length overflow",
129 "MS-VHDX/2.6.1.2",
130 ),
131 );
132 return Err(Error::InvalidMetadata(
133 "METADATA_ENTRY_INVALID: offset+length overflow".into(),
134 ));
135 };
136 if end > region_len {
137 Self::push_issue(
138 issues,
139 ValidationIssue::new(
140 "metadata",
141 "METADATA_ENTRY_INVALID",
142 format!("item extent [{offset}..{end}] exceeds region ({region_len})"),
143 "MS-VHDX/2.6.1.2",
144 ),
145 );
146 return Err(Error::InvalidMetadata(format!(
147 "METADATA_ENTRY_INVALID: item extent [{offset}..{end}] exceeds region ({region_len})"
148 )));
149 }
150 ranges.push((
151 u32::try_from(offset).expect("metadata item offset fits u32"),
152 u32::try_from(offset + length).expect("metadata item end fits u32"),
153 entry.item_id(),
154 ));
155 }
156 Ok(())
157 }
158
159 fn validate_metadata_entry_reserved_flags(
160 entry: &crate::metadata::TableEntry<'_>, issues: &mut Vec<ValidationIssue>,
161 ) -> Result<()> {
162 if entry.flags().has_reserved_bits() {
163 Self::push_issue(
164 issues,
165 ValidationIssue::new(
166 "metadata",
167 "METADATA_RESERVED_FLAGS_SET",
168 format!(
169 "metadata entry GUID {} has reserved flags bits set: {:#010x}",
170 entry.item_id(),
171 entry.flags_bits()
172 ),
173 "MS-VHDX/2.6.1.2",
174 ),
175 );
176 return Err(Error::MetadataReservedFlagsSet {
177 flags: entry.flags_bits(),
178 });
179 }
180 Ok(())
181 }
182
183 fn validate_metadata_entry_reserved_field(
184 entry: &crate::metadata::TableEntry<'_>, issues: &mut Vec<ValidationIssue>,
185 ) -> Result<()> {
186 if entry.reserved() != 0 {
187 Self::push_issue(
188 issues,
189 ValidationIssue::new(
190 "metadata",
191 "METADATA_ENTRY_RESERVED_NONZERO",
192 format!(
193 "metadata entry GUID {} has reserved field set to {:#010x}",
194 entry.item_id(),
195 entry.reserved()
196 ),
197 "MS-VHDX/2.6.1.2",
198 ),
199 );
200 return Err(Error::MetadataEntryReservedNonzero {
201 reserved: entry.reserved(),
202 });
203 }
204 Ok(())
205 }
206
207 fn validate_metadata_unknown_guid_policy(
208 &self, entry: &crate::metadata::TableEntry<'_>, issues: &mut Vec<ValidationIssue>,
209 ) -> Result<()> {
210 if is_known_metadata_guid(&entry.item_id()) {
211 return Ok(());
212 }
213 Self::push_issue(
214 issues,
215 ValidationIssue::new(
216 "metadata",
217 "METADATA_GUID_UNKNOWN",
218 format!("unknown metadata GUID {}", entry.item_id()),
219 "MS-VHDX/2.6.2",
220 ),
221 );
222 if entry.flags().is_required() {
223 Self::push_issue(
224 issues,
225 ValidationIssue::new(
226 "metadata",
227 "METADATA_REQUIRED_UNKNOWN",
228 format!("required unknown metadata GUID {}", entry.item_id()),
229 "RELAX",
230 ),
231 );
232 return Err(Error::MetadataRequiredUnknown {
233 guid: entry.item_id(),
234 });
235 }
236 if self.strict {
237 Self::push_issue(
238 issues,
239 ValidationIssue::new(
240 "metadata",
241 "METADATA_OPTIONAL_UNKNOWN",
242 format!(
243 "optional unknown metadata GUID {} in strict mode",
244 entry.item_id()
245 ),
246 "RELAX",
247 ),
248 );
249 return Err(Error::MetadataOptionalUnknown {
250 guid: entry.item_id(),
251 });
252 }
253 Self::push_issue(
254 issues,
255 ValidationIssue::new(
256 "metadata",
257 "METADATA_OPTIONAL_UNKNOWN",
258 format!(
259 "optional unknown metadata GUID {} tolerated in non-strict mode",
260 entry.item_id()
261 ),
262 "RELAX",
263 ),
264 );
265 Ok(())
266 }
267
268 fn validate_metadata_ranges_overlap(
269 ranges: &[(u32, u32, Guid)], issues: &mut Vec<ValidationIssue>,
270 ) -> Result<()> {
271 for i in 0..ranges.len() {
272 for j in (i + 1)..ranges.len() {
273 let (s1, e1, g1) = &ranges[i];
274 let (s2, e2, g2) = &ranges[j];
275 if *s1 < *e2 && *s2 < *e1 {
276 Self::push_issue(
277 issues,
278 ValidationIssue::new(
279 "metadata",
280 "METADATA_ITEMS_OVERLAP",
281 format!("metadata items overlap: {g1} and {g2}"),
282 "MS-VHDX/2.6.2",
283 ),
284 );
285 return Err(Error::InvalidMetadata(format!(
286 "METADATA_ITEMS_OVERLAP: metadata items overlap: {g1} and {g2}"
287 )));
288 }
289 }
290 }
291 Ok(())
292 }
293
294 fn push_corrupted_known_metadata_items(
295 table: &crate::metadata::MetadataTable<'_>, issues: &mut Vec<ValidationIssue>,
296 ) {
297 let known_items: &[(&Guid, &str, u32)] = &[
298 (&StandardItems::FILE_PARAMETERS, "FileParameters", 8),
299 (&StandardItems::VIRTUAL_DISK_SIZE, "VirtualDiskSize", 8),
300 (&StandardItems::VIRTUAL_DISK_ID, "VirtualDiskId", 16),
301 (&StandardItems::LOGICAL_SECTOR_SIZE, "LogicalSectorSize", 4),
302 (
303 &StandardItems::PHYSICAL_SECTOR_SIZE,
304 "PhysicalSectorSize",
305 4,
306 ),
307 ];
308 for &(guid, name, min_len) in known_items {
309 if let Ok(entry) = table.entry(guid)
310 && entry.length() > 0
311 && entry.length() < min_len
312 {
313 Self::push_issue(
314 issues,
315 ValidationIssue::new(
316 "metadata",
317 "METADATA_ITEM_CORRUPTED",
318 format!(
319 "{name}: data length {} < expected minimum {} bytes",
320 entry.length(),
321 min_len
322 ),
323 "MS-VHDX/2.6.2",
324 ),
325 );
326 }
327 }
328 }
329
330 pub fn validate_required_metadata_items(&self) -> Result<Vec<ValidationIssue>> {
345 let mut issues = Vec::new();
346 let Some(meta_data) = self.metadata_region() else {
347 return Ok(issues);
348 };
349
350 let meta = crate::metadata::Metadata::new(meta_data)?;
351 let items = meta.items();
352
353 Self::validate_required_metadata_core(&meta, &items, &mut issues)?;
354 self.validate_required_parent_locator_item(&meta, &items, &mut issues)?;
355
356 Ok(issues)
357 }
358
359 fn validate_required_metadata_core(
360 meta: &crate::metadata::Metadata<'_>, items: &crate::metadata::MetadataItems<'_>,
361 issues: &mut Vec<ValidationIssue>,
362 ) -> Result<()> {
363 let required_items: &[(&Guid, &str)] = &[
364 (&StandardItems::FILE_PARAMETERS, "FileParameters"),
365 (&StandardItems::VIRTUAL_DISK_SIZE, "VirtualDiskSize"),
366 (&StandardItems::VIRTUAL_DISK_ID, "VirtualDiskId"),
367 (&StandardItems::LOGICAL_SECTOR_SIZE, "LogicalSectorSize"),
368 (&StandardItems::PHYSICAL_SECTOR_SIZE, "PhysicalSectorSize"),
369 ];
370 for (guid, name) in required_items {
371 Self::ensure_required_metadata_entry_present(meta, guid, name, issues)?;
372 Self::ensure_required_metadata_item_data_present(items, guid, name, issues)?;
373 }
374 Ok(())
375 }
376
377 fn ensure_required_metadata_entry_present(
378 meta: &crate::metadata::Metadata<'_>, guid: &Guid, name: &str,
379 issues: &mut Vec<ValidationIssue>,
380 ) -> Result<()> {
381 if meta.table().entry(guid).is_err() {
382 Self::push_issue(
383 issues,
384 ValidationIssue::new(
385 "metadata_required",
386 "METADATA_REQUIRED_MISSING",
387 format!("{name} entry not found in metadata table"),
388 "RELAX",
389 ),
390 );
391 return Err(Error::MetadataRequiredMissing { guid: *guid });
392 }
393 Ok(())
394 }
395
396 fn ensure_required_metadata_item_data_present(
397 items: &crate::metadata::MetadataItems<'_>, guid: &Guid, name: &str,
398 issues: &mut Vec<ValidationIssue>,
399 ) -> Result<()> {
400 match name {
401 "FileParameters" => Self::ensure_file_parameters_data(items, issues),
402 "VirtualDiskSize" if items.virtual_disk_size().is_err() => {
403 Self::push_required_data_missing(issues, name, *guid)
404 }
405 "VirtualDiskId" if items.virtual_disk_id().is_err() => {
406 Self::push_required_data_missing(issues, name, *guid)
407 }
408 "LogicalSectorSize" if items.logical_sector_size().is_err() => {
409 Self::push_required_data_missing(issues, name, *guid)
410 }
411 "PhysicalSectorSize" if items.physical_sector_size().is_err() => {
412 Self::push_required_data_missing(issues, name, *guid)
413 }
414 _ => Ok(()),
415 }
416 }
417
418 fn ensure_file_parameters_data(
419 items: &crate::metadata::MetadataItems<'_>, issues: &mut Vec<ValidationIssue>,
420 ) -> Result<()> {
421 let Ok(fp) = items.file_parameters() else {
422 Self::push_issue(
423 issues,
424 ValidationIssue::new(
425 "metadata_required",
426 "METADATA_REQUIRED_MISSING",
427 "FileParameters data not present",
428 "RELAX",
429 ),
430 );
431 return Err(Error::MetadataRequiredMissing {
432 guid: StandardItems::FILE_PARAMETERS,
433 });
434 };
435 if fp.has_reserved_bits_set() {
436 let fp_flags = fp.flags();
437 Self::push_issue(
438 issues,
439 ValidationIssue::new(
440 "metadata_required",
441 "METADATA_FILE_PARAMETERS_RESERVED_FLAGS",
442 format!("FileParameters reserved flags (bits 2-31) are set: {fp_flags:#010x}"),
443 "MS-VHDX/2.6.2.1",
444 ),
445 );
446 return Err(Error::FileParametersReservedFlags { flags: fp_flags });
447 }
448 Ok(())
449 }
450
451 fn push_required_data_missing(
452 issues: &mut Vec<ValidationIssue>, name: &str, guid: Guid,
453 ) -> Result<()> {
454 Self::push_issue(
455 issues,
456 ValidationIssue::new(
457 "metadata_required",
458 "METADATA_REQUIRED_MISSING",
459 format!("{name} data not present"),
460 "RELAX",
461 ),
462 );
463 Err(Error::MetadataRequiredMissing { guid })
464 }
465
466 fn validate_required_parent_locator_item(
467 &self, meta: &crate::metadata::Metadata<'_>, items: &crate::metadata::MetadataItems<'_>,
468 issues: &mut Vec<ValidationIssue>,
469 ) -> Result<()> {
470 if !self.has_parent() {
471 return Ok(());
472 }
473 if meta.table().entry(&StandardItems::PARENT_LOCATOR).is_err() {
474 Self::push_issue(
475 issues,
476 ValidationIssue::new(
477 "metadata_required",
478 "METADATA_REQUIRED_MISSING",
479 "ParentLocator entry not found for differencing disk",
480 "RELAX",
481 ),
482 );
483 return Err(Error::MetadataRequiredMissing {
484 guid: StandardItems::PARENT_LOCATOR,
485 });
486 }
487 if items.parent_locator().is_err() {
488 Self::push_issue(
489 issues,
490 ValidationIssue::new(
491 "metadata_required",
492 "METADATA_REQUIRED_MISSING",
493 "ParentLocator data not present for differencing disk",
494 "RELAX",
495 ),
496 );
497 return Err(Error::MetadataRequiredMissing {
498 guid: StandardItems::PARENT_LOCATOR,
499 });
500 }
501 Ok(())
502 }
503}