Skip to content

Commit

Permalink
superblock: Add LUKS2 decoder, expand UUID to be error-based
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
ikeycode committed May 19, 2024
1 parent a7e8611 commit 530e3f5
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 14 deletions.
8 changes: 4 additions & 4 deletions blsforme/src/superblock/btrfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ pub fn from_reader<R: Read>(reader: &mut R) -> Result<Btrfs, Error> {
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<String, Error> {
Ok(Uuid::from_bytes(self.fsid).hyphenated().to_string())
}

fn kind(&self) -> Kind {
Expand All @@ -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");
}
}
8 changes: 4 additions & 4 deletions blsforme/src/superblock/ext4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ pub fn from_reader<R: Read>(reader: &mut R) -> Result<Ext4, Error> {
} else {
log::trace!(
"valid magic field: UUID={} [volume label: \"{}\"]",
data.uuid(),
data.uuid()?,
data.label().unwrap_or_else(|_| "[invalid utf8]".into())
);
Ok(data)
Expand All @@ -128,8 +128,8 @@ pub fn from_reader<R: Read>(reader: &mut R) -> Result<Ext4, Error> {

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<String, Error> {
Ok(Uuid::from_bytes(self.uuid).hyphenated().to_string())
}

/// Return the volume label as valid utf8
Expand All @@ -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");
}
}
8 changes: 4 additions & 4 deletions blsforme/src/superblock/f2fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ pub fn from_reader<R: Read>(reader: &mut R) -> Result<F2FS, Error> {
} else {
log::trace!(
"valid magic field: UUID={} [volume label: \"{}\"]",
data.uuid(),
data.uuid()?,
data.label().unwrap_or_else(|_| "[invalid utf8]".into())
);
Ok(data)
Expand All @@ -112,8 +112,8 @@ pub fn from_reader<R: Read>(reader: &mut R) -> Result<F2FS, Error> {

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<String, Error> {
Ok(Uuid::from_bytes(self.uuid).hyphenated().to_string())
}

/// Return the volume label as valid utf16 String
Expand Down Expand Up @@ -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");
}
}
100 changes: 100 additions & 0 deletions blsforme/src/superblock/luks2.rs
Original file line number Diff line number Diff line change
@@ -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<R: Read>(reader: &mut R) -> Result<Luks2, Error> {
const SIZE: usize = std::mem::size_of::<Luks2>();
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<String, super::Error> {
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<String, super::Error> {
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");
}
}
12 changes: 11 additions & 1 deletion blsforme/src/superblock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand All @@ -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"),
}
}
Expand All @@ -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<String, self::Error>;

/// Get the volume label
fn label(&self) -> Result<String, self::Error>;
Expand Down Expand Up @@ -81,6 +85,12 @@ pub fn for_reader<R: Read + Seek>(reader: &mut R) -> Result<Box<dyn Superblock>,
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)
}

Expand Down
2 changes: 1 addition & 1 deletion blsforme/src/topology.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?))
}
}

0 comments on commit 530e3f5

Please sign in to comment.