From 98b6f5d1f65635bcdf50298cc2b76b150adf01d0 Mon Sep 17 00:00:00 2001 From: Florian Stecker Date: Thu, 3 Aug 2023 19:15:38 -0400 Subject: [PATCH] adapt address translator to derive parser --- Cargo.toml | 1 + src/addrmap.rs | 86 ++++++++++++ src/btrfs_structs.rs | 318 ++++++++++++++++++++++++++++--------------- src/main.rs | 37 ++++- 4 files changed, 332 insertions(+), 110 deletions(-) create mode 100644 src/addrmap.rs diff --git a/Cargo.toml b/Cargo.toml index 390e56f..c6119aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.72" binparse_derive = { path = "../binparse_derive" } memmap2 = "0.7.1" rouille = "3.6.2" diff --git a/src/addrmap.rs b/src/addrmap.rs new file mode 100644 index 0000000..eb66d60 --- /dev/null +++ b/src/addrmap.rs @@ -0,0 +1,86 @@ +use crate::btrfs_structs::{ParseBin, Key, ChunkItem, Leaf, Value, Superblock, LogToPhys}; + +#[derive(Debug)] +pub struct AddressMap(Vec<(u64,u64,Vec<(u64,u64)>)>); + +// TODO: support for internal nodes, multiple devices? +impl AddressMap { + pub fn new(image: &[u8]) -> Result { + let superblock = Superblock::parse(&image[0x10000..])?; + let bootstrap_addr = AddressMap::from_superblock(&superblock)?; + + let chunk_root_log = superblock.chunk_root; + println!("Chunk Tree Root Logical Address: {:016x}", chunk_root_log); + + let chunk_root_phys = bootstrap_addr.to_phys(chunk_root_log).unwrap(); + println!("Chunk Tree Root Physical Address: {:016x}", chunk_root_phys); + + let chunk_root = Leaf::parse(&image[chunk_root_phys as usize..])?; + + let mut addr_map = Vec::new(); + for item in chunk_root.items { + let chunk_key = item.key; + if let Value::Chunk(chunk_value) = item.value { + addr_map.push(( + chunk_key.key_offset, + chunk_value.size, + chunk_value.stripes.iter() + .map(|stripe|(stripe.devid, stripe.offset)) + .collect() + )); + } + } + + addr_map.sort_by_key(|x|x.0); + println!("Address Table: {:?}", addr_map); + Ok(AddressMap(addr_map)) + } + + pub fn from_superblock(superblock: &Superblock) -> Result { + let sys_chunk_array_size = superblock.sys_chunk_array_size; + let sys_chunk_array: &[u8] = &superblock.sys_chunk_array; + let mut addr_map = Vec::new(); + let mut len: usize = 0; + + while len < sys_chunk_array_size as usize { + let chunk_key: Key = Key::parse(&sys_chunk_array[len .. ])?; + let chunk_value: ChunkItem = ChunkItem::parse(&sys_chunk_array[len+0x11 .. ])?; + + addr_map.push(( + chunk_key.key_offset, + chunk_value.size, + chunk_value.stripes.iter() + .map(|stripe|(stripe.devid, stripe.offset)) + .collect() + )); + len += 0x41 + 0x20 * chunk_value.stripes.len(); + } + + addr_map.sort_by_key(|x|x.0); + println!("Bootstrap Address Table: {:?}", addr_map); + Ok(AddressMap(addr_map)) + } +} + +impl LogToPhys for AddressMap { + fn to_phys(&self, log: u64) -> Option { + let index = match self.0.binary_search_by_key(&log, |x|x.0) { + Ok(idx) => idx as usize, + Err(idx) => if idx == 0 { + return None; + } else { + idx-1 as usize + } + }; + + let log_offset = self.0[index].0; + let size = self.0[index].1; + let phys_offset = self.0[index].2[0].1; + + if log >= log_offset && log < log_offset + size { + Some(phys_offset + (log - log_offset)) + } else { + None + } + } +} diff --git a/src/btrfs_structs.rs b/src/btrfs_structs.rs index 432db3f..2a50ace 100644 --- a/src/btrfs_structs.rs +++ b/src/btrfs_structs.rs @@ -1,14 +1,16 @@ use binparse_derive::AllVariants; use binparse_derive::ParseBin; use std::fmt; +use std::error; +use std::io; use std::ffi::CString; /***** BTRFS structures *****/ -const NODE_SIZE: usize = 0x4000; +pub const NODE_SIZE: usize = 0x4000; #[allow(unused)] -#[derive(Debug,Clone,Copy,AllVariants)] +#[derive(Debug,Clone,Copy,AllVariants,PartialEq,Eq,PartialOrd,Ord)] #[repr(u8)] pub enum ItemType { Invalid = 0x00, @@ -41,7 +43,7 @@ pub enum ItemType { FreeSpaceBitmap = 0xc8, DevExtent = 0xcc, // implemented Dev = 0xd8, // implemented - Chunk = 0xe4, // implemented? (awaiting len feature) + Chunk = 0xe4, // implemented QGroupStatus = 0xf0, QGroupInfo = 0xf2, QGroupLimit = 0xf4, @@ -55,15 +57,15 @@ pub enum ItemType { } #[allow(unused)] -#[derive(Debug,Clone,Copy,ParseBin)] +#[derive(Debug,Clone,Copy,ParseBin,PartialEq,Eq,PartialOrd,Ord)] pub struct Key { - key_id: u64, - key_type: ItemType, - key_offset: u64, + pub key_id: u64, + pub key_type: ItemType, + pub key_offset: u64, } #[allow(unused)] -#[derive(Debug)] +#[derive(Debug,Clone)] pub enum Value { Extent(ExtentItem), BlockGroup(BlockGroupItem), @@ -82,144 +84,192 @@ pub enum Value { #[allow(unused)] #[allow(unused)] -#[derive(Debug)] +#[derive(Debug,Clone)] pub struct Item { - key: Key, - value: Value, + pub key: Key, + pub value: Value, } #[allow(unused)] -#[derive(ParseBin)] +#[derive(Clone,ParseBin)] pub struct Checksum([u8; 32]); #[allow(unused)] -#[derive(ParseBin)] +#[derive(Clone,ParseBin)] pub struct UUID([u8; 16]); #[allow(unused)] #[derive(Debug,ParseBin)] +pub struct Superblock { + pub csum: Checksum, + pub fsid: UUID, + pub bytenr: u64, + pub flags: u64, + pub magic: u64, + pub generation: u64, + pub root: u64, + pub chunk_root: u64, + pub log_root: u64, + #[skip_bytes = 8] + pub total_bytes: u64, + pub bytes_used: u64, + + pub root_dir_objectid: u64, + pub num_devices: u64, + pub sectorsize: u32, + pub nodesize: u32, + #[skip_bytes = 4] + pub stripesize: u32, + pub sys_chunk_array_size: u32, + pub chunk_root_generation: u64, + pub compat_flags: u64, + pub compat_ro_flags: u64, + pub incompat_flags: u64, + pub csum_type: u16, + pub root_level: u8, + pub chunk_root_level: u8, + pub log_root_level: u8, + + pub dev_item: DevItem, + + pub label: [u8; 0x100], + + pub cache_generation: u64, + pub uuid_tree_generation: u64, + + pub metadata_uuid: UUID, + pub nr_global_roots: u64, + + #[skip_bytes = 216] + pub sys_chunk_array: [u8; 0x800], + + // next up would be the root backups, but we don't read them for now +} + +#[allow(unused)] +#[derive(Debug,Clone,ParseBin)] pub struct NodeHeader { - csum: Checksum, - fs_uid: UUID, - bytenr: u64, - flags: u64, - chunk_tree_uid: UUID, - generation: u64, - owner: u64, - nritems: u32, - level: u8, + pub csum: Checksum, + pub fs_uid: UUID, + pub bytenr: u64, + pub flags: u64, + pub chunk_tree_uid: UUID, + pub generation: u64, + pub owner: u64, + pub nritems: u32, + pub level: u8, } #[allow(unused)] -#[derive(Debug)] +#[derive(Debug,Clone)] pub struct Leaf { - header: NodeHeader, - items: Vec, + pub header: NodeHeader, + pub items: Vec, } #[allow(unused)] -#[derive(Debug,ParseBin)] +#[derive(Debug,Clone,ParseBin)] pub struct BlockGroupItem { - used: u64, - chunk_objectid: u64, - flags: u64, + pub used: u64, + pub chunk_objectid: u64, + pub flags: u64, } #[allow(unused)] -#[derive(Debug,ParseBin)] +#[derive(Debug,Clone,ParseBin)] pub struct ExtentItem { - refs: u64, - generation: u64, - flags: u64, - data: Vec, + pub refs: u64, + pub generation: u64, + pub flags: u64, + pub data: Vec, } #[allow(unused)] -#[derive(Debug,ParseBin)] +#[derive(Debug,Clone,ParseBin)] pub struct Time { - sec: u64, - nsec: u32, + pub sec: u64, + pub nsec: u32, } #[allow(unused)] -#[derive(Debug,ParseBin)] +#[derive(Debug,Clone,ParseBin)] pub struct InodeItem { - generation: u64, - transid: u64, - size: u64, - nbytes: u64, - block_group: u64, - nlink: u32, - uid: u32, - gid: u32, - mode: u32, - rdev: u64, - flags: u64, - sequence: u64, + pub generation: u64, + pub transid: u64, + pub size: u64, + pub nbytes: u64, + pub block_group: u64, + pub nlink: u32, + pub uid: u32, + pub gid: u32, + pub mode: u32, + pub rdev: u64, + pub flags: u64, + pub sequence: u64, #[skip_bytes = 32] - atime: Time, - ctime: Time, - mtime: Time, - otime: Time, + pub atime: Time, + pub ctime: Time, + pub mtime: Time, + pub otime: Time, } #[allow(unused)] -#[derive(Debug,ParseBin)] +#[derive(Debug,Clone,ParseBin)] pub struct ChunkItem { - size: u64, - root: u64, - stripelen: u64, - chunktype: u64, - align: u32, - width: u32, - sectorsize: u32, - nrstripes: u16, - substripes: u16, + pub size: u64, + pub root: u64, + pub stripelen: u64, + pub chunktype: u64, + pub align: u32, + pub width: u32, + pub sectorsize: u32, + pub nrstripes: u16, + pub substripes: u16, -// #[vector_length = nrstripes] -// stripes: Vec, + #[len = "nrstripes"] + pub stripes: Vec, } #[allow(unused)] -#[derive(Debug,ParseBin)] +#[derive(Debug,Clone,ParseBin)] pub struct ChunkItemStripe { - devid: u64, - offset: u64, - devuuid: UUID, + pub devid: u64, + pub offset: u64, + pub devuuid: UUID, } #[allow(unused)] -#[derive(Debug,ParseBin)] +#[derive(Debug,Clone,ParseBin)] pub struct RootItem { - inode: InodeItem, - generation: u64, - root_dirid: u64, - bytenr: u64, - byte_limit: u64, - bytes_used: u64, - last_snapshot: u64, - flags: u64, - refs: u32, - drop_progress: Key, - drop_level: u8, - level: u8, + pub inode: InodeItem, + pub generation: u64, + pub root_dirid: u64, + pub bytenr: u64, + pub byte_limit: u64, + pub bytes_used: u64, + pub last_snapshot: u64, + pub flags: u64, + pub refs: u32, + pub drop_progress: Key, + pub drop_level: u8, + pub level: u8, - generation_v2: u64, - uuid: UUID, - parent_uuid: UUID, - received_uuid: UUID, - ctransid: u64, - otransid: u64, - stransid: u64, - rtransid: u64, - ctime: Time, - otime: Time, - stime: Time, - rtime: Time, + pub generation_v2: u64, + pub uuid: UUID, + pub parent_uuid: UUID, + pub received_uuid: UUID, + pub ctransid: u64, + pub otransid: u64, + pub stransid: u64, + pub rtransid: u64, + pub ctime: Time, + pub otime: Time, + pub stime: Time, + pub rtime: Time, } #[allow(unused)] -#[derive(Debug,ParseBin)] +#[derive(Debug,Clone,ParseBin)] pub struct DirItem { location: Key, transid: u64, @@ -227,25 +277,25 @@ pub struct DirItem { name_len: u16, dir_type: u8, - #[len = "name_len"] +// #[len = "name_len"] name: CString, } #[allow(unused)] -#[derive(Debug,ParseBin)] +#[derive(Debug,Clone,ParseBin)] pub struct FreeSpaceInfoItem { extent_count: u32, flags: u32, } #[allow(unused)] -#[derive(Debug,ParseBin)] +#[derive(Debug,Clone,ParseBin)] pub struct UUIDSubvolItem { subvol_id: u64, } #[allow(unused)] -#[derive(Debug,ParseBin)] +#[derive(Debug,Clone,ParseBin)] pub struct DevItem { devid: u64, total_bytes: u64, @@ -264,7 +314,7 @@ pub struct DevItem { } #[allow(unused)] -#[derive(Debug,ParseBin)] +#[derive(Debug,Clone,ParseBin)] pub struct DevExtentItem { chunk_tree: u64, chunk_objectid: u64, @@ -274,21 +324,21 @@ pub struct DevExtentItem { } #[allow(unused)] -#[derive(Debug)] +#[derive(Debug,Clone)] pub struct ExtentDataItem { header: ExtentDataHeader, data: ExtentDataBody, } #[allow(unused)] -#[derive(Debug)] +#[derive(Debug,Clone)] pub enum ExtentDataBody { Inline(Vec), External(ExternalExtent), } #[allow(unused)] -#[derive(Debug,ParseBin)] +#[derive(Debug,Clone,ParseBin)] pub struct ExternalExtent { disk_bytenr: u64, disk_num_bytes: u64, @@ -297,7 +347,7 @@ pub struct ExternalExtent { } #[allow(unused)] -#[derive(Debug,ParseBin)] +#[derive(Debug,Clone,ParseBin)] pub struct ExtentDataHeader { generation: u64, ram_bytes: u64, @@ -310,6 +360,35 @@ pub struct ExtentDataHeader { /***** trait for parsing, and implementations for basic types *****/ // most of the more complex types will be parsed using derive macros +#[derive(Debug)] +pub struct ParseError(String); + +impl error::Error for ParseError {} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.0) + } +} + +impl From for ParseError { + fn from(value: String) -> ParseError { + ParseError(value) + } +} + +impl From<&str> for ParseError { + fn from(value: &str) -> ParseError { + ParseError::from(String::from(value)) + } +} + +impl From for io::Error { + fn from(value: ParseError) -> io::Error { + io::Error::other(value) + } +} + pub trait ParseBin where Self: Sized { fn parse_len(bytes: &[u8]) -> Result<(Self, usize), String>; @@ -454,7 +533,6 @@ impl ParseBin for Leaf { Value::DevExtent(DevExtentItem::parse(data_slice)?), ItemType::ExtentData => Value::ExtentData(ExtentDataItem::parse(data_slice)?), - _ => Value::Unknown(Vec::from(data_slice)), }; @@ -489,6 +567,28 @@ impl ParseBin for ExtentDataItem { } } +/***** looking up keys *****/ + +impl Leaf { + pub fn find_key(&self, key: Key) -> Option { + self.items.iter().find(|x|x.key == key).map(|x|x.clone()) + } +} + +pub fn node_at_log<'a, T: LogToPhys>(image: &'a [u8], addr: &T, log: u64) -> Option<&'a [u8]> { + let phys_addr = addr.to_phys(log)?; + Some(&image[phys_addr as usize .. phys_addr as usize + NODE_SIZE]) +} + +pub trait LogToPhys { + fn to_phys(&self, log: u64) -> Option; +} + +pub fn find_key_in_tree(image: &[u8], addr: &T, root_addr_log: u64, key: Key) -> Option { + // assuming level is 0 + let leaf = Leaf::parse(node_at_log(image, addr, root_addr_log)?).ok()?; + leaf.find_key(key) +} /***** prettier debug output for UUIDs and checksums *****/ @@ -520,3 +620,9 @@ impl fmt::Debug for Checksum { x24, x25, x26, x27, x28, x29, x30, x31) } } + +impl Key { + pub fn new(key_id: u64, key_type: ItemType, key_offset: u64) -> Key { + Key { key_id, key_type, key_offset } + } +} diff --git a/src/main.rs b/src/main.rs index 46af62a..0fdb3ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,48 @@ -mod btrfs_structs; +#![feature(io_error_other)] +mod btrfs_structs; +mod addrmap; + +use addrmap::AddressMap; use memmap2::Mmap; use std::fs::File; +use std::io::Error as IOError; +use anyhow::Error as AError; use rouille::Request; use rouille::Response; -use btrfs_structs::{ItemType, ParseBin, Key, Item, Leaf, NodeHeader}; +use btrfs_structs::{ItemType, ParseBin, Key, Item, Leaf, NodeHeader, NODE_SIZE, Superblock, Value, LogToPhys}; //const ACTIVE_NODES: &'static[usize] = &[0x14000, 0x18000, 0x1c000, 0x20000, 0x28000, 0x2c000, 0x3c000, 0x40000]; -fn main() -> Result<(), std::io::Error> { +const EXTENT_TREE: u64 = 2; +const FS_TREE: u64 = 5; + +fn main() -> Result<(), IOError> { let file = File::open("../image")?; let image = unsafe { Mmap::map(&file)? }; + let addr = AddressMap::new(&image).map_err(|e|IOError::other(e))?; + let superblock = Superblock::parse(&image[0x10000..]).map_err(|e|IOError::other(e))?; + + let chunk_root_phys = addr.to_phys(superblock.chunk_root).unwrap() as usize; + let root_phys = addr.to_phys(superblock.root).unwrap() as usize; + + let root_tree_leaf = Leaf::parse(&image[root_phys .. root_phys + NODE_SIZE]).map_err(|e|IOError::other(e))?; + + let root_key = Key::new(EXTENT_TREE, ItemType::Root, 0); + let root = root_tree_leaf.find_key(root_key).unwrap(); + + let root_addr = if let Value::Root(root_item) = root.value { + addr.to_phys(root_item.bytenr) + } else { + None + }.unwrap() as usize; + + let tree_leaf = Leaf::parse(&image[root_addr .. root_addr + NODE_SIZE]).map_err(|e|IOError::other(e))?; + + println!("{:#?}", &tree_leaf); + // println!("{:#?}", Leaf::parse(&image[0x253c000..0x2540000])); - println!("{:#?}", Leaf::parse(&image[0x1d04000..0x1d08000])); Ok(()) }