Skip to content

Commit fb1de9f

Browse files
src/fsverity: up our FsVerityHashValue trait game
Right now we define pub type Sha256HashValue = [u8; 32]; pub type Sha512HashValue = [u8; 64]; which is simple enough but also painful: we can implement our own traits for these types (and indeed, we do it for FsVerityHashValue) but we can't implement things like fmt::Debug because there's a default impl (which rather unhelpfully prints the hashes out as an array of bytes). Turn these into proper types using a trivial struct wrapper: pub struct Sha256HashValue([u8; 32]); pub struct Sha512HashValue([u8; 64]); which gives us a lot more control. We can start by implementing meaningful fmt::Debug traits. Doing this means that we implicitly lose our implementation of AsRef<[u8]> and AsMut<[u8]> which we've been mostly using in order to do various ad-hoc encoding to/from various forms of hex values all over the codebase. Move all of those ad-hoc forms of encoding into proper methods on the trait itself by adding functions like ::to_hex() and ::from_hex() which we can use instead. We also drop our last instances of using parse_sha256() for fs-verity digests. There are a couple of other places where we need the actual bytes, though: when reading/writing hash values to splitstreams and when reading/writing the metacopy xattr in erofs images. Deal with those by deriving the usual zerocopy FromBytes/IntoBytes traits for our new hash types and moving the code in the splitstream and erofs modules to directly include the data inside of the relevant structure types, mark those types as FromBytes/IntoBytes, and use the zerocopy APIs directly. In the case of the metacopy attr in the erofs code this provides an opportunity for a substantial cleanup in an otherwise scary part of the code. Signed-off-by: Allison Karlitskaya <[email protected]>
1 parent dc5d409 commit fb1de9f

File tree

16 files changed

+261
-148
lines changed

16 files changed

+261
-148
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ thiserror = "2.0.0"
3636
tokio = "1.24.2"
3737
toml = "0.8.0"
3838
xxhash-rust = { version = "0.8.2", features = ["xxh32"] }
39-
zerocopy = { version = "0.8.0", features = ["derive"] }
39+
zerocopy = { version = "0.8.0", features = ["derive", "std"] }
4040
zstd = "0.13.0"
4141

4242
[dev-dependencies]

src/bin/cfsctl.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ use clap::{Parser, Subcommand};
55

66
use rustix::fs::CWD;
77

8-
use composefs::{oci, repository::Repository, util::parse_sha256};
8+
use composefs::{
9+
fsverity::{FsVerityHashValue, Sha256HashValue},
10+
oci,
11+
repository::Repository,
12+
util::parse_sha256,
13+
};
914

1015
/// cfsctl
1116
#[derive(Debug, Parser)]
@@ -130,7 +135,7 @@ fn main() -> Result<()> {
130135
}
131136
Command::ImportImage { reference } => {
132137
let image_id = repo.import_image(&reference, &mut std::io::stdin())?;
133-
println!("{}", hex::encode(image_id));
138+
println!("{}", image_id.to_hex());
134139
}
135140
Command::Oci { cmd: oci_cmd } => match oci_cmd {
136141
OciCommand::ImportLayer { name, sha256 } => {
@@ -140,7 +145,7 @@ fn main() -> Result<()> {
140145
name.as_deref(),
141146
&mut std::io::stdin(),
142147
)?;
143-
println!("{}", hex::encode(object_id));
148+
println!("{}", object_id.to_hex());
144149
}
145150
OciCommand::LsLayer { name } => {
146151
oci::ls_layer(&repo, &name)?;
@@ -150,7 +155,7 @@ fn main() -> Result<()> {
150155
}
151156
OciCommand::CreateImage { config, name } => {
152157
let image_id = oci::image::create_image(&repo, &config, name.as_deref(), None)?;
153-
println!("{}", hex::encode(image_id));
158+
println!("{}", image_id.to_hex());
154159
}
155160
OciCommand::Pull { ref image, name } => {
156161
let runtime = tokio::runtime::Builder::new_current_thread()
@@ -161,10 +166,13 @@ fn main() -> Result<()> {
161166
runtime.block_on(async move { oci::pull(&repo, image, name.as_deref()).await })?;
162167
}
163168
OciCommand::Seal { verity, ref name } => {
164-
let (sha256, verity) =
165-
oci::seal(&repo, name, verity.map(parse_sha256).transpose()?.as_ref())?;
169+
let (sha256, verity) = oci::seal(
170+
&repo,
171+
name,
172+
verity.map(Sha256HashValue::from_hex).transpose()?.as_ref(),
173+
)?;
166174
println!("sha256 {}", hex::encode(sha256));
167-
println!("verity {}", hex::encode(verity));
175+
println!("verity {}", verity.to_hex());
168176
}
169177
OciCommand::Mount {
170178
ref name,
@@ -182,7 +190,7 @@ fn main() -> Result<()> {
182190
},
183191
Command::CreateImage { ref path } => {
184192
let image_id = composefs::fs::create_image(path, Some(&repo))?;
185-
println!("{}", hex::encode(image_id));
193+
println!("{}", image_id.to_hex());
186194
}
187195
Command::CreateDumpfile { ref path } => {
188196
composefs::fs::create_dumpfile(path)?;
@@ -193,7 +201,7 @@ fn main() -> Result<()> {
193201
Command::ImageObjects { name } => {
194202
let objects = repo.objects_for_image(&name)?;
195203
for object in objects {
196-
println!("{}", hex::encode(object));
204+
println!("{}", object.to_hex());
197205
}
198206
}
199207
Command::GC => {

src/bin/composefs-setup-root.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use rustix::{
2121
use serde::Deserialize;
2222

2323
use composefs::{
24-
fsverity::Sha256HashValue,
24+
fsverity::{FsVerityHashValue, Sha256HashValue},
2525
mount::{composefs_fsmount, mount_at, FsHandle},
2626
mountcompat::{overlayfs_set_fd, overlayfs_set_lower_and_data_fds, prepare_mount},
2727
repository::Repository,
@@ -204,9 +204,7 @@ fn parse_composefs_cmdline(cmdline: &[u8]) -> Result<Sha256HashValue> {
204204
// TODO?: officially we need to understand quoting with double-quotes...
205205
for part in cmdline.split(|c| c.is_ascii_whitespace()) {
206206
if let Some(digest) = part.strip_prefix(b"composefs=") {
207-
let mut value = [0; 32];
208-
hex::decode_to_slice(digest, &mut value).context("Parsing composefs=")?;
209-
return Ok(value);
207+
return Sha256HashValue::from_hex(digest).context("Parsing composefs=");
210208
}
211209
}
212210
bail!("Unable to find composefs= cmdline parameter");
@@ -238,7 +236,7 @@ fn setup_root(args: Args) -> Result<()> {
238236
Some(cmdline) => cmdline.as_bytes(),
239237
None => &std::fs::read("/proc/cmdline")?,
240238
};
241-
let image = hex::encode(parse_composefs_cmdline(cmdline)?);
239+
let image = parse_composefs_cmdline(cmdline)?.to_hex();
242240

243241
let new_root = match args.root_fs {
244242
Some(path) => open_root_fs(&path).context("Failed to clone specified root fs")?,
@@ -295,12 +293,9 @@ mod test {
295293
assert!(parse_composefs_cmdline(case.as_bytes()).is_err());
296294
}
297295
let digest = "8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52";
298-
let digest_bytes = hex::decode(digest).unwrap();
299296
similar_asserts::assert_eq!(
300-
parse_composefs_cmdline(format!("composefs={digest}").as_bytes())
301-
.unwrap()
302-
.as_slice(),
303-
&digest_bytes
297+
parse_composefs_cmdline(format!("composefs={digest}").as_bytes()).unwrap(),
298+
Sha256HashValue::from_hex(digest).unwrap()
304299
);
305300
}
306301
}

src/dumpfile.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use anyhow::Result;
1212
use rustix::fs::FileType;
1313

1414
use crate::{
15-
fsverity::Sha256HashValue,
15+
fsverity::{FsVerityHashValue, Sha256HashValue},
1616
image::{Directory, FileSystem, Inode, Leaf, LeafContent, RegularFile, Stat},
1717
};
1818

@@ -66,7 +66,7 @@ fn write_entry(
6666
write_escaped(writer, content)?;
6767
write!(writer, " ")?;
6868
if let Some(id) = digest {
69-
write!(writer, "{}", hex::encode(id))?;
69+
write!(writer, "{}", id.to_hex())?;
7070
} else {
7171
write_empty(writer)?;
7272
}
@@ -129,7 +129,7 @@ pub fn write_leaf(
129129
*size,
130130
nlink,
131131
0,
132-
format!("{:02x}/{}", id[0], hex::encode(&id[1..])),
132+
id.to_object_pathname(),
133133
&[],
134134
Some(id),
135135
),

src/erofs/composefs.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
2+
3+
use crate::fsverity::FsVerityHashValue;
4+
5+
/* From linux/fs/overlayfs/overlayfs.h struct ovl_metacopy */
6+
#[derive(Debug, FromBytes, Immutable, KnownLayout, IntoBytes)]
7+
#[repr(C)]
8+
pub(super) struct OverlayMetacopy<H: FsVerityHashValue> {
9+
version: u8,
10+
len: u8,
11+
flags: u8,
12+
digest_algo: u8,
13+
pub(super) digest: H,
14+
}
15+
16+
impl<H: FsVerityHashValue> OverlayMetacopy<H> {
17+
pub(super) fn new(digest: &H) -> Self {
18+
Self {
19+
version: 0,
20+
len: size_of::<Self>() as u8,
21+
flags: 0,
22+
digest_algo: H::ALGORITHM,
23+
digest: digest.clone(),
24+
}
25+
}
26+
27+
pub(super) fn valid(&self) -> bool {
28+
self.version == 0
29+
&& self.len == size_of::<Self>() as u8
30+
&& self.flags == 0
31+
&& self.digest_algo == H::ALGORITHM
32+
}
33+
}

src/erofs/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod composefs;
12
pub mod debug;
23
pub mod format;
34
pub mod reader;

src/erofs/reader.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ use std::ops::Range;
55
use thiserror::Error;
66
use zerocopy::{little_endian::U32, FromBytes, Immutable, KnownLayout};
77

8-
use super::format::{
9-
CompactInodeHeader, ComposefsHeader, DataLayout, DirectoryEntryHeader, ExtendedInodeHeader,
10-
InodeXAttrHeader, ModeField, Superblock, XAttrHeader,
8+
use super::{
9+
composefs::OverlayMetacopy,
10+
format::{
11+
CompactInodeHeader, ComposefsHeader, DataLayout, DirectoryEntryHeader, ExtendedInodeHeader,
12+
InodeXAttrHeader, ModeField, Superblock, XAttrHeader,
13+
},
1114
};
1215
use crate::fsverity::Sha256HashValue;
1316

@@ -491,18 +494,17 @@ pub struct ObjectCollector {
491494

492495
impl ObjectCollector {
493496
fn visit_xattr(&mut self, attr: &XAttr) {
494-
// TODO: "4" is a bit magic, isn't it?
497+
// This is the index of "trusted". See XATTR_PREFIXES in format.rs.
495498
if attr.header.name_index != 4 {
496499
return;
497500
}
498501
if attr.suffix() != b"overlay.metacopy" {
499502
return;
500503
}
501-
let value = attr.value();
502-
// TODO: oh look, more magic values...
503-
if value.len() == 36 && value[..4] == [0, 36, 0, 1] {
504-
// SAFETY: We already checked that the length is 4 + 32
505-
self.objects.insert(value[4..].try_into().unwrap());
504+
if let Ok(value) = OverlayMetacopy::read_from_bytes(attr.value()) {
505+
if value.valid() {
506+
self.objects.insert(value.digest);
507+
}
506508
}
507509
}
508510

src/erofs/writer.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ use xxhash_rust::xxh32::xxh32;
1111
use zerocopy::{Immutable, IntoBytes};
1212

1313
use crate::{
14-
erofs::{format, reader::round_up},
14+
erofs::{composefs::OverlayMetacopy, format, reader::round_up},
15+
fsverity::FsVerityHashValue,
1516
image,
1617
};
1718

@@ -409,10 +410,12 @@ impl<'a> InodeCollector<'a> {
409410
..
410411
}) = content
411412
{
412-
let metacopy = [&[0, 36, 0, 1], &id[..]].concat();
413-
xattrs.add(b"trusted.overlay.metacopy", &metacopy);
413+
xattrs.add(
414+
b"trusted.overlay.metacopy",
415+
OverlayMetacopy::new(id).as_bytes(),
416+
);
414417

415-
let redirect = format!("/{:02x}/{}", id[0], hex::encode(&id[1..]));
418+
let redirect = format!("/{}", id.to_object_pathname());
416419
xattrs.add(b"trusted.overlay.redirect", redirect.as_bytes());
417420
}
418421

src/fsverity/digest.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ impl<H: FsVerityHashValue, const LG_BLKSZ: u8> FsVerityHasher<H, LG_BLKSZ> {
6464
// We had a complete value, but now we're adding new data.
6565
// This means that we need to add a new hash layer...
6666
let mut new_layer = FsVerityLayer::new();
67-
new_layer.add_data(value.as_ref());
67+
new_layer.add_data(value.as_bytes());
6868
self.layers.push(new_layer);
6969
}
7070

@@ -76,7 +76,7 @@ impl<H: FsVerityHashValue, const LG_BLKSZ: u8> FsVerityHasher<H, LG_BLKSZ> {
7676

7777
for layer in self.layers.iter_mut() {
7878
// We have a layer we need to hash this value into
79-
layer.add_data(value.as_ref());
79+
layer.add_data(value.as_bytes());
8080
if layer.remaining != 0 {
8181
return;
8282
}
@@ -97,7 +97,7 @@ impl<H: FsVerityHashValue, const LG_BLKSZ: u8> FsVerityHasher<H, LG_BLKSZ> {
9797
for layer in self.layers.iter_mut() {
9898
// We have a layer we need to hash this value into
9999
if value != H::EMPTY {
100-
layer.add_data(value.as_ref());
100+
layer.add_data(value.as_bytes());
101101
}
102102
if layer.remaining != (1 << LG_BLKSZ) {
103103
// ...but now this layer itself is complete, so get the value of *it*.
@@ -143,7 +143,7 @@ impl<H: FsVerityHashValue, const LG_BLKSZ: u8> FsVerityHasher<H, LG_BLKSZ> {
143143
context.update(0u8.to_le_bytes()); /* salt_size */
144144
context.update([0; 4]); /* reserved */
145145
context.update(self.n_bytes.to_le_bytes());
146-
context.update(self.root_hash());
146+
context.update(self.root_hash().as_bytes());
147147
context.update([0].repeat(64 - size_of::<H>()));
148148
context.update([0; 32]); /* salt */
149149
context.update([0; 144]); /* reserved */
@@ -162,12 +162,12 @@ mod tests {
162162
#[test]
163163
fn test_digest() {
164164
assert_eq!(
165-
hex::encode(FsVerityHasher::<Sha256HashValue, 12>::hash(b"hello world")),
165+
FsVerityHasher::<Sha256HashValue, 12>::hash(b"hello world").to_hex(),
166166
"1e2eaa4202d750a41174ee454970b92c1bc2f925b1e35076d8c7d5f56362ba64"
167167
);
168168

169169
assert_eq!(
170-
hex::encode(FsVerityHasher::<Sha512HashValue, 12>::hash(b"hello world")),
170+
FsVerityHasher::<Sha512HashValue, 12>::hash(b"hello world").to_hex(),
171171
"18430270729d162d4e469daca123ae61893db4b0583d8f7081e3bf4f92b88ba514e7982f10733fb6aa895195c5ae8fd2eb2c47a8be05513ce5a0c51a6f570409"
172172
);
173173
}

0 commit comments

Comments
 (0)