From e4c90e84dc33a6686a2f15ac74c6db7782a9f20b Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 1 Feb 2026 01:47:24 +0800 Subject: [PATCH] feat(luks2): implement atomic initialization check with subsystem - Add --subsystem cryptpilot option to cryptsetup luksFormat in cryptpilot-convert.sh - Implement direct binary LUKS2 header parsing to read subsystem field - Add mark_volume_as_initialized function to set subsystem after successful initialization - Modify is_initialized function to check for 'cryptpilot' subsystem - Call mark_volume_as_initialized after mkfs in init, open, and before_sysroot stages - Ensure atomicity: only fully initialized volumes (with subsystem set) are recognized as initialized This provides atomic initialization semantics: a volume is considered initialized only after both LUKS2 creation and mkfs are completed. Signed-off-by: Kun Lai --- cryptpilot-convert.sh | 2 +- cryptpilot-core/src/fs/luks2.rs | 121 +++++++++++++++++- cryptpilot-crypt/src/cmd/init.rs | 4 + cryptpilot-crypt/src/cmd/open.rs | 5 + .../cmd/boot_service/stage/before_sysroot.rs | 6 + 5 files changed, 133 insertions(+), 5 deletions(-) diff --git a/cryptpilot-convert.sh b/cryptpilot-convert.sh index aab738a..8d3afa9 100755 --- a/cryptpilot-convert.sh +++ b/cryptpilot-convert.sh @@ -1125,7 +1125,7 @@ step::setup_rootfs_lv_with_encrypt() { proc::exec_subshell_flose_fds lvcreate -n rootfs --size ${rootfs_lv_size_in_bytes}B system # Note that the real size will be a little bit larger than the specified size, since they will be aligned to the Physical Extentsize (PE) size, which by default is 4MB. # Create a encrypted volume log::info "Encrypting rootfs logical volume with LUKS2" - echo -n "${rootfs_passphrase}" | cryptsetup luksFormat --type luks2 --cipher aes-xts-plain64 /dev/mapper/system-rootfs --key-file=- + echo -n "${rootfs_passphrase}" | cryptsetup luksFormat --type luks2 --cipher aes-xts-plain64 --subsystem cryptpilot /dev/mapper/system-rootfs --key-file=- proc::hook_exit "[[ -e /dev/mapper/rootfs ]] && disk::dm_remove_wait_busy rootfs" log::info "Opening encrypted rootfs volume" diff --git a/cryptpilot-core/src/fs/luks2.rs b/cryptpilot-core/src/fs/luks2.rs index 593205e..fa26261 100644 --- a/cryptpilot-core/src/fs/luks2.rs +++ b/cryptpilot-core/src/fs/luks2.rs @@ -10,6 +10,7 @@ use libcryptsetup_rs::{ }; use rand::{distributions::Alphanumeric, Rng as _}; use tokio::fs::OpenOptions; +use tokio::io::AsyncReadExt; use crate::types::{IntegrityType, Passphrase}; @@ -18,6 +19,67 @@ use super::get_verbose; const LUKS2_VOLUME_KEY_SIZE_BIT_WITH_INTEGRITY: usize = 768; const LUKS2_VOLUME_KEY_SIZE_BIT_WITHOUT_INTEGRITY: usize = 512; const LUKS2_SECTOR_SIZE: u32 = 4096; +const LUKS2_SUBSYSTEM_NAME: &str = "cryptpilot"; + +async fn get_luks2_subsystem(dev: &str) -> Result> { + /// LUKS2 header structure according to the specification + /// Reference: https://gitlab.com/cryptsetup/cryptsetup/-/blob/24d10f412e2ca1b0a8ed5addb1381507662a9862/lib/luks2/luks2.h + #[repr(C, packed)] + #[derive(Debug, Clone)] + struct Luks2HdrDisk { + magic: [u8; 6], + version: u16, // big endian + hdr_size: u64, + seqid: u64, + label: [u8; 48], + checksum_alg: [u8; 32], + salt: [u8; 64], + uuid: [u8; 40], + subsystem: [u8; 48], + hdr_offset: u64, + padding: [u8; 184], + csum: [u8; 64], + padding4096: [u8; 7 * 512], + } + + let device_path = PathBuf::from(&dev); + let mut file = tokio::fs::File::open(&device_path).await?; + + let mut header_buf = vec![0u8; std::mem::size_of::()]; + file.read_exact(&mut header_buf).await?; + if header_buf.len() < std::mem::size_of::() { + return Err(anyhow::anyhow!( + "Header buffer size is too small: {} bytes, expected at least {} bytes", + header_buf.len(), + std::mem::size_of::() + )); + } + + let header: &Luks2HdrDisk = unsafe { &*(header_buf.as_ptr() as *const Luks2HdrDisk) }; + + let magic_bytes = &header.magic; + if !(magic_bytes == b"LUKS\xba\xbe" || magic_bytes == b"SKUL\xba\xbe") + || u16::from_be(header.version) != 2 + { + return Err(anyhow::anyhow!( + "Invalid LUKS2 header: magic='{:?}', version={}", + magic_bytes, + u16::from_be(header.version) + )); + } + + let subsystem_str = match header.subsystem.iter().position(|&x| x == 0) { + Some(pos) => String::from_utf8_lossy(&header.subsystem[..pos]).to_string(), + None => String::from_utf8_lossy(&header.subsystem).to_string(), + }; + + if !subsystem_str.is_empty() && subsystem_str != "-" { + tracing::debug!("Found LUKS2 subsystem in binary header: {}", subsystem_str); + Ok(Some(subsystem_str)) + } else { + Ok(None) + } +} pub async fn format(dev: &str, passphrase: &Passphrase, integrity: IntegrityType) -> Result<()> { let passphrase = passphrase.to_owned(); @@ -80,6 +142,43 @@ pub async fn format(dev: &str, passphrase: &Passphrase, integrity: IntegrityType Ok(()) } +pub async fn mark_volume_as_initialized(dev: &Path) -> Result<()> { + let verbose = get_verbose().await; + let dev_path = dev.to_path_buf(); + let dev_path_for_error = dev_path.clone(); + + tokio::task::spawn_blocking(move || { + if verbose { + libcryptsetup_rs::set_debug_level(CryptDebugLevel::All); + } else { + libcryptsetup_rs::set_debug_level(CryptDebugLevel::None); + } + + let mut device = CryptInit::init(&dev_path)?; + + // Load the device + device + .context_handle() + .load::<()>(Some(EncryptionFormat::Luks2), None)?; + + // Mark the volume as initialized by setting the subsystem to "cryptpilot" + device + .context_handle() + .set_label(None, Some(LUKS2_SUBSYSTEM_NAME))?; + + Ok::<_, anyhow::Error>(()) + }) + .await? + .with_context(|| { + format!( + "Failed to mark volume as initialized for {:?}", + dev_path_for_error + ) + })?; + + Ok(()) +} + pub async fn check_passphrase(dev: &str, passphrase: &Passphrase) -> Result<(), anyhow::Error> { let passphrase = passphrase.to_owned(); let verbose = get_verbose().await; @@ -159,14 +258,15 @@ pub async fn open_with_check_passphrase( } pub async fn is_initialized(dev: &str) -> Result { - is_a_luks2_volume(dev).await + is_a_cryptpilot_initialized_luks2_volume(dev).await } -async fn is_a_luks2_volume(dev: &str) -> Result { +async fn is_a_cryptpilot_initialized_luks2_volume(dev: &str) -> Result { let verbose = get_verbose().await; let device_path = PathBuf::from(&dev); - tokio::task::spawn_blocking(move || { + // First check if it's a LUKS2 volume using the blocking operation + let is_luks2 = tokio::task::spawn_blocking(move || { if verbose { libcryptsetup_rs::set_debug_level(CryptDebugLevel::All); } else { @@ -183,7 +283,20 @@ async fn is_a_luks2_volume(dev: &str) -> Result { Ok::<_, anyhow::Error>(is_luks2) }) .await? - .with_context(|| format!("Failed to check luks2 initialization status of device {dev}")) + .with_context(|| format!("Failed to check luks2 initialization status of device {dev}"))?; + + if !is_luks2 { + return Ok(false); + } + + // Check if the subsystem is set to "cryptpilot" + let subsystem_is_set = get_luks2_subsystem(dev) + .await + .with_context(|| format!("Failed to get LUKS2 device subsystem for {dev}"))? + .map(|subsystem| subsystem == LUKS2_SUBSYSTEM_NAME) + .unwrap_or(false); + + Ok(subsystem_is_set) } pub fn is_active(volume: &str) -> bool { diff --git a/cryptpilot-crypt/src/cmd/init.rs b/cryptpilot-crypt/src/cmd/init.rs index ac2b87c..96b25b8 100644 --- a/cryptpilot-crypt/src/cmd/init.rs +++ b/cryptpilot-crypt/src/cmd/init.rs @@ -104,5 +104,9 @@ async fn persistent_disk_init( cryptpilot::fs::mkfs::makefs_if_empty(&tmp_volume.volume_path(), makefs, integrity).await?; } + // Mark the volume as fully initialized + cryptpilot::fs::luks2::mark_volume_as_initialized(std::path::Path::new(&volume_config.dev)) + .await?; + Ok(()) } diff --git a/cryptpilot-crypt/src/cmd/open.rs b/cryptpilot-crypt/src/cmd/open.rs index ae76151..ace47d6 100644 --- a/cryptpilot-crypt/src/cmd/open.rs +++ b/cryptpilot-crypt/src/cmd/open.rs @@ -109,6 +109,11 @@ async fn temporary_disk_open( } }; } + + // Mark the volume as fully initialized + cryptpilot::fs::luks2::mark_volume_as_initialized(std::path::Path::new(&volume_config.dev)) + .await?; + Ok(()) } diff --git a/cryptpilot-fde/src/cmd/boot_service/stage/before_sysroot.rs b/cryptpilot-fde/src/cmd/boot_service/stage/before_sysroot.rs index f4f8aa7..4b451a6 100644 --- a/cryptpilot-fde/src/cmd/boot_service/stage/before_sysroot.rs +++ b/cryptpilot-fde/src/cmd/boot_service/stage/before_sysroot.rs @@ -178,6 +178,12 @@ pub async fn setup_volumes_required_by_fde() -> Result<()> { ) .await?; } + + // Mark the volume as fully initialized + cryptpilot::fs::luks2::mark_volume_as_initialized(std::path::Path::new( + DATA_LOGICAL_VOLUME, + )) + .await?; } tracing::info!("Both rootfs volume and data volume are ready");