1use super::{Error, Guid, Header, Result, SignaturePosition, SpecValidator, ValidationIssue};
2
3impl SpecValidator {
4 pub fn validate_log(&self) -> Result<Vec<ValidationIssue>> {
10 let mut issues = Vec::new();
11 let Some(log_data) = self.log_region() else {
12 return Ok(issues);
13 };
14 if Self::log_region_is_empty_or_zero(log_data) {
15 return Ok(issues);
16 }
17 let log = crate::log::Log::new(log_data)?;
18 let header_log_guid = Self::read_current_header_log_guid(&self.parse_header()?)?;
19 Self::prescan_log_signatures(log_data, &mut issues);
20 let entries: Vec<_> = log.entries().collect();
21 if entries.is_empty() {
22 if header_log_guid != Guid::zero() {
25 Self::push_issue(
26 &mut issues,
27 ValidationIssue::new(
28 "log",
29 "LOG_ACTIVE_SEQUENCE_EMPTY",
30 "header LogGuid is non-zero but no valid log entries found".to_string(),
31 "MS-VHDX/2.3.3",
32 ),
33 );
34 return Err(Error::LogActiveSequenceEmpty);
35 }
36 return Ok(issues);
37 }
38 Self::validate_log_entries(&entries, header_log_guid, &mut issues)?;
39 Self::push_log_replay_required_issue(header_log_guid, &mut issues);
40
41 Ok(issues)
42 }
43
44 fn log_region_is_empty_or_zero(log_data: &[u8]) -> bool {
45 log_data.is_empty() || log_data.iter().all(|&b| b == 0)
46 }
47
48 fn read_current_header_log_guid(header: &Header<'_>) -> Result<Guid> {
49 Ok(header.header(0)?.log_guid())
50 }
51
52 fn prescan_log_signatures(log_data: &[u8], issues: &mut Vec<ValidationIssue>) {
53 let mut scan_offset: usize = 0;
54 while scan_offset + 64 <= log_data.len() {
55 let sig = &log_data[scan_offset..scan_offset + 4];
56 if sig == b"loge" {
57 let entry_length = u32::from_le_bytes(
58 log_data[scan_offset + 8..scan_offset + 12]
59 .try_into()
60 .expect("slice length checked by loop guard"),
61 ) as usize;
62 if entry_length > 0
63 && entry_length.is_multiple_of(4096)
64 && scan_offset + entry_length <= log_data.len()
65 {
66 scan_offset += entry_length;
67 } else {
68 scan_offset += 4096;
69 }
70 } else if sig == [0u8; 4] {
71 break;
72 } else if sig == b"data" {
73 scan_offset += 4096;
74 } else {
75 let mut found = [0u8; 4];
76 found.copy_from_slice(sig);
77 Self::push_issue(
78 issues,
79 ValidationIssue::new(
80 "log",
81 "LOG_SIGNATURE_INVALID",
82 format!("expected \"loge\", found {found:?}"),
83 "MS-VHDX/2.3.1.1",
84 ),
85 );
86 scan_offset += 4096;
87 }
88 }
89 }
90
91 fn validate_log_entries(
92 entries: &[crate::log::Entry<'_>], header_log_guid: Guid, issues: &mut Vec<ValidationIssue>,
93 ) -> Result<()> {
94 let mut prev_seq: Option<u64> = None;
95 for entry in entries {
96 let seq = Self::validate_log_entry(entry, header_log_guid, prev_seq, issues)?;
97 prev_seq = Some(seq);
98 }
99 Ok(())
100 }
101
102 fn validate_log_entry(
103 entry: &crate::log::Entry<'_>, header_log_guid: Guid, prev_seq: Option<u64>,
104 issues: &mut Vec<ValidationIssue>,
105 ) -> Result<u64> {
106 Self::validate_log_entry_checksum(entry, issues)?;
107 let hdr = entry.header();
108 Self::validate_log_entry_length_and_tail(&hdr, issues)?;
109 Self::validate_log_entry_guid(&hdr, header_log_guid, issues)?;
110 let seq = Self::validate_log_sequence_continuity(&hdr, prev_seq, issues)?;
111 let data_sectors = Self::validate_log_data_sector_count(entry, issues)?;
112 Self::validate_log_data_sectors(&data_sectors, seq, issues)?;
113 Self::validate_log_descriptors(entry, seq, issues)?;
114 Ok(seq)
115 }
116
117 fn validate_log_entry_checksum(
118 entry: &crate::log::Entry<'_>, issues: &mut Vec<ValidationIssue>,
119 ) -> Result<()> {
120 if entry.verify_checksum().is_err() {
121 Self::push_issue(
122 issues,
123 ValidationIssue::new(
124 "log",
125 "LOG_ENTRY_CHECKSUM_MISMATCH",
126 "entry CRC-32C mismatch",
127 "MS-VHDX/2.3.1.1",
128 ),
129 );
130 return Err(Error::LogEntryCorrupted(
131 "LOG_ENTRY_CHECKSUM_MISMATCH: entry CRC-32C mismatch".into(),
132 ));
133 }
134 Ok(())
135 }
136
137 fn validate_log_entry_length_and_tail(
138 hdr: &crate::log::LogEntryHeader<'_>, issues: &mut Vec<ValidationIssue>,
139 ) -> Result<()> {
140 let entry_length = hdr.entry_length();
141 if entry_length == 0 || !entry_length.is_multiple_of(4096) {
142 Self::push_issue(
143 issues,
144 ValidationIssue::new(
145 "log",
146 "LOG_ENTRY_LENGTH_INVALID",
147 format!("entry_length={entry_length}"),
148 "MS-VHDX/2.3.1.1",
149 ),
150 );
151 return Err(Error::LogEntryCorrupted(format!(
152 "LOG_ENTRY_LENGTH_INVALID: entry_length={entry_length}"
153 )));
154 }
155 let tail = hdr.tail();
156 if !tail.is_multiple_of(4096) {
157 Self::push_issue(
158 issues,
159 ValidationIssue::new(
160 "log",
161 "LOG_ENTRY_TAIL_INVALID",
162 format!("tail={tail}"),
163 "MS-VHDX/2.3.1.1",
164 ),
165 );
166 return Err(Error::LogEntryCorrupted(format!(
167 "LOG_ENTRY_TAIL_INVALID: tail={tail}"
168 )));
169 }
170 Ok(())
171 }
172
173 fn validate_log_entry_guid(
174 hdr: &crate::log::LogEntryHeader<'_>, header_log_guid: Guid,
175 issues: &mut Vec<ValidationIssue>,
176 ) -> Result<()> {
177 let entry_log_guid = hdr.log_guid();
178 let is_zero_guid = entry_log_guid.to_bytes() == [0u8; 16];
179 if !is_zero_guid && entry_log_guid != header_log_guid {
180 Self::push_issue(
181 issues,
182 ValidationIssue::new(
183 "log",
184 "LOG_SEQUENCE_GUID_MISMATCH",
185 format!("entry LogGuid {entry_log_guid} != header LogGuid {header_log_guid}"),
186 "MS-VHDX/2.3.2",
187 ),
188 );
189 return Err(Error::LogSequenceGuidMismatch {
190 entry_log_guid,
191 header_log_guid,
192 });
193 }
194 Ok(())
195 }
196
197 fn validate_log_sequence_continuity(
198 hdr: &crate::log::LogEntryHeader<'_>, prev_seq: Option<u64>,
199 issues: &mut Vec<ValidationIssue>,
200 ) -> Result<u64> {
201 let seq = hdr.sequence_number();
202 if let Some(prev) = prev_seq
203 && seq != prev + 1
204 {
205 Self::push_issue(
206 issues,
207 ValidationIssue::new(
208 "log",
209 "LOG_SEQUENCE_GAP",
210 format!("seq {seq} does not follow {prev}"),
211 "MS-VHDX/2.3.2",
212 ),
213 );
214 return Err(Error::LogSequenceGap {
215 expected: prev + 1,
216 found: seq,
217 });
218 }
219 Ok(seq)
220 }
221
222 fn validate_log_data_sector_count<'b>(
223 entry: &'b crate::log::Entry<'b>, issues: &mut Vec<ValidationIssue>,
224 ) -> Result<Vec<crate::log::DataSector<'b>>> {
225 let _desc_count = entry.header().descriptor_count();
226 let actual_data_descs: usize = entry
227 .descriptors()
228 .filter_map(std::result::Result::ok)
229 .filter(|d| matches!(d, crate::log::Descriptor::Data(_)))
230 .count();
231 let data_sectors: Vec<_> = entry.data().collect();
232 if data_sectors.len() != actual_data_descs {
233 Self::push_issue(
234 issues,
235 ValidationIssue::new(
236 "log",
237 "LOG_DESCRIPTOR_COUNT_MISMATCH",
238 format!(
239 "data sectors ({}) != data descriptors ({})",
240 data_sectors.len(),
241 actual_data_descs
242 ),
243 "MS-VHDX/2.3.1",
244 ),
245 );
246 return Err(Error::LogEntryCorrupted(format!(
247 "LOG_DESCRIPTOR_COUNT_MISMATCH: data sectors ({}) != data descriptors ({})",
248 data_sectors.len(),
249 actual_data_descs
250 )));
251 }
252 Ok(data_sectors)
253 }
254
255 fn validate_log_data_sectors(
256 data_sectors: &[crate::log::DataSector<'_>], seq: u64, issues: &mut Vec<ValidationIssue>,
257 ) -> Result<()> {
258 for sector in data_sectors {
259 let sig = sector.signature();
260 if sig != b"data" {
261 Self::push_issue(
262 issues,
263 ValidationIssue::new(
264 "log",
265 "LOG_DATA_SECTOR_INVALID",
266 "invalid data sector signature",
267 "MS-VHDX/2.3.1.4",
268 ),
269 );
270 return Err(Error::InvalidSignature {
271 position: SignaturePosition::DataSector,
272 expected: crate::error::pad_signature_4to8(*b"data"),
273 found: crate::error::pad_signature_4to8(*sig),
274 });
275 }
276 let sector_seq = sector.sequence_number();
277 if sector_seq != seq {
278 Self::push_issue(
279 issues,
280 ValidationIssue::new(
281 "log",
282 "LOG_DATA_SECTOR_INVALID",
283 format!("sector seq {sector_seq} != entry seq {seq}"),
284 "MS-VHDX/2.3.1.4",
285 ),
286 );
287 return Err(Error::LogEntryCorrupted(format!(
288 "LOG_DATA_SECTOR_INVALID: sector seq {sector_seq} != entry seq {seq}"
289 )));
290 }
291 }
292 Ok(())
293 }
294
295 fn validate_log_descriptors(
296 entry: &crate::log::Entry<'_>, seq: u64, issues: &mut Vec<ValidationIssue>,
297 ) -> Result<()> {
298 for desc_result in entry.descriptors() {
299 let desc = match desc_result {
300 Ok(d) => d,
301 Err(e) => {
302 Self::push_issue(
303 issues,
304 ValidationIssue::new(
305 "log",
306 "LOG_DESCRIPTOR_SIGNATURE_INVALID",
307 format!("{e}"),
308 "MS-VHDX/2.3.1",
309 ),
310 );
311 return Err(Error::LogEntryCorrupted(format!(
312 "LOG_DESCRIPTOR_SIGNATURE_INVALID: {e}"
313 )));
314 }
315 };
316 let desc_seq = desc.sequence_number();
317 if desc_seq != seq {
318 Self::push_issue(
319 issues,
320 ValidationIssue::new(
321 "log",
322 "LOG_DESCRIPTOR_SEQUENCE_MISMATCH",
323 format!("descriptor seq {desc_seq} != entry seq {seq}"),
324 "MS-VHDX/2.3.1",
325 ),
326 );
327 return Err(Error::LogEntryCorrupted(format!(
328 "LOG_DESCRIPTOR_SEQUENCE_MISMATCH: descriptor seq {desc_seq} != entry seq {seq}"
329 )));
330 }
331 }
332 Ok(())
333 }
334
335 fn push_log_replay_required_issue(header_log_guid: Guid, issues: &mut Vec<ValidationIssue>) {
336 if header_log_guid != Guid::zero() {
337 Self::push_issue(
338 issues,
339 ValidationIssue::new(
340 "log",
341 "LOG_REPLAY_REQUIRED",
342 "replayable log entries exist (use --log-replay to replay)",
343 "ROEXT",
344 ),
345 );
346 }
347 }
348}