1use super::{
2 Error, MIB, Result, SignaturePosition, SpecValidator, ValidationIssue, is_known_region_guid,
3};
4
5impl SpecValidator {
6 pub fn validate_region_table(&self) -> Result<Vec<ValidationIssue>> {
24 let mut issues = Vec::new();
25 let header = self.parse_header()?;
26
27 let rt1 = header.region_table(1);
29 let rt2 = header.region_table(2);
30
31 match (&rt1, &rt2) {
32 (Err(e), _) | (_, Err(e)) => {
33 if let Error::InvalidSignature {
34 position: SignaturePosition::RegionTable,
35 ..
36 } = e
37 {
38 Self::push_issue(
39 &mut issues,
40 ValidationIssue::new(
41 "region_table",
42 "REGION_SIGNATURE_INVALID",
43 format!("region table signature error: {e}"),
44 "MS-VHDX/2.2.3.1",
45 ),
46 );
47 return Err(Error::InvalidRegionTable(format!("{e}")));
48 }
49 Self::push_issue(
50 &mut issues,
51 ValidationIssue::new(
52 "region_table",
53 "REGION_CHECKSUM_MISMATCH",
54 format!("{e}"),
55 "MS-VHDX/2.2.3.1",
56 ),
57 );
58 return Err(Error::InvalidRegionTable(format!("{e}")));
59 }
60 _ => {}
61 }
62
63 let rt1 = rt1.unwrap();
64 let rt2 = rt2.unwrap();
65
66 for (idx, rt) in [rt1, rt2].iter().enumerate() {
69 let count = rt.header().entry_count();
70 if count > 2047 {
71 Self::push_issue(
72 &mut issues,
73 ValidationIssue::new(
74 "region_table",
75 "REGION_ENTRY_COUNT_EXCEEDS_MAXIMUM",
76 format!("region table {idx} entry count {count} exceeds maximum of 2047"),
77 "MS-VHDX/2.2.3.1",
78 ),
79 );
80 return Err(Error::InvalidRegionTable(format!(
81 "REGION_ENTRY_COUNT_EXCEEDS_MAXIMUM: region table {idx} entry count {count} exceeds maximum of 2047"
82 )));
83 }
84 }
85
86 let current_rt = match header.region_table(0) {
88 Ok(rt) => rt,
89 Err(e) => {
90 Self::push_issue(
91 &mut issues,
92 ValidationIssue::new(
93 "region_table",
94 "REGION_CHECKSUM_MISMATCH",
95 format!("current region table: {e}"),
96 "MS-VHDX/2.2.3.1",
97 ),
98 );
99 return Err(Error::InvalidRegionTable(format!(
100 "current region table: {e}"
101 )));
102 }
103 };
104
105 issues.extend(self.validate_region_entries(¤t_rt)?);
106
107 Ok(issues)
108 }
109
110 fn validate_region_entries(
112 &self, rt: &crate::header::RegionTable<'_>,
113 ) -> Result<Vec<ValidationIssue>> {
114 let mut issues = Vec::new();
115 let entries: Vec<_> = rt.entries().collect();
116
117 for (i, entry) in entries.iter().enumerate() {
118 self.validate_region_entry(i, entry, &entries, &mut issues)?;
119 }
120
121 Ok(issues)
122 }
123
124 fn validate_region_entry(
125 &self, i: usize, entry: &crate::header::RegionTableEntry<'_>,
126 entries: &[crate::header::RegionTableEntry<'_>], issues: &mut Vec<ValidationIssue>,
127 ) -> Result<()> {
128 let file_offset = entry.file_offset();
129 let length = entry.length();
130
131 if !file_offset.is_multiple_of(u64::from(MIB)) {
132 Self::push_issue(
133 issues,
134 ValidationIssue::new(
135 "region_table",
136 "REGION_ENTRY_ALIGNMENT",
137 format!("entry {i} file_offset {file_offset:#x} not 1MB-aligned"),
138 "MS-VHDX/2.2.3.2",
139 ),
140 );
141 return Err(Error::InvalidRegionTable(format!(
142 "REGION_ENTRY_ALIGNMENT: entry {i} file_offset {file_offset:#x} not 1MB-aligned"
143 )));
144 }
145 if file_offset < u64::from(MIB) {
146 Self::push_issue(
147 issues,
148 ValidationIssue::new(
149 "region_table",
150 "REGION_ENTRY_OFFSET_MINIMUM",
151 format!("entry {i} file_offset {file_offset} < 1MB minimum"),
152 "MS-VHDX/2.2.3.2",
153 ),
154 );
155 return Err(Error::InvalidRegionTable(format!(
156 "REGION_ENTRY_OFFSET_MINIMUM: entry {i} file_offset {file_offset} < 1MB minimum"
157 )));
158 }
159 if u64::from(length) % u64::from(MIB) != 0 {
160 Self::push_issue(
161 issues,
162 ValidationIssue::new(
163 "region_table",
164 "REGION_ENTRY_ALIGNMENT",
165 format!("entry {i} length {length} not 1MB-aligned"),
166 "MS-VHDX/2.2.3.2",
167 ),
168 );
169 return Err(Error::InvalidRegionTable(format!(
170 "REGION_ENTRY_ALIGNMENT: entry {i} length {length} not 1MB-aligned"
171 )));
172 }
173
174 Self::validate_region_entry_overlap(i, file_offset, length, entries, issues)?;
175 self.validate_region_entry_guid(entry, issues)
176 }
177
178 fn validate_region_entry_overlap(
179 i: usize, file_offset: u64, length: u32, entries: &[crate::header::RegionTableEntry<'_>],
180 issues: &mut Vec<ValidationIssue>,
181 ) -> Result<()> {
182 let end = file_offset + u64::from(length);
183 for (j, prev) in entries[..i].iter().enumerate() {
184 let prev_end = prev.file_offset() + u64::from(prev.length());
185 if file_offset < prev_end && prev.file_offset() < end {
186 Self::push_issue(
187 issues,
188 ValidationIssue::new(
189 "region_table",
190 "REGION_ENTRY_OVERLAP",
191 format!("entries {j} and {i} overlap"),
192 "MS-VHDX/2.1",
193 ),
194 );
195 return Err(Error::InvalidRegionTable(format!(
196 "REGION_ENTRY_OVERLAP: entries {j} and {i} overlap"
197 )));
198 }
199 }
200 Ok(())
201 }
202
203 fn validate_region_entry_guid(
204 &self, entry: &crate::header::RegionTableEntry<'_>, issues: &mut Vec<ValidationIssue>,
205 ) -> Result<()> {
206 if is_known_region_guid(&entry.guid()) {
207 return Ok(());
208 }
209 if entry.required() {
210 Self::push_issue(
211 issues,
212 ValidationIssue::new(
213 "region_table",
214 "REGION_REQUIRED_UNKNOWN",
215 format!("required unknown region GUID {}", entry.guid()),
216 "RELAX",
217 ),
218 );
219 return Err(Error::RegionRequiredUnknown { guid: entry.guid() });
220 }
221 if self.strict {
222 Self::push_issue(
223 issues,
224 ValidationIssue::new(
225 "region_table",
226 "REGION_OPTIONAL_UNKNOWN",
227 format!(
228 "optional unknown region GUID {} in strict mode",
229 entry.guid()
230 ),
231 "RELAX",
232 ),
233 );
234 return Err(Error::RegionOptionalUnknown { guid: entry.guid() });
235 }
236 Self::push_issue(
237 issues,
238 ValidationIssue::new(
239 "region_table",
240 "REGION_OPTIONAL_UNKNOWN",
241 format!(
242 "optional unknown region GUID {} tolerated in non-strict mode",
243 entry.guid()
244 ),
245 "RELAX",
246 ),
247 );
248 Ok(())
249 }
250}