ext4fs/forensic/
superblock_verify.rs1#![forbid(unsafe_code)]
2
3use crate::error::Result;
4use crate::inode::InodeReader;
5use crate::ondisk::superblock::Superblock;
6use std::io::{Read, Seek};
7
8#[derive(Debug, Clone)]
10pub struct SuperblockComparison {
11 pub group: u32,
13 pub block: u64,
15 pub matches_primary: bool,
17 pub differences: Vec<String>,
19}
20
21fn backup_groups(group_count: u32) -> Vec<u32> {
26 let mut groups = vec![0, 1]; for base in [3u32, 5, 7] {
28 let mut power = base;
29 while power < group_count {
30 if !groups.contains(&power) {
31 groups.push(power);
32 }
33 power = match power.checked_mul(base) {
34 Some(p) => p,
35 None => break,
36 };
37 }
38 }
39 groups.sort_unstable();
40 groups.retain(|&g| g < group_count);
41 groups
42}
43
44fn compare_superblocks(primary: &Superblock, backup: &Superblock) -> Vec<String> {
46 let mut diffs = Vec::new();
47
48 if primary.magic != backup.magic {
49 diffs.push("magic".to_string());
50 }
51 if primary.block_size != backup.block_size {
52 diffs.push("block_size".to_string());
53 }
54 if primary.blocks_count != backup.blocks_count {
55 diffs.push("blocks_count".to_string());
56 }
57 if primary.inodes_count != backup.inodes_count {
58 diffs.push("inodes_count".to_string());
59 }
60 if primary.blocks_per_group != backup.blocks_per_group {
61 diffs.push("blocks_per_group".to_string());
62 }
63 if primary.inodes_per_group != backup.inodes_per_group {
64 diffs.push("inodes_per_group".to_string());
65 }
66 if primary.uuid != backup.uuid {
67 diffs.push("uuid".to_string());
68 }
69 if primary.inode_size != backup.inode_size {
70 diffs.push("inode_size".to_string());
71 }
72 if primary.feature_compat != backup.feature_compat {
73 diffs.push("feature_compat".to_string());
74 }
75 if primary.feature_incompat != backup.feature_incompat {
76 diffs.push("feature_incompat".to_string());
77 }
78 if primary.feature_ro_compat != backup.feature_ro_compat {
79 diffs.push("feature_ro_compat".to_string());
80 }
81
82 diffs
83}
84
85pub fn verify_superblock_backups<R: Read + Seek>(
87 reader: &mut InodeReader<R>,
88) -> Result<Vec<SuperblockComparison>> {
89 let primary = reader.block_reader().superblock().clone();
90 let group_count = reader.block_reader().group_count();
91 let block_size = u64::from(primary.block_size);
92 let blocks_per_group = u64::from(primary.blocks_per_group);
93
94 let groups = backup_groups(group_count);
95 let mut results = Vec::new();
96
97 for &group in &groups {
98 if group == 0 {
99 continue;
100 } let block = u64::from(group) * blocks_per_group;
103
104 let sb_byte_offset = if block_size >= 2048 {
108 block * block_size
109 } else {
110 block * block_size + 1024
111 };
112
113 let buf = match reader.block_reader_mut().read_bytes(sb_byte_offset, 1024) {
115 Ok(b) => b,
116 Err(_) => continue,
117 };
118
119 let backup_sb = if let Ok(sb) = Superblock::parse(&buf) {
120 sb
121 } else {
122 results.push(SuperblockComparison {
123 group,
124 block,
125 matches_primary: false,
126 differences: vec!["unparseable".to_string()],
127 });
128 continue;
129 };
130
131 let diffs = compare_superblocks(&primary, &backup_sb);
132 results.push(SuperblockComparison {
133 group,
134 block,
135 matches_primary: diffs.is_empty(),
136 differences: diffs,
137 });
138 }
139
140 Ok(results)
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use crate::block::BlockReader;
147 use std::io::Cursor;
148
149 fn open_forensic() -> Option<InodeReader<Cursor<Vec<u8>>>> {
150 let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../tests/data/forensic.img");
151 let data = std::fs::read(path).ok()?;
152 let br = BlockReader::open(Cursor::new(data)).ok()?;
153 Some(InodeReader::new(br))
154 }
155
156 #[test]
157 fn backup_groups_small_image() {
158 let groups = backup_groups(1);
160 assert_eq!(groups, vec![0]);
161 }
162
163 #[test]
164 fn backup_groups_medium_image() {
165 let groups = backup_groups(10);
167 assert!(groups.contains(&0));
168 assert!(groups.contains(&1));
169 assert!(groups.contains(&3));
170 assert!(groups.contains(&5));
171 assert!(groups.contains(&7));
172 assert!(groups.contains(&9)); }
174
175 #[test]
176 fn backup_groups_large_image() {
177 let groups = backup_groups(200);
178 assert!(groups.contains(&0));
179 assert!(groups.contains(&1));
180 assert!(groups.contains(&25)); assert!(groups.contains(&27)); assert!(groups.contains(&49)); assert!(groups.contains(&125)); assert!(!groups.contains(&200)); }
186
187 #[test]
188 fn verify_backups_on_forensic_img() {
189 let mut reader = if let Some(r) = open_forensic() {
190 r
191 } else {
192 eprintln!("skip");
193 return;
194 };
195 let results = verify_superblock_backups(&mut reader).unwrap();
196 eprintln!("Backup verification results: {} entries", results.len());
200 for r in &results {
201 eprintln!(
202 " group {}: matches={}, diffs={:?}",
203 r.group, r.matches_primary, r.differences
204 );
205 }
206 }
207
208 #[test]
209 fn compare_identical_superblocks() {
210 let mut buf = vec![0u8; 1024];
211 buf[0x38..0x3A].copy_from_slice(&0xEF53u16.to_le_bytes());
212 buf[0x18..0x1C].copy_from_slice(&2u32.to_le_bytes()); buf[0x28..0x2C].copy_from_slice(&64u32.to_le_bytes()); buf[0x04..0x08].copy_from_slice(&100u32.to_le_bytes()); buf[0x58..0x5A].copy_from_slice(&256u16.to_le_bytes()); buf[0x4C..0x50].copy_from_slice(&1u32.to_le_bytes()); let sb = Superblock::parse(&buf).unwrap();
218 let diffs = compare_superblocks(&sb, &sb);
219 assert!(
220 diffs.is_empty(),
221 "identical superblocks should have no differences"
222 );
223 }
224}