Skip to main content

vhdx/validation/
log.rs

1use super::{Error, Guid, Header, Result, SignaturePosition, SpecValidator, ValidationIssue};
2
3impl SpecValidator {
4    /// Validate the log section.
5    ///
6    /// # Errors
7    ///
8    /// Returns an error when log entry integrity or sequencing checks fail.
9    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 the header indicates a log should exist (LogGuid != 0) but the
23            // log region contains no parseable entries, the active sequence is empty.
24            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}