From 530e3f507a9ad09156f5cfdf2f70aab0d5ac2d78 Mon Sep 17 00:00:00 2001 From: Ikey Doherty Date: Sun, 19 May 2024 02:53:54 +0100 Subject: [PATCH] superblock: Add LUKS2 decoder, expand UUID to be error-based Some bright spark decided to store a 40 byte char array as an encoded, NUL terminated UUID rather than, yknow, an actual 128-bit UUID with endian target (or u8*16) Signed-off-by: Ikey Doherty --- blsforme/src/superblock/btrfs.rs | 8 +-- blsforme/src/superblock/ext4.rs | 8 +-- blsforme/src/superblock/f2fs.rs | 8 +-- blsforme/src/superblock/luks2.rs | 100 +++++++++++++++++++++++++++++++ blsforme/src/superblock/mod.rs | 12 +++- blsforme/src/topology.rs | 2 +- 6 files changed, 124 insertions(+), 14 deletions(-) create mode 100644 blsforme/src/superblock/luks2.rs diff --git a/blsforme/src/superblock/btrfs.rs b/blsforme/src/superblock/btrfs.rs index 1d2ee7e..0a62ed1 100644 --- a/blsforme/src/superblock/btrfs.rs +++ b/blsforme/src/superblock/btrfs.rs @@ -52,15 +52,15 @@ pub fn from_reader(reader: &mut R) -> Result { if data.magic != MAGIC { Err(Error::InvalidMagic) } else { - log::trace!("valid magic field: UUID={}", data.uuid()); + log::trace!("valid magic field: UUID={}", data.uuid()?); Ok(data) } } impl Superblock for Btrfs { /// Return the encoded UUID for this superblock - fn uuid(&self) -> String { - Uuid::from_bytes(self.fsid).hyphenated().to_string() + fn uuid(&self) -> Result { + Ok(Uuid::from_bytes(self.fsid).hyphenated().to_string()) } fn kind(&self) -> Kind { @@ -84,6 +84,6 @@ mod tests { let mut fi = fs::File::open("../test/blocks/btrfs.img.zst").expect("cannot open ext4 img"); let mut stream = zstd::stream::Decoder::new(&mut fi).expect("Unable to decode stream"); let sb = from_reader(&mut stream).expect("Cannot parse superblock"); - assert_eq!(sb.uuid(), "829d6a03-96a5-4749-9ea2-dbb6e59368b2"); + assert_eq!(sb.uuid().unwrap(), "829d6a03-96a5-4749-9ea2-dbb6e59368b2"); } } diff --git a/blsforme/src/superblock/ext4.rs b/blsforme/src/superblock/ext4.rs index 8f125c3..b06a98b 100644 --- a/blsforme/src/superblock/ext4.rs +++ b/blsforme/src/superblock/ext4.rs @@ -119,7 +119,7 @@ pub fn from_reader(reader: &mut R) -> Result { } else { log::trace!( "valid magic field: UUID={} [volume label: \"{}\"]", - data.uuid(), + data.uuid()?, data.label().unwrap_or_else(|_| "[invalid utf8]".into()) ); Ok(data) @@ -128,8 +128,8 @@ pub fn from_reader(reader: &mut R) -> Result { impl super::Superblock for Ext4 { /// Return the encoded UUID for this superblock - fn uuid(&self) -> String { - Uuid::from_bytes(self.uuid).hyphenated().to_string() + fn uuid(&self) -> Result { + Ok(Uuid::from_bytes(self.uuid).hyphenated().to_string()) } /// Return the volume label as valid utf8 @@ -156,6 +156,6 @@ mod tests { let sb = from_reader(&mut stream).expect("Cannot parse superblock"); let label = sb.label().expect("Cannot determine volume name"); assert_eq!(label, "blsforme testing"); - assert_eq!(sb.uuid(), "731af94c-9990-4eed-944d-5d230dbe8a0d"); + assert_eq!(sb.uuid().unwrap(), "731af94c-9990-4eed-944d-5d230dbe8a0d"); } } diff --git a/blsforme/src/superblock/f2fs.rs b/blsforme/src/superblock/f2fs.rs index 9440d9f..5f77f9f 100644 --- a/blsforme/src/superblock/f2fs.rs +++ b/blsforme/src/superblock/f2fs.rs @@ -103,7 +103,7 @@ pub fn from_reader(reader: &mut R) -> Result { } else { log::trace!( "valid magic field: UUID={} [volume label: \"{}\"]", - data.uuid(), + data.uuid()?, data.label().unwrap_or_else(|_| "[invalid utf8]".into()) ); Ok(data) @@ -112,8 +112,8 @@ pub fn from_reader(reader: &mut R) -> Result { impl Superblock for F2FS { /// Return the encoded UUID for this superblock - fn uuid(&self) -> String { - Uuid::from_bytes(self.uuid).hyphenated().to_string() + fn uuid(&self) -> Result { + Ok(Uuid::from_bytes(self.uuid).hyphenated().to_string()) } /// Return the volume label as valid utf16 String @@ -142,6 +142,6 @@ mod tests { let sb = from_reader(&mut stream).expect("Cannot parse superblock"); let label = sb.label().expect("Cannot determine volume name"); assert_eq!(label, "blsforme testing"); - assert_eq!(sb.uuid(), "d2c85810-4e75-4274-bc7d-a78267af7443"); + assert_eq!(sb.uuid().unwrap(), "d2c85810-4e75-4274-bc7d-a78267af7443"); } } diff --git a/blsforme/src/superblock/luks2.rs b/blsforme/src/superblock/luks2.rs new file mode 100644 index 0000000..e7ff5e6 --- /dev/null +++ b/blsforme/src/superblock/luks2.rs @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: Copyright © 2024 Serpent OS Developers +// +// SPDX-License-Identifier: MPL-2.0 + +//! LUKS2 superblock support + +use std::slice; +use std::{io::Read, ptr}; + +use super::{Error, Superblock}; + +const MAGIC_LEN: usize = 6; +const LABEL_LEN: usize = 48; +const CHECKSUM_ALG_LEN: usize = 32; +const SALT_LEN: usize = 64; +const UUID_LEN: usize = 40; +const CHECKSUM_LEN: usize = 64; + +/// Per the `cryptsetup` docs for dm-crypt backed LUKS2, header is at first byte. +#[derive(Debug)] +#[repr(C, packed)] +pub struct Luks2 { + magic: [u8; MAGIC_LEN], + version: u16, + hdr_size: u64, + seqid: u64, + label: [u8; LABEL_LEN], + checksum_alg: [u8; CHECKSUM_ALG_LEN], + salt: [u8; SALT_LEN], + uuid: [u8; UUID_LEN], + subsystem: [u8; LABEL_LEN], + hdr_offset: u64, + padding: [u8; 184], + csum: [u8; CHECKSUM_LEN], + padding4096: [u8; 7 * 512], +} + +// Magic matchers: Guessing someone fudged endian encoding at some point. +const MAGIC1: [u8; MAGIC_LEN] = [b'L', b'U', b'K', b'S', 0xba, 0xbe]; +const MAGIC2: [u8; MAGIC_LEN] = [b'S', b'K', b'U', b'L', 0xba, 0xbe]; + +/// Attempt to decode the Superblock from the given read stream +pub fn from_reader(reader: &mut R) -> Result { + const SIZE: usize = std::mem::size_of::(); + let mut data: Luks2 = unsafe { std::mem::zeroed() }; + let data_sliced = unsafe { slice::from_raw_parts_mut(&mut data as *mut _ as *mut u8, SIZE) }; + reader.read_exact(data_sliced)?; + + let magic = unsafe { ptr::read_unaligned(ptr::addr_of!(data.magic)) }; + + match magic { + MAGIC1 | MAGIC2 => { + log::trace!( + "valid magic field: UUID={} [volume label: \"{}\"]", + data.uuid()?, + data.label().unwrap_or_else(|_| "[invalid utf8]".into()) + ); + Ok(data) + } + _ => Err(Error::InvalidMagic), + } +} + +impl Superblock for Luks2 { + fn kind(&self) -> super::Kind { + super::Kind::LUKS2 + } + + /// NOTE: LUKS2 stores string UUID rather than 128-bit sequence.. + fn uuid(&self) -> Result { + let uuid = unsafe { ptr::read_unaligned(ptr::addr_of!(self.uuid)) }; + Ok(std::str::from_utf8(&uuid)? + .trim_end_matches('\0') + .to_owned()) + } + + /// NOTE: Label is often empty, set in config instead... + fn label(&self) -> Result { + let label = unsafe { ptr::read_unaligned(ptr::addr_of!(self.label)) }; + Ok(std::str::from_utf8(&label)? + .trim_end_matches('\0') + .to_owned()) + } +} + +#[cfg(test)] +mod tests { + + use crate::superblock::{luks2::from_reader, Superblock}; + use std::fs; + + #[test] + fn test_basic() { + let mut fi = + fs::File::open("../test/blocks/luks+ext4.img.zst").expect("cannot open luks2 img"); + let mut stream = zstd::stream::Decoder::new(&mut fi).expect("Unable to decode stream"); + let sb = from_reader(&mut stream).expect("Cannot parse superblock"); + assert_eq!(sb.uuid().unwrap(), "be373cae-2bd1-4ad5-953f-3463b2e53e59"); + } +} diff --git a/blsforme/src/superblock/mod.rs b/blsforme/src/superblock/mod.rs index fe8fb97..5248ccd 100644 --- a/blsforme/src/superblock/mod.rs +++ b/blsforme/src/superblock/mod.rs @@ -11,9 +11,12 @@ use thiserror::Error; pub mod btrfs; pub mod ext4; pub mod f2fs; +pub mod luks2; + pub enum Kind { Btrfs, Ext4, + LUKS2, F2FS, } @@ -22,6 +25,7 @@ impl std::fmt::Display for Kind { match &self { Kind::Btrfs => f.write_str("btrfs"), Kind::Ext4 => f.write_str("ext4"), + Kind::LUKS2 => f.write_str("luks2"), Kind::F2FS => f.write_str("f2fs"), } } @@ -32,7 +36,7 @@ pub trait Superblock: std::fmt::Debug + Sync + Send { fn kind(&self) -> self::Kind; /// Get the filesystem UUID - fn uuid(&self) -> String; + fn uuid(&self) -> Result; /// Get the volume label fn label(&self) -> Result; @@ -81,6 +85,12 @@ pub fn for_reader(reader: &mut R) -> Result, return Ok(Box::new(block)); } + // try luks2 + reader.rewind()?; + if let Ok(block) = luks2::from_reader(reader) { + return Ok(Box::new(block)); + } + Err(Error::UnknownSuperblock) } diff --git a/blsforme/src/topology.rs b/blsforme/src/topology.rs index a3387db..82af888 100644 --- a/blsforme/src/topology.rs +++ b/blsforme/src/topology.rs @@ -318,6 +318,6 @@ impl Topology { let sb = superblock::for_reader(&mut cursor)?; log::trace!("detected superblock: {}", sb.kind()); - Ok(FilesystemID::UUID(sb.uuid())) + Ok(FilesystemID::UUID(sb.uuid()?)) } }