Skip to content

Commit b8c8215

Browse files
composefs: Disable auto fsverity enforcement on unsupported fs
On filesystems that do not support fsverity, we now do not auto enforce fsverity. On filesystems that do support it, we look at the `--allow-missing-verity` option, if passed in by the user, and if true, fs-verity enforcement is disabled and vice-versa. In case of UKIs, we take a different approach of looking at the UKI cmdline beforehand and checking if the `composefs=` parameter has `?` or not. This approach, though valid, fails in a few cases, viz, - The cmdline is in an UKI addon - The target image is not the one we're currently running in Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
1 parent 1481c9b commit b8c8215

4 files changed

Lines changed: 140 additions & 21 deletions

File tree

crates/lib/src/install.rs

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,14 +193,15 @@ use crate::containerenv::ContainerExecutionInfo;
193193
use crate::deploy::{
194194
MergeState, PreparedImportMeta, PreparedPullResult, prepare_for_pull, pull_from_prepared,
195195
};
196+
use crate::install::config::Filesystem as FilesystemEnum;
196197
use crate::lsm;
197198
use crate::progress_jsonl::ProgressWriter;
198199
use crate::spec::{Bootloader, ImageReference};
199200
use crate::store::Storage;
200201
use crate::task::Task;
201202
use crate::utils::sigpolicy_from_opt;
202203
use bootc_kernel_cmdline::{INITRD_ARG_PREFIX, ROOTFLAGS, bytes, utf8};
203-
use bootc_mount::Filesystem;
204+
use bootc_mount::{Filesystem, inspect_filesystem};
204205
use composefs::fsverity::FsVerityHashValue;
205206

206207
/// The toplevel boot directory
@@ -1507,6 +1508,7 @@ async fn prepare_install(
15071508
source_opts: InstallSourceOpts,
15081509
target_opts: InstallTargetOpts,
15091510
mut composefs_options: InstallComposefsOpts,
1511+
target_fs: Option<FilesystemEnum>,
15101512
) -> Result<Arc<State>> {
15111513
tracing::trace!("Preparing install");
15121514
let rootfs = cap_std::fs::Dir::open_ambient_dir("/", cap_std::ambient_authority())
@@ -1576,12 +1578,15 @@ async fn prepare_install(
15761578
};
15771579
tracing::debug!("Target image reference: {target_imgref}");
15781580

1579-
let composefs_required = if let Some(root) = target_rootfs.as_ref() {
1580-
crate::kernel::find_kernel(root)?
1581-
.map(|k| k.kernel.unified)
1582-
.unwrap_or(false)
1581+
let (composefs_required, kernel) = if let Some(root) = target_rootfs.as_ref() {
1582+
let kernel = crate::kernel::find_kernel(root)?;
1583+
1584+
(
1585+
kernel.as_ref().map(|k| k.kernel.unified).unwrap_or(false),
1586+
kernel,
1587+
)
15831588
} else {
1584-
false
1589+
(false, None)
15851590
};
15861591

15871592
tracing::debug!("Composefs required: {composefs_required}");
@@ -1656,6 +1661,59 @@ async fn prepare_install(
16561661
tracing::debug!("No install configuration found");
16571662
}
16581663

1664+
let root_filesystem = target_fs
1665+
.or(install_config
1666+
.as_ref()
1667+
.and_then(|c| c.filesystem_root())
1668+
.and_then(|r| r.fstype))
1669+
.ok_or_else(|| anyhow::anyhow!("No root filesystem specified"))?;
1670+
1671+
let mut is_uki = false;
1672+
1673+
// For composefs backend, automatically disable fs-verity hard requirement if the
1674+
// filesystem doesn't support it
1675+
//
1676+
// If we have a sealed UKI on our hands, then we can assume that user wanted fs-verity so
1677+
// we hard require it in that particular case
1678+
//
1679+
// NOTE: This isn't really 100% accurate 100% of the time as the cmdline can be in an addon
1680+
match kernel {
1681+
Some(k) => match k.k_type {
1682+
crate::kernel::KernelType::Uki {
1683+
allow_missing_fsverity,
1684+
..
1685+
} => {
1686+
if !allow_missing_fsverity {
1687+
anyhow::ensure!(
1688+
root_filesystem.supports_fsverity(),
1689+
"Specified filesystem {root_filesystem} does not support fs-verity"
1690+
);
1691+
}
1692+
1693+
composefs_options.allow_missing_verity = allow_missing_fsverity;
1694+
is_uki = true;
1695+
}
1696+
1697+
crate::kernel::KernelType::Vmlinuz { .. } => {}
1698+
},
1699+
1700+
None => {}
1701+
}
1702+
1703+
// If `--allow-missing-verity` is already passed via CLI, don't modify
1704+
if composefs_options.composefs_backend && !composefs_options.allow_missing_verity && !is_uki {
1705+
composefs_options.allow_missing_verity = !root_filesystem.supports_fsverity();
1706+
1707+
tracing::debug!(
1708+
"Missing fsverity {}",
1709+
if composefs_options.allow_missing_verity {
1710+
"allowed"
1711+
} else {
1712+
"not allowed"
1713+
}
1714+
);
1715+
}
1716+
16591717
if let Some(crate::spec::Bootloader::None) = config_opts.bootloader {
16601718
if cfg!(target_arch = "s390x") {
16611719
anyhow::bail!("Bootloader set to none is not supported for the s390x architecture");
@@ -1994,6 +2052,7 @@ pub(crate) async fn install_to_disk(mut opts: InstallToDiskOpts) -> Result<()> {
19942052
opts.source_opts,
19952053
opts.target_opts,
19962054
opts.composefs_opts,
2055+
block_opts.filesystem,
19972056
)
19982057
.await?;
19992058

@@ -2294,6 +2353,8 @@ pub(crate) async fn install_to_filesystem(
22942353
target_path
22952354
);
22962355

2356+
let fs_inspect = inspect_filesystem(&opts.filesystem_opts.root_path)?;
2357+
22972358
// Gather global state, destructuring the provided options.
22982359
// IMPORTANT: We might re-execute the current process in this function (for SELinux among other things)
22992360
// IMPORTANT: and hence anything that is done before MUST BE IDEMPOTENT.
@@ -2304,6 +2365,7 @@ pub(crate) async fn install_to_filesystem(
23042365
opts.source_opts,
23052366
opts.target_opts,
23062367
opts.composefs_opts,
2368+
Some(fs_inspect.fstype.as_str().try_into()?),
23072369
)
23082370
.await?;
23092371

crates/lib/src/install/config.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,25 @@ impl std::fmt::Display for Filesystem {
3232
}
3333
}
3434

35+
impl TryFrom<&str> for Filesystem {
36+
type Error = anyhow::Error;
37+
38+
fn try_from(value: &str) -> Result<Self, Self::Error> {
39+
match value {
40+
"xfs" => Ok(Self::Xfs),
41+
"ext4" => Ok(Self::Ext4),
42+
"btrfs" => Ok(Self::Btrfs),
43+
other => anyhow::bail!("Unknown filesystem: {}", other),
44+
}
45+
}
46+
}
47+
48+
impl Filesystem {
49+
pub(crate) fn supports_fsverity(&self) -> bool {
50+
matches!(self, Self::Ext4 | Self::Btrfs)
51+
}
52+
}
53+
3554
/// The toplevel config entry for installation configs stored
3655
/// in bootc/install (e.g. /etc/bootc/install/05-custom.toml)
3756
#[derive(Debug, Clone, Serialize, Deserialize, Default)]

crates/lib/src/kernel.rs

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66
77
use std::path::Path;
88

9-
use anyhow::Result;
9+
use anyhow::{Context, Result};
10+
use bootc_kernel_cmdline::utf8::Cmdline;
1011
use camino::Utf8PathBuf;
1112
use cap_std_ext::cap_std::fs::Dir;
1213
use cap_std_ext::dirext::CapStdExtDirExt;
1314
use serde::Serialize;
1415

1516
use crate::bootc_composefs::boot::EFI_LINUX;
17+
use crate::bootc_composefs::status::ComposefsCmdline;
18+
use crate::composefs_consts::COMPOSEFS_CMDLINE;
1619

1720
/// Information about the kernel in a container image.
1821
#[derive(Debug, Serialize)]
@@ -31,8 +34,11 @@ pub(crate) struct Kernel {
3134
/// UKI kernels only have the single PE binary, whereas
3235
/// traditional "vmlinuz" kernels have distinct kernel and
3336
/// initramfs.
34-
pub(crate) enum KernelPath {
35-
Uki(Utf8PathBuf),
37+
pub(crate) enum KernelType {
38+
Uki {
39+
path: Utf8PathBuf,
40+
allow_missing_fsverity: bool,
41+
},
3642
Vmlinuz {
3743
path: Utf8PathBuf,
3844
initramfs: Utf8PathBuf,
@@ -47,7 +53,7 @@ pub(crate) enum KernelPath {
4753
/// to get the "public" form where needed.
4854
pub(crate) struct KernelInternal {
4955
pub(crate) kernel: Kernel,
50-
pub(crate) path: KernelPath,
56+
pub(crate) k_type: KernelType,
5157
}
5258

5359
impl From<KernelInternal> for Kernel {
@@ -67,12 +73,44 @@ pub(crate) fn find_kernel(root: &Dir) -> Result<Option<KernelInternal>> {
6773
// First, try to find a UKI
6874
if let Some(uki_path) = find_uki_path(root)? {
6975
let version = uki_path.file_stem().unwrap_or(uki_path.as_str()).to_owned();
76+
77+
let uki = std::fs::read(&uki_path).context("Reading UKI")?;
78+
79+
// Best effort to check for composefs=?verity in the UKI cmdline
80+
let cmdline = composefs_boot::uki::get_cmdline(&uki);
81+
82+
let allow_missing_fsverity = match cmdline {
83+
Ok(cmdline) => {
84+
let cmdline = Cmdline::from(cmdline);
85+
86+
match cmdline.find(COMPOSEFS_CMDLINE) {
87+
Some(param) => ComposefsCmdline::new(&param).allow_missing_fsverity,
88+
89+
// The cmdline might be in an addon, so don't allow missing verity
90+
None => false,
91+
}
92+
}
93+
94+
Err(uki_error) => match uki_error {
95+
composefs_boot::uki::UkiError::MissingSection(_) => {
96+
// TODO(Johan-Liebert1): Check this when we have full UKI Addons support
97+
// The cmdline might be in an addon, so don't allow missing verity
98+
false
99+
}
100+
101+
e => anyhow::bail!("Failed to read UKI cmdline: {e:?}"),
102+
},
103+
};
104+
70105
return Ok(Some(KernelInternal {
71106
kernel: Kernel {
72107
version,
73108
unified: true,
74109
},
75-
path: KernelPath::Uki(uki_path),
110+
k_type: KernelType::Uki {
111+
path: uki_path,
112+
allow_missing_fsverity,
113+
},
76114
}));
77115
}
78116

@@ -89,7 +127,7 @@ pub(crate) fn find_kernel(root: &Dir) -> Result<Option<KernelInternal>> {
89127
version,
90128
unified: false,
91129
},
92-
path: KernelPath::Vmlinuz {
130+
k_type: KernelType::Vmlinuz {
93131
path: vmlinuz,
94132
initramfs,
95133
},
@@ -156,8 +194,8 @@ mod tests {
156194
let kernel_internal = find_kernel(&tempdir)?.expect("should find kernel");
157195
assert_eq!(kernel_internal.kernel.version, "6.12.0-100.fc41.x86_64");
158196
assert!(!kernel_internal.kernel.unified);
159-
match &kernel_internal.path {
160-
KernelPath::Vmlinuz { path, initramfs } => {
197+
match &kernel_internal.k_type {
198+
KernelType::Vmlinuz { path, initramfs } => {
161199
assert_eq!(
162200
path.as_str(),
163201
"usr/lib/modules/6.12.0-100.fc41.x86_64/vmlinuz"
@@ -167,7 +205,7 @@ mod tests {
167205
"usr/lib/modules/6.12.0-100.fc41.x86_64/initramfs.img"
168206
);
169207
}
170-
KernelPath::Uki(_) => panic!("Expected Vmlinuz, got Uki"),
208+
KernelType::Uki { .. } => panic!("Expected Vmlinuz, got Uki"),
171209
}
172210
Ok(())
173211
}
@@ -181,11 +219,11 @@ mod tests {
181219
let kernel_internal = find_kernel(&tempdir)?.expect("should find kernel");
182220
assert_eq!(kernel_internal.kernel.version, "fedora-6.12.0");
183221
assert!(kernel_internal.kernel.unified);
184-
match &kernel_internal.path {
185-
KernelPath::Uki(path) => {
222+
match &kernel_internal.k_type {
223+
KernelType::Uki { path, .. } => {
186224
assert_eq!(path.as_str(), "boot/EFI/Linux/fedora-6.12.0.efi");
187225
}
188-
KernelPath::Vmlinuz { .. } => panic!("Expected Uki, got Vmlinuz"),
226+
KernelType::Vmlinuz { .. } => panic!("Expected Uki, got Vmlinuz"),
189227
}
190228
Ok(())
191229
}

crates/lib/src/ukify.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ pub(crate) fn build_ukify(
5656
.ok_or_else(|| anyhow::anyhow!("No kernel found in {rootfs}"))?;
5757

5858
// Extract vmlinuz and initramfs paths, or bail if this is already a UKI
59-
let (vmlinuz_path, initramfs_path) = match kernel.path {
60-
crate::kernel::KernelPath::Vmlinuz { path, initramfs } => (path, initramfs),
61-
crate::kernel::KernelPath::Uki(path) => {
59+
let (vmlinuz_path, initramfs_path) = match kernel.k_type {
60+
crate::kernel::KernelType::Vmlinuz { path, initramfs } => (path, initramfs),
61+
crate::kernel::KernelType::Uki { path, .. } => {
6262
anyhow::bail!("Cannot build UKI: rootfs already contains a UKI at {path}");
6363
}
6464
};

0 commit comments

Comments
 (0)