#![forbid(unsafe_code)]
use crate::error::{Ext4Error, Result};
use crate::ondisk::superblock::EXT4_CRC32C;
use bitflags::bitflags;
use crc::Crc;
fn le16(buf: &[u8], off: usize) -> u16 {
u16::from_le_bytes([buf[off], buf[off + 1]])
}
fn le32(buf: &[u8], off: usize) -> u32 {
u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]])
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GroupDescFlags: u16 {
const INODE_UNINIT = 0x0001;
const BLOCK_UNINIT = 0x0002;
const INODE_ZEROED = 0x0004;
}
}
#[derive(Debug, Clone)]
pub struct GroupDescriptor {
pub block_bitmap: u64,
pub inode_bitmap: u64,
pub inode_table: u64,
pub free_blocks_count: u32,
pub free_inodes_count: u32,
pub used_dirs_count: u32,
pub flags: GroupDescFlags,
pub itable_unused: u32,
pub checksum: u16,
}
impl GroupDescriptor {
pub fn parse(buf: &[u8], desc_size: u16) -> Result<Self> {
let min_len = desc_size as usize;
if buf.len() < min_len {
return Err(Ext4Error::TooShort {
structure: "GroupDescriptor",
expected: min_len,
found: buf.len(),
});
}
let block_bitmap_lo = u64::from(le32(buf, 0x00));
let inode_bitmap_lo = u64::from(le32(buf, 0x04));
let inode_table_lo = u64::from(le32(buf, 0x08));
let free_blocks_lo = u32::from(le16(buf, 0x0C));
let free_inodes_lo = u32::from(le16(buf, 0x0E));
let used_dirs_lo = u32::from(le16(buf, 0x10));
let flags_raw = le16(buf, 0x12);
let itable_unused_lo = u32::from(le16(buf, 0x1C));
let checksum = le16(buf, 0x1E);
let (
block_bitmap,
inode_bitmap,
inode_table,
free_blocks_count,
free_inodes_count,
used_dirs_count,
itable_unused,
) = if desc_size >= 64 {
let bb_hi = u64::from(le32(buf, 0x20)) << 32;
let ib_hi = u64::from(le32(buf, 0x24)) << 32;
let it_hi = u64::from(le32(buf, 0x28)) << 32;
let fb_hi = u32::from(le16(buf, 0x2C)) << 16;
let fi_hi = u32::from(le16(buf, 0x2E)) << 16;
let ud_hi = u32::from(le16(buf, 0x30)) << 16;
let iu_hi = u32::from(le16(buf, 0x32)) << 16;
(
bb_hi | block_bitmap_lo,
ib_hi | inode_bitmap_lo,
it_hi | inode_table_lo,
fb_hi | free_blocks_lo,
fi_hi | free_inodes_lo,
ud_hi | used_dirs_lo,
iu_hi | itable_unused_lo,
)
} else {
(
block_bitmap_lo,
inode_bitmap_lo,
inode_table_lo,
free_blocks_lo,
free_inodes_lo,
used_dirs_lo,
itable_unused_lo,
)
};
let flags = GroupDescFlags::from_bits_truncate(flags_raw);
Ok(Self {
block_bitmap,
inode_bitmap,
inode_table,
free_blocks_count,
free_inodes_count,
used_dirs_count,
flags,
itable_unused,
checksum,
})
}
pub fn verify_checksum(
&self,
raw_buf: &[u8],
uuid: &[u8; 16],
group: u32,
csum_seed: u32,
) -> bool {
let crc32c = Crc::<u32>::new(&EXT4_CRC32C);
let seed = if csum_seed != 0 {
csum_seed
} else {
let mut d = crc32c.digest();
d.update(uuid);
d.finalize()
};
let mut digest = crc32c.digest_with_initial(seed.reverse_bits());
digest.update(&group.to_le_bytes());
digest.update(&raw_buf[..0x1E]);
if raw_buf.len() > 0x20 {
digest.update(&[0u8; 2]); digest.update(&raw_buf[0x20..]);
}
let computed = digest.finalize();
(computed & 0xFFFF) as u16 == self.checksum
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_32byte_descriptor() {
let mut buf = vec![0u8; 32];
buf[0x00] = 100; buf[0x04] = 101; buf[0x08] = 102; buf[0x0C] = 50; buf[0x0E] = 30; buf[0x12] = 0x04;
let gd = GroupDescriptor::parse(&buf, 32).unwrap();
assert_eq!(gd.block_bitmap, 100);
assert_eq!(gd.inode_bitmap, 101);
assert_eq!(gd.inode_table, 102);
assert_eq!(gd.free_blocks_count, 50);
assert_eq!(gd.free_inodes_count, 30);
assert!(gd.flags.contains(GroupDescFlags::INODE_ZEROED));
}
#[test]
fn parse_64byte_descriptor() {
let mut buf = vec![0u8; 64];
buf[0x00] = 100;
buf[0x04] = 101;
buf[0x08] = 102;
buf[0x20] = 1; buf[0x24] = 2; buf[0x28] = 3;
let gd = GroupDescriptor::parse(&buf, 64).unwrap();
assert_eq!(gd.block_bitmap, (1u64 << 32) | 0x64);
assert_eq!(gd.inode_bitmap, (2u64 << 32) | 0x65);
assert_eq!(gd.inode_table, (3u64 << 32) | 0x66);
}
#[test]
fn reject_too_short() {
let buf = vec![0u8; 10];
let err = GroupDescriptor::parse(&buf, 32).unwrap_err();
assert!(matches!(err, crate::error::Ext4Error::TooShort { .. }));
}
#[test]
fn verify_group_descriptor_checksum_on_forensic_img() {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../tests/data/forensic.img");
let data = if let Ok(d) = std::fs::read(path) {
d
} else {
eprintln!("skip: forensic.img not found");
return;
};
use crate::ondisk::superblock::Superblock;
let sb = Superblock::parse(&data[1024..]).unwrap();
assert!(
sb.has_metadata_csum(),
"forensic.img should have metadata_csum"
);
let desc_size = sb.desc_size;
let gdt_offset = sb.block_size as usize;
let gd_buf = &data[gdt_offset..gdt_offset + desc_size as usize];
let gd = GroupDescriptor::parse(gd_buf, desc_size).unwrap();
assert!(
gd.verify_checksum(gd_buf, &sb.uuid, 0, sb.checksum_seed),
"group 0 descriptor checksum should verify"
);
}
}