Skip to main content

ext4fs/
block.rs

1#![forbid(unsafe_code)]
2use crate::error::{Ext4Error, Result};
3use crate::ondisk::{GroupDescriptor, Superblock};
4use std::io::{Read, Seek, SeekFrom};
5use std::sync::{Mutex, PoisonError};
6
7#[derive(Debug)]
8pub struct BlockReader<R: Read + Seek> {
9    // Interior mutability: the positioned `seek`+`read_exact` needs `&mut R`, but
10    // the forensic-vfs `FileSystem` contract serves every read through `&self`
11    // (one `Arc<dyn FileSystem>` shared across N workers). A `Mutex` gives that
12    // shared handle while keeping the reader `Send + Sync`.
13    source: Mutex<R>,
14    superblock: Superblock,
15    group_descs: Vec<GroupDescriptor>,
16}
17
18impl<R: Read + Seek> BlockReader<R> {
19    pub fn open(mut source: R) -> Result<Self> {
20        // Read superblock at offset 1024
21        source.seek(SeekFrom::Start(1024))?;
22        let mut sb_buf = vec![0u8; 1024];
23        source.read_exact(&mut sb_buf)?;
24        let superblock = Superblock::parse(&sb_buf)?;
25
26        // GDT starts in block after superblock.
27        // For 1024-byte blocks, superblock occupies block 1, GDT starts at block 2.
28        // For larger blocks, both fit in block 0, GDT starts at block 1.
29        let gdt_block = if superblock.block_size == 1024 {
30            2u64
31        } else {
32            1u64
33        };
34        let gdt_offset = gdt_block * u64::from(superblock.block_size);
35        let group_count = superblock.group_count();
36        let desc_size = superblock.desc_size as usize;
37
38        // Guard against crafted images with huge group_count causing OOM before
39        // read_exact can return an EOF error.
40        let source_size = source.seek(SeekFrom::End(0))?;
41        let available = source_size.saturating_sub(gdt_offset);
42        let gdt_size = u64::from(group_count)
43            .saturating_mul(desc_size as u64)
44            .min(available);
45        if u64::from(group_count).saturating_mul(desc_size as u64) > available {
46            return Err(Ext4Error::CorruptMetadata {
47                structure: "GroupDescriptorTable",
48                detail: format!(
49                    "GDT requires {} bytes but only {available} available",
50                    u64::from(group_count).saturating_mul(desc_size as u64)
51                ),
52            });
53        }
54
55        source.seek(SeekFrom::Start(gdt_offset))?;
56        let mut gdt_buf = vec![0u8; gdt_size as usize];
57        source.read_exact(&mut gdt_buf)?;
58
59        let mut group_descs = Vec::with_capacity(group_count as usize);
60        for i in 0..group_count as usize {
61            let off = i * desc_size;
62            let gd = GroupDescriptor::parse(&gdt_buf[off..off + desc_size], superblock.desc_size)?;
63            group_descs.push(gd);
64        }
65
66        Ok(BlockReader {
67            source: Mutex::new(source),
68            superblock,
69            group_descs,
70        })
71    }
72
73    pub fn superblock(&self) -> &Superblock {
74        &self.superblock
75    }
76
77    pub fn group_descriptors(&self) -> &[GroupDescriptor] {
78        &self.group_descs
79    }
80
81    pub fn group_count(&self) -> u32 {
82        self.group_descs.len() as u32
83    }
84
85    pub fn block_size(&self) -> u32 {
86        self.superblock.block_size
87    }
88
89    pub fn read_block(&self, block_num: u64) -> Result<Vec<u8>> {
90        if block_num >= self.superblock.blocks_count {
91            return Err(Ext4Error::BlockOutOfRange {
92                block: block_num,
93                max: self.superblock.blocks_count,
94            });
95        }
96        let offset = block_num * u64::from(self.superblock.block_size);
97        self.read_bytes(offset, self.superblock.block_size as usize)
98    }
99
100    pub fn read_blocks(&self, start: u64, count: u64) -> Result<Vec<u8>> {
101        let end = start.checked_add(count).ok_or(Ext4Error::BlockOutOfRange {
102            block: start,
103            max: self.superblock.blocks_count,
104        })?;
105        if end > self.superblock.blocks_count {
106            return Err(Ext4Error::BlockOutOfRange {
107                block: end - 1,
108                max: self.superblock.blocks_count,
109            });
110        }
111        let offset = start * u64::from(self.superblock.block_size);
112        let len = count as usize * self.superblock.block_size as usize;
113        self.read_bytes(offset, len)
114    }
115
116    pub fn read_bytes(&self, offset: u64, len: usize) -> Result<Vec<u8>> {
117        // Poison-safe lock: a panic in another thread while holding the guard
118        // must not turn every subsequent read into a panic (Paranoid Gatekeeper).
119        let mut source = self.source.lock().unwrap_or_else(PoisonError::into_inner);
120        source.seek(SeekFrom::Start(offset))?;
121        let mut buf = vec![0u8; len];
122        source.read_exact(&mut buf)?;
123        Ok(buf)
124    }
125
126    pub fn group_descriptor(&self, group: u32) -> Result<&GroupDescriptor> {
127        self.group_descs
128            .get(group as usize)
129            .ok_or(Ext4Error::CorruptMetadata {
130                structure: "group_descriptor",
131                detail: format!(
132                    "group {group} out of range (max {})",
133                    self.group_descs.len()
134                ),
135            })
136    }
137
138    pub fn inode_bitmap_block(&self, group: u32) -> Result<u64> {
139        Ok(self.group_descriptor(group)?.inode_bitmap)
140    }
141
142    pub fn block_bitmap_block(&self, group: u32) -> Result<u64> {
143        Ok(self.group_descriptor(group)?.block_bitmap)
144    }
145
146    pub fn inode_table_block(&self, group: u32) -> Result<u64> {
147        Ok(self.group_descriptor(group)?.inode_table)
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use std::io::Cursor;
155
156    fn load_minimal_image() -> Option<Vec<u8>> {
157        let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../tests/data/minimal.img");
158        std::fs::read(path).ok()
159    }
160
161    #[test]
162    fn open_minimal_image() {
163        let data = load_minimal_image().expect("minimal.img required");
164        let reader = BlockReader::open(Cursor::new(data)).unwrap();
165        assert_eq!(reader.superblock().magic, 0xEF53);
166        assert_eq!(reader.superblock().block_size, 4096);
167        assert!(reader.group_count() > 0);
168        assert!(!reader.group_descriptors().is_empty());
169    }
170
171    #[test]
172    fn reject_too_small_image() {
173        let data = vec![0u8; 512];
174        let err = BlockReader::open(Cursor::new(data)).unwrap_err();
175        assert!(matches!(
176            err,
177            crate::error::Ext4Error::Io(_) | crate::error::Ext4Error::TooShort { .. }
178        ));
179    }
180
181    #[test]
182    fn read_block_zero() {
183        let data = load_minimal_image().expect("minimal.img required");
184        let reader = BlockReader::open(Cursor::new(data)).unwrap();
185        let block = reader.read_block(0).unwrap();
186        assert_eq!(block.len(), 4096);
187    }
188
189    #[test]
190    fn read_block_out_of_range() {
191        let data = load_minimal_image().expect("minimal.img required");
192        let reader = BlockReader::open(Cursor::new(data)).unwrap();
193        let err = reader.read_block(u64::MAX).unwrap_err();
194        assert!(matches!(
195            err,
196            crate::error::Ext4Error::BlockOutOfRange { .. }
197        ));
198    }
199
200    #[test]
201    fn reads_are_served_through_a_shared_ref() {
202        // The forensic-vfs `FileSystem` trait serves every read through `&self`
203        // so one mounted handle backs N workers. Pin that BlockReader reads take
204        // `&self`: two shared borrows read at the same time (uncompilable while
205        // the read methods take `&mut self`).
206        let data = load_minimal_image().expect("minimal.img required");
207        let reader = BlockReader::open(Cursor::new(data)).unwrap();
208        let a: &BlockReader<Cursor<Vec<u8>>> = &reader;
209        let b: &BlockReader<Cursor<Vec<u8>>> = &reader;
210        let x = a.read_bytes(1024, 2).unwrap();
211        let y = b.read_bytes(1024, 2).unwrap();
212        assert_eq!(x.len(), 2);
213        assert_eq!(y.len(), 2);
214    }
215}