-
Notifications
You must be signed in to change notification settings - Fork 13
Make fs-verity optional #147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
16b00da
0755ff8
6fbb822
85e3b1c
e5cd2a0
f3e80af
f37ae95
3eff24d
9a4be07
928bcf6
6597f49
752ca83
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,9 @@ pub struct App { | |
#[clap(long, group = "repopath")] | ||
system: bool, | ||
|
||
#[clap(long)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A small doc here might be nice. |
||
insecure: bool, | ||
|
||
#[clap(subcommand)] | ||
cmd: Command, | ||
} | ||
|
@@ -160,7 +163,7 @@ async fn main() -> Result<()> { | |
|
||
let args = App::parse(); | ||
|
||
let repo: Repository<Sha256HashValue> = (if let Some(path) = &args.repo { | ||
let mut repo: Repository<Sha256HashValue> = (if let Some(path) = &args.repo { | ||
Repository::open_path(CWD, path) | ||
} else if args.system { | ||
Repository::open_system() | ||
|
@@ -172,6 +175,8 @@ async fn main() -> Result<()> { | |
Repository::open_user() | ||
})?; | ||
|
||
repo.set_insecure(args.insecure); | ||
|
||
match args.cmd { | ||
Command::Transaction => { | ||
// just wait for ^C | ||
|
@@ -339,7 +344,7 @@ async fn main() -> Result<()> { | |
fs.print_dumpfile()?; | ||
} | ||
Command::Mount { name, mountpoint } => { | ||
repo.mount(&name, &mountpoint)?; | ||
repo.mount_at(&name, &mountpoint)?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please merge this into the commit that made this change. Each commit ought to compile cleanly and pass tests. |
||
} | ||
Command::ImageObjects { name } => { | ||
let objects = repo.objects_for_image(&name)?; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,9 +59,14 @@ pub fn write_t2_simple<ObjectID: FsVerityHashValue>( | |
create_dir_all(&efi_linux)?; | ||
let filename = efi_linux.join(t2.filename.as_ref()); | ||
let content = read_file(&t2.file, repo)?; | ||
let Some(composefs) = get_cmdline_value(uki::get_cmdline(&content)?, "composefs=") else { | ||
let Some(mut composefs) = get_cmdline_value(uki::get_cmdline(&content)?, "composefs=") else { | ||
bail!("The UKI is missing a composefs= commandline parameter"); | ||
}; | ||
|
||
if let Some(stripped) = composefs.strip_prefix('?') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, please add a new function to the end of |
||
composefs = stripped | ||
} | ||
|
||
let expected = root_id.to_hex(); | ||
ensure!( | ||
composefs == expected, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -152,7 +152,7 @@ pub fn mount<ObjectID: FsVerityHashValue>( | |
let Some(id) = config.get_config_annotation("containers.composefs.fsverity") else { | ||
bail!("Can only mount sealed containers"); | ||
}; | ||
repo.mount(id, mountpoint) | ||
repo.mount_at(id, mountpoint) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto. Please merge that into the original commit. |
||
} | ||
|
||
#[cfg(test)] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,7 @@ use serde::Deserialize; | |
|
||
use composefs::{ | ||
fsverity::{FsVerityHashValue, Sha256HashValue}, | ||
mount::{composefs_fsmount, mount_at, FsHandle}, | ||
mount::{mount_at, FsHandle}, | ||
mountcompat::{overlayfs_set_fd, overlayfs_set_lower_and_data_fds, prepare_mount}, | ||
repository::Repository, | ||
}; | ||
|
@@ -164,10 +164,10 @@ fn open_root_fs(path: &Path) -> Result<OwnedFd> { | |
Ok(rootfs) | ||
} | ||
|
||
fn mount_composefs_image(sysroot: &OwnedFd, name: &str) -> Result<OwnedFd> { | ||
let repo = Repository::<Sha256HashValue>::open_path(sysroot, "composefs")?; | ||
let image = repo.open_image(name)?; | ||
composefs_fsmount(image, name, repo.objects_dir()?).context("Failed to mount composefs image") | ||
fn mount_composefs_image(sysroot: &OwnedFd, name: &str, insecure: bool) -> Result<OwnedFd> { | ||
let mut repo = Repository::<Sha256HashValue>::open_path(sysroot, "composefs")?; | ||
repo.set_insecure(insecure); | ||
repo.mount(name).context("Failed to mount composefs image") | ||
} | ||
|
||
fn mount_subdir( | ||
|
@@ -198,12 +198,19 @@ fn mount_subdir( | |
} | ||
|
||
// Implementation | ||
fn parse_composefs_cmdline<H: FsVerityHashValue>(cmdline: &str) -> Result<H> { | ||
let Some(digest) = get_cmdline_value(cmdline, "composefs=") else { | ||
fn parse_composefs_cmdline<H: FsVerityHashValue>(cmdline: &str) -> Result<(H, bool)> { | ||
let Some(mut digest) = get_cmdline_value(cmdline, "composefs=") else { | ||
bail!("Unable to find composefs= cmdline parameter"); | ||
}; | ||
|
||
H::from_hex(digest).context("Parsing composefs=") | ||
let mut insecure = false; | ||
if let Some(stripped) = digest.strip_prefix('?') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we were going to move this code into |
||
digest = stripped; | ||
insecure = true; | ||
} | ||
|
||
let hash = H::from_hex(digest).context("Parsing composefs=")?; | ||
Ok((hash, insecure)) | ||
} | ||
|
||
fn gpt_workaround() -> Result<()> { | ||
|
@@ -232,11 +239,12 @@ fn setup_root(args: Args) -> Result<()> { | |
Some(cmdline) => cmdline, | ||
None => &std::fs::read_to_string("/proc/cmdline")?, | ||
}; | ||
let image = parse_composefs_cmdline::<Sha256HashValue>(cmdline)?.to_hex(); | ||
let (img, insecure) = parse_composefs_cmdline::<Sha256HashValue>(cmdline)?; | ||
let image = img.to_hex(); | ||
|
||
let new_root = match args.root_fs { | ||
Some(path) => open_root_fs(&path).context("Failed to clone specified root fs")?, | ||
None => mount_composefs_image(&sysroot, &image)?, | ||
None => mount_composefs_image(&sysroot, &image, insecure)?, | ||
}; | ||
|
||
// we need to clone this before the next step to make sure we get the old one | ||
|
@@ -289,9 +297,8 @@ mod test { | |
assert!(parse_composefs_cmdline::<Sha256HashValue>(case).is_err()); | ||
} | ||
let digest = "8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52"; | ||
similar_asserts::assert_eq!( | ||
parse_composefs_cmdline::<Sha256HashValue>(&format!("composefs={digest}")).unwrap(), | ||
Sha256HashValue::from_hex(digest).unwrap() | ||
); | ||
let (digest_cmdline, _) = | ||
parse_composefs_cmdline::<Sha256HashValue>(&format!("composefs={digest}")).unwrap(); | ||
similar_asserts::assert_eq!(digest_cmdline, Sha256HashValue::from_hex(digest).unwrap()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -78,14 +78,21 @@ pub fn erofs_mount(image: OwnedFd) -> Result<OwnedFd> { | |
)?) | ||
} | ||
|
||
pub fn composefs_fsmount(image: OwnedFd, name: &str, basedir: impl AsFd) -> Result<OwnedFd> { | ||
pub fn composefs_fsmount( | ||
image: OwnedFd, | ||
name: &str, | ||
basedir: impl AsFd, | ||
enable_verity: bool, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Almost feels like this should be a flags field.... in which case disable-verity would be the opt-in. Can we make this function |
||
) -> Result<OwnedFd> { | ||
let erofs_mnt = prepare_mount(erofs_mount(image)?)?; | ||
|
||
let overlayfs = FsHandle::open("overlay")?; | ||
fsconfig_set_string(overlayfs.as_fd(), "source", format!("composefs:{name}"))?; | ||
fsconfig_set_string(overlayfs.as_fd(), "metacopy", "on")?; | ||
fsconfig_set_string(overlayfs.as_fd(), "redirect_dir", "on")?; | ||
fsconfig_set_string(overlayfs.as_fd(), "verity", "require")?; | ||
if enable_verity { | ||
fsconfig_set_string(overlayfs.as_fd(), "verity", "require")?; | ||
} | ||
overlayfs_set_lower_and_data_fds(&overlayfs, &erofs_mnt, Some(&basedir))?; | ||
fsconfig_create(overlayfs.as_fd())?; | ||
|
||
|
@@ -101,7 +108,8 @@ pub fn mount_composefs_at( | |
name: &str, | ||
basedir: impl AsFd, | ||
mountpoint: impl AsRef<Path>, | ||
enable_verity: bool, | ||
) -> Result<()> { | ||
let mnt = composefs_fsmount(image, name, basedir)?; | ||
let mnt = composefs_fsmount(image, name, basedir, enable_verity)?; | ||
Ok(mount_at(mnt, CWD, &canonicalize(mountpoint)?)?) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,8 +22,9 @@ use sha2::{Digest, Sha256}; | |
use crate::{ | ||
fsverity::{ | ||
compute_verity, enable_verity, ensure_verity_equal, measure_verity, FsVerityHashValue, | ||
MeasureVerityError, | ||
}, | ||
mount::mount_composefs_at, | ||
mount::{composefs_fsmount, mount_composefs_at}, | ||
splitstream::{DigestMap, SplitStreamReader, SplitStreamWriter}, | ||
util::{proc_self_fd, Sha256Digest}, | ||
}; | ||
|
@@ -58,6 +59,7 @@ pub struct Repository<ObjectID: FsVerityHashValue> { | |
repository: OwnedFd, | ||
objects: OnceCell<OwnedFd>, | ||
_data: std::marker::PhantomData<ObjectID>, | ||
insecure: bool, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably move that above the |
||
} | ||
|
||
impl<ObjectID: FsVerityHashValue> Drop for Repository<ObjectID> { | ||
|
@@ -86,6 +88,7 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> { | |
repository, | ||
objects: OnceCell::new(), | ||
_data: std::marker::PhantomData, | ||
insecure: false, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ...ditto |
||
}) | ||
} | ||
|
||
|
@@ -127,7 +130,9 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> { | |
Ok(fd) => { | ||
// measure the existing file to ensure that it's correct | ||
// TODO: try to replace file if it's broken? | ||
ensure_verity_equal(fd, &id)?; | ||
if !self.insecure { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a general rule I think we always want to attempt to measure fs-verity even in insecure mode, but allow that it fails with "not supported" errors. |
||
ensure_verity_equal(fd, &id)?; | ||
} | ||
return Ok(id); | ||
} | ||
Err(Errno::NOENT) => { | ||
|
@@ -151,8 +156,23 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> { | |
)?; | ||
drop(file); | ||
|
||
enable_verity::<ObjectID>(&ro_fd).context("Enabling verity digest")?; | ||
ensure_verity_equal(&ro_fd, &id).context("Double-checking verity digest")?; | ||
if self.insecure { | ||
match measure_verity::<ObjectID>(&ro_fd) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The measure here doesn't make much sense. We just created this, so the answer will always be either that there's no fs-verity on the file, or that fs-verity is unsupported. But you could get that information by just trying to enable it and deal with the errors. I think you could probably also merge the code paths with the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also please merge this into the first commit which adds |
||
Ok(found) if found == FsVerityHashValue::from_hex(&path)? => { | ||
// insecure but file sytem supports it, so enable it anyway | ||
enable_verity::<ObjectID>(&ro_fd).context("Enabling verity digest")?; | ||
ensure_verity_equal(&ro_fd, &id).context("Double-checking verity digest")?; | ||
} | ||
Ok(_) => bail!("fs-verity content mismatch"), | ||
Err(MeasureVerityError::VerityMissing) => { | ||
// file system doesn't support it, just continue without | ||
} | ||
Err(other) => Err(other)?, | ||
} | ||
} else { | ||
enable_verity::<ObjectID>(&ro_fd).context("Enabling verity digest")?; | ||
ensure_verity_equal(&ro_fd, &id).context("Double-checking verity digest")?; | ||
} | ||
|
||
match linkat( | ||
CWD, | ||
|
@@ -175,10 +195,17 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> { | |
|
||
fn open_with_verity(&self, filename: &str, expected_verity: &ObjectID) -> Result<OwnedFd> { | ||
let fd = self.openat(filename, OFlags::RDONLY)?; | ||
ensure_verity_equal(&fd, expected_verity)?; | ||
if !self.insecure { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
ensure_verity_equal(&fd, expected_verity)?; | ||
} | ||
Ok(fd) | ||
} | ||
|
||
pub fn set_insecure(&mut self, insecure: bool) -> &mut Self { | ||
self.insecure = insecure; | ||
self | ||
} | ||
|
||
/// Creates a SplitStreamWriter for writing a split stream. | ||
/// You should write the data to the returned object and then pass it to .store_stream() to | ||
/// store the result. | ||
|
@@ -220,6 +247,10 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> { | |
|
||
/// Basically the same as has_stream() except that it performs expensive verification | ||
pub fn check_stream(&self, sha256: &Sha256Digest) -> Result<Option<ObjectID>> { | ||
if self.insecure { | ||
return self.has_stream(sha256); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't sufficient, I'm afraid. We want to iterate the full structure to check that it's consistent and matches the given sha256 body digest, even if we don't have fs-verity. |
||
} | ||
|
||
match self.openat(&format!("streams/{}", hex::encode(sha256)), OFlags::RDONLY) { | ||
Ok(stream) => { | ||
let measured_verity: ObjectID = measure_verity(&stream)?; | ||
|
@@ -383,24 +414,40 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> { | |
self.write_image(Some(name), &data) | ||
} | ||
|
||
pub fn open_image(&self, name: &str) -> Result<OwnedFd> { | ||
fn open_image(&self, name: &str) -> Result<(OwnedFd, bool)> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Depending on how many users this API has, it might make sense to inline it where it's used... I guess the GC thing is using it as well, though...... I'm glad it's no longer public, at least. A short doc about what the 'bool' is would certainly be appreciated though. |
||
let image = self.openat(&format!("images/{name}"), OFlags::RDONLY)?; | ||
|
||
if !name.contains("/") { | ||
if !name.contains("/") && !self.insecure { | ||
// A name with no slashes in it is taken to be a sha256 fs-verity digest | ||
ensure_verity_equal(&image, &ObjectID::from_hex(name)?)?; | ||
} | ||
|
||
Ok(image) | ||
match measure_verity::<ObjectID>(&image) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're effectively measuring and comparing verity twice here... You left the check above... |
||
Ok(found) if found == FsVerityHashValue::from_hex(name)? => Ok((image, true)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, if it does I guess we can just skip the entire thing and return (image, true)? |
||
Ok(_) => bail!("fs-verity content mismatch"), | ||
Err(MeasureVerityError::VerityMissing) if self.insecure => Ok((image, false)), | ||
Err(other) => Err(other)?, | ||
} | ||
} | ||
|
||
pub fn mount(&self, name: &str) -> Result<OwnedFd> { | ||
let (image, enable_verity) = self.open_image(name)?; | ||
Ok(composefs_fsmount( | ||
image, | ||
name, | ||
self.objects_dir()?, | ||
enable_verity, | ||
)?) | ||
} | ||
|
||
pub fn mount(&self, name: &str, mountpoint: &str) -> Result<()> { | ||
let image = self.open_image(name)?; | ||
pub fn mount_at(&self, name: &str, mountpoint: &str) -> Result<()> { | ||
let (image, enable_verity) = self.open_image(name)?; | ||
Ok(mount_composefs_at( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we maybe get rid of |
||
image, | ||
name, | ||
self.objects_dir()?, | ||
mountpoint, | ||
enable_verity, | ||
)?) | ||
} | ||
|
||
|
@@ -522,7 +569,7 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> { | |
} | ||
|
||
pub fn objects_for_image(&self, name: &str) -> Result<HashSet<ObjectID>> { | ||
let image = self.open_image(name)?; | ||
let (image, _) = self.open_image(name)?; | ||
let mut data = vec![]; | ||
std::fs::File::from(image).read_to_end(&mut data)?; | ||
Ok(crate::erofs::reader::collect_objects(&data)?) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,47 @@ | ||
#!/bin/sh | ||
#!/bin/bash | ||
|
||
set -eux | ||
|
||
output="$1" | ||
FIX_VERITY=0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The user should be able to specify |
||
SETUP_REPART=0 | ||
FS_FORMAT=ext4 | ||
parsecmd() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This commandline interface thing seems a bit overkill to be honest... It might be nicer to just be able to set a couple of extra envvars to modify the test environment, from outside:
Maybe we could avoid having a whole separate test scenario... |
||
while [ $# -gt 0 ]; do | ||
case "$1" in | ||
#CMDSTART | ||
-fv|--fix-verity) # | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please don't do two-character 'short' opts... |
||
# run run-repart with FIX_VERITY=1 | ||
FIX_VERITY=1 | ||
;; | ||
-sr|--setup-repart) # | ||
# run run-repart with SETUP_REPART=1 | ||
SETUP_REPART=1 | ||
;; | ||
-h|--help) # | ||
# this help | ||
echo "Usage: $0 [OPTION]..." | ||
printf "\nCommand line arguments:\n" | ||
sed -rn '/CMDSTART/,/CMDEND/{/\) \#|^ +# /{s/\)? #//g;s/^ //;p}}' "$0" | ||
exit 0 | ||
;; | ||
*) | ||
echo "Unknown parameter $1" | ||
exit 1 | ||
;; | ||
esac | ||
#CMDEND | ||
shift | ||
done | ||
} | ||
|
||
args=($@) | ||
output="${args[-1]}" | ||
parse_args=("${args[*]:0:${#args[@]}-1}") | ||
parsecmd ${parse_args[@]} | ||
|
||
# check that the image doesn't have errors | ||
fsck.erofs tmp/sysroot/composefs/images/* | ||
|
||
fakeroot "${0%/*}/run-repart" tmp/image.raw | ||
SETUP_REPART=$SETUP_REPART FIX_VERITY=$FIX_VERITY fakeroot "${0%/*}/run-repart" tmp/image.raw | ||
qemu-img convert -f raw tmp/image.raw -O qcow2 "${output}" | ||
rm tmp/image.raw |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,7 @@ chcon system_u:object_r:etc_t:s0 tmp/sysroot/state/*/etc/* | |
|
||
definitions="${0%/*}/repart.d" | ||
|
||
if [ "${FIX_VERITY:-}" = '1' ]; then | ||
if [ "$SETUP_REPART" = '1' ]; then | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This variable is hilariously poorly named 🤣 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be interesting to make this setup a bit more 'neutral' by dynamically generating the |
||
export SYSTEMD_REPART_MKFS_OPTIONS_EXT4='-O verity' | ||
cp -r "${definitions}" tmp/repart.d | ||
sed -i 's/:fsverity=copy//' tmp/repart.d/02-sysroot.conf | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you might have forgotten to also add the directory in this commit :)