Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions cryptpilot-core/src/fs/block/dummy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ impl DummyDevice {
}

async fn setup(device_size: u64, block_size: u64, on_tmpfs: bool) -> Result<Self> {
// Load loop module if not available
crate::fs::kernel_module::ensure_module_loaded("loop", &[]).await;

let mut sparse_file = tempfile::Builder::new()
.prefix("cryptpilot-")
.suffix(".img")
Expand All @@ -57,8 +60,7 @@ impl DummyDevice {
sparse_file.seek(std::io::SeekFrom::Start(device_size - 1))?;
sparse_file.write_all(&[0])?;

let lc = LoopControl::open()
.context("Failed to open loop control, maybe forgot to run 'sudo modprobe loop'?")?;
let lc = LoopControl::open().context("Failed to open loop control")?;
// Retry to avoid conflicts and waiting for avaliable loop device
let ld = RetryPolicy::exponential(Duration::from_millis(1))
.with_max_retries(200)
Expand Down
101 changes: 101 additions & 0 deletions cryptpilot-core/src/fs/kernel_module.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use std::path::Path;
use tokio::process::Command;

use crate::fs::cmd::CheckCommandOutput;

/// Check if a kernel module is loaded (either built-in or as a loadable module)
///
/// Returns true if:
/// 1. The module exists in /sys/module/<name> (already loaded as module)
/// 2. The module is built-in (compiled into kernel)
///
/// For built-in modules, /sys/module/<name> will exist even though they
/// were not loaded via modprobe.
pub fn is_module_available(name: &str) -> bool {
// Check if module is already loaded or built-in
// Convert hyphens to underscores module name
// (e.g., "dm-mod" -> "dm_mod")
Path::new(&format!("/sys/module/{}", name.replace('-', "_"))).exists()
}

/// Load a kernel module if not already available
///
/// This function first checks if the module is already loaded or built-in.
/// If not, it attempts to load it using modprobe.
///
/// # Arguments
/// * `name` - Module name
/// * `args` - Optional module parameters (e.g., ["max_part=8"])
///
/// # Examples
///
/// ```ignore
/// // Load module without parameters
/// ensure_module_loaded("dm-verity", &[]).await;
///
/// // Load module with parameters
/// ensure_module_loaded("nbd", &["max_part=8"]).await;
/// ```
pub async fn ensure_module_loaded(name: &str, args: &[&str]) {
if is_module_available(name) {
tracing::debug!("Kernel module '{}' is already available", name);
return;
}

// Check if modprobe is available
if !Path::new("/sbin/modprobe").exists() && !Path::new("/usr/sbin/modprobe").exists() {
tracing::warn!(
"modprobe not found, skipping module load for '{}' (module may be built-in)",
name
);
return;
}

tracing::info!("Loading kernel module '{}' with args: {:?}", name, args);

let mut cmd = Command::new("modprobe");
cmd.arg(name);
cmd.args(args);

if let Err(e) = cmd.run().await {
// Check if module is actually available now (might be built-in)
if is_module_available(name) {
tracing::debug!(
"Module '{}' appears to be available after modprobe attempt",
name
);
return;
}

tracing::warn!(module = name, error = %e, "Failed to load kernel module");
return;
};

// Verify module is now available
if is_module_available(name) {
tracing::debug!("Successfully loaded kernel module '{}'", name);
} else {
tracing::warn!(
"Module '{}' was loaded by modprobe but not found in /sys/module/ maybe it is built-in module",
name
)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_is_module_available_builtin() {
// These modules are typically built-in
// The test may vary depending on kernel configuration
let _ = is_module_available("kernel"); // Should exist
}

#[test]
fn test_is_module_available_nonexistent() {
// This module should not exist
assert!(!is_module_available("nonexistent_module_xyz123"));
}
}
6 changes: 6 additions & 0 deletions cryptpilot-core/src/fs/luks2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,12 @@ pub async fn open_with_check_passphrase(
passphrase: &Passphrase,
integrity: IntegrityType,
) -> Result<(), anyhow::Error> {
crate::fs::kernel_module::ensure_module_loaded("dm_crypt", &[]).await;

if !matches!(integrity, IntegrityType::None) {
crate::fs::kernel_module::ensure_module_loaded("dm-integrity", &[]).await;
}

let passphrase = passphrase.to_owned();
let verbose = get_verbose().await;

Expand Down
1 change: 1 addition & 0 deletions cryptpilot-core/src/fs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod block;
pub mod cmd;
pub mod kernel_module;
pub mod luks2;
pub mod mkfs;
pub mod mount;
Expand Down
12 changes: 3 additions & 9 deletions cryptpilot-core/src/fs/nbd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,13 @@ impl NbdDevice {
Path::new("/dev/nbd0").exists()
}

pub async fn modprobe() -> Result<()> {
Command::new("modprobe")
.args(["nbd", "max_part=8"])
.run()
.await
.context("Failed to load kernel module 'nbd'")?;

Ok(())
pub async fn modprobe() {
super::kernel_module::ensure_module_loaded("nbd", &["max_part=8"]).await;
}

pub async fn get_avaliable() -> Result<NbdDeviceNumber> {
if !Self::is_module_loaded() {
Self::modprobe().await?;
Self::modprobe().await;
}

for i in 0..=99 {
Expand Down
45 changes: 45 additions & 0 deletions cryptpilot-fde/docs/boot.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,48 @@ No additional operations are required at this stage; all write operations are ha
## 5. System Switch Stage

After `cryptpilot-fde-after-sysroot` completes, the initrd stage ends. dracut performs cleanup work and hands over control to systemd. systemd switches `/sysroot` as the real root filesystem, and the system enters the normal System Manager stage. At this point, the running root filesystem may be either the overlayfs unified view (overlayfs mechanism) or the dm-snapshot device (dm-snapshot mechanism), protected by dm-verity integrity while supporting normal write operations.

## 6. Statically Linked Kernel Configuration

CryptPilot supports statically linked kernels (all kernel modules compiled into the kernel). If your kernel uses static linking, ensure the following kernel modules are built-in:

### 6.1 Required Kernel Modules

| Module Name | Purpose | Config Option |
|-------------|---------|---------------|
| **dm_mod** | Device Mapper core | `CONFIG_BLK_DEV_DM=y` |
| **dm_linear** | Linear device mapping | Builtin |
| **dm_verity** | Integrity verification | `CONFIG_DM_VERITY=y` |
| **dm_snapshot** | Snapshot support (dm-snapshot backend) | `CONFIG_DM_SNAPSHOT=y` |
| **dm_zero** | Zero target device (dm-snapshot backend) | `CONFIG_DM_ZERO=y` |
| **dm_crypt** | LUKS/dm-crypt encryption | `CONFIG_DM_CRYPT=y` |
| **dm_integrity** | dm-integrity protection | `CONFIG_BLK_DEV_INTEGRITY=y` |
| **overlay** | OverlayFS filesystem (overlayfs backend) | `CONFIG_OVERLAY_FS=y` |
| **zram** | Compressed RAM block device (ram mode) | `CONFIG_ZRAM=y` |

### 6.2 Optional Kernel Modules

| Module Name | Purpose | Config Option |
|-------------|---------|---------------|
| **loop** | Loop device (testing/debugging) | `CONFIG_BLK_DEV_LOOP=y` |
| **nbd** | Network block device (external disk) | `CONFIG_BLK_DEV_NBD=y` |

### 6.3 Configuration Example

Enable static linking in your `.config` file:

```
# Device Mapper support
CONFIG_BLK_DEV_DM=y
CONFIG_DM_VERITY=y
CONFIG_DM_SNAPSHOT=y
CONFIG_DM_ZERO=y

# Filesystem support
CONFIG_OVERLAY_FS=y

# Block device support
CONFIG_ZRAM=y
CONFIG_BLK_DEV_LOOP=y
CONFIG_BLK_DEV_NBD=y
```
45 changes: 45 additions & 0 deletions cryptpilot-fde/docs/boot_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,4 +242,49 @@ overlayfs挂载将lowerdir、upperdir和workdir组合,将联合视图挂载到

`cryptpilot-fde-after-sysroot`完成后,initrd阶段结束。dracut执行清理工作,将控制权交给systemd。systemd将`/sysroot`作为真实根文件系统切换,系统进入正常的System Manager阶段。此时运行的根文件系统根据配置可能是overlayfs联合视图(overlayfs机制)或dm-snapshot设备(dm-snapshot机制),既受dm-verity完整性保护又支持正常写入操作。

## 静态链接内核配置

CryptPilot支持静态链接内核(所有内核模块编译进内核)。如果您的内核采用静态链接方式,需要确保以下内核模块已内联到内核中:

### 6.1 必需的内核模块

| 模块名 | 用途 | 配置选项 |
|--------|------|----------|
| **dm_mod** | Device Mapper 核心 | `CONFIG_BLK_DEV_DM=y` |
| **dm_linear** | 线性设备映射 | 内建 |
| **dm_verity** | 完整性验证 | `CONFIG_DM_VERITY=y` |
| **dm_snapshot** | 快照支持(dm-snapshot后端) | `CONFIG_DM_SNAPSHOT=y` |
| **dm_zero** | Zero 目标设备(dm-snapshot后端) | `CONFIG_DM_ZERO=y` |
| **dm_crypt** | LUKS/dm-crypt 加密 | `CONFIG_DM_CRYPT=y` |
| **dm_integrity** | dm-integrity 完整性保护 | `CONFIG_BLK_DEV_INTEGRITY=y` |
| **overlay** | OverlayFS 文件系统(overlayfs后端) | `CONFIG_OVERLAY_FS=y` |
| **zram** | 压缩 RAM 块设备(ram模式) | `CONFIG_ZRAM=y` |
| **loop** | 回环设备 | `CONFIG_BLK_DEV_LOOP=y` |

### 6.2 可选的内核模块

| 模块名 | 用途 | 配置选项 |
|--------|------|----------|
| **nbd** | 网络块设备(外部磁盘使用) | `CONFIG_BLK_DEV_NBD=y` |

### 6.3 配置示例

在 `.config` 文件中启用静态链接:

```
# Device Mapper 支持
CONFIG_BLK_DEV_DM=y
CONFIG_DM_VERITY=y
CONFIG_DM_CRYPT=y
CONFIG_BLK_DEV_INTEGRITY=y
CONFIG_DM_SNAPSHOT=y
CONFIG_DM_ZERO=y

# 文件系统支持
CONFIG_OVERLAY_FS=y

# 块设备支持
CONFIG_ZRAM=y
CONFIG_BLK_DEV_LOOP=y
CONFIG_BLK_DEV_NBD=y
```
4 changes: 2 additions & 2 deletions cryptpilot-fde/docs/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,13 @@ cryptpilot-convert --in ./aliyun_3_x64_20G_nocloud_alibase_20251030.qcow2 \
--out ./uki-encrypted.qcow2 \
-c ./config_dir/ \
--rootfs-no-encryption \
--uki-mode
--uki
```

**Parameter Explanation:**

- `--rootfs-no-encryption`: rootfs measure-only without encryption
- `--uki-mode`: Generate UKI unified kernel image
- `--uki`: Generate UKI unified kernel image

### Calculate Reference Values

Expand Down
4 changes: 2 additions & 2 deletions cryptpilot-fde/docs/quick-start_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,13 @@ cryptpilot-convert --in ./aliyun_3_x64_20G_nocloud_alibase_20251030.qcow2 \
--out ./uki-encrypted.qcow2 \
-c ./config_dir/ \
--rootfs-no-encryption \
--uki-mode
--uki
```

**参数说明:**

- `--rootfs-no-encryption`:rootfs 仅度量不加密
- `--uki-mode`:生成 UKI 统一内核镜像
- `--uki`:生成 UKI 统一内核镜像

### 计算参考值

Expand Down
8 changes: 2 additions & 6 deletions cryptpilot-fde/src/cmd/boot_service/stage/after_sysroot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,8 @@ async fn setup_overlayfs_mounts(fde_config: crate::config::FdeConfig) -> Result<
.delta_location
.unwrap_or(DeltaLocation::Disk);

// Load overlay module if not loaded
Command::new("modprobe")
.arg("overlay")
.run()
.await
.context("Failed to load kernel module 'overlay'")?;
// Load overlay module if not available
cryptpilot::fs::kernel_module::ensure_module_loaded("overlay", &[]).await;

let overlay_dir = match delta_location {
DeltaLocation::Ram => {
Expand Down
26 changes: 8 additions & 18 deletions cryptpilot-fde/src/cmd/boot_service/stage/before_sysroot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ pub async fn setup_volumes_required_by_fde() -> Result<()> {

tracing::info!("Setting up volumes required by FDE");

// Load required kernel modules for LVM and device mapper
cryptpilot::fs::kernel_module::ensure_module_loaded("dm_mod", &[]).await;

// 1. Checking and activating LVM volume group
tracing::info!(
volume_group_name = VOLUME_GROUP_NAME,
Expand Down Expand Up @@ -245,11 +248,7 @@ async fn setup_rootfs_dm_verity(
lower_dm_device: &Path,
) -> Result<()> {
async {
Command::new("modprobe")
.arg("dm-verity")
.run()
.await
.context("Failed to load kernel module 'dm-verity'")?;
cryptpilot::fs::kernel_module::ensure_module_loaded("dm-verity", &[]).await;

Command::new("veritysetup")
.arg("open")
Expand Down Expand Up @@ -400,14 +399,8 @@ async fn setup_delta_volume_luks2(
}

async fn create_zram_cow_device() -> Result<PathBuf> {
// Load zram module
if !Path::new("/sys/class/zram-control").exists() {
Command::new("modprobe")
.arg("zram")
.run()
.await
.context("Failed to load zram module")?;
}
// Load zram module if not available
cryptpilot::fs::kernel_module::ensure_module_loaded("zram", &[]).await;

// Get total memory in KB
let mem_info = tokio::fs::read_to_string("/proc/meminfo").await?;
Expand Down Expand Up @@ -448,11 +441,8 @@ async fn setup_dm_snapshot_device_chain(
);

// Load required kernel modules
Command::new("modprobe")
.arg("dm-snapshot")
.run()
.await
.context("Failed to load dm-snapshot module")?;
cryptpilot::fs::kernel_module::ensure_module_loaded("dm-snapshot", &[]).await;
cryptpilot::fs::kernel_module::ensure_module_loaded("dm-zero", &[]).await;

// Get device sizes (in sectors, 512 bytes each)
let verity_size = get_device_size_bytes(rootfs_device).await? / 512;
Expand Down
Loading