Skip to content
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

cli/avb: Update kernel cmdline descriptor for devices that use dm= #205

Merged
merged 1 commit into from
Nov 14, 2023
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
125 changes: 123 additions & 2 deletions avbroot/src/cli/avb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::{
crypto::{self, PassphraseSource},
format::avb::{
self, AlgorithmType, AppendedDescriptorMut, AppendedDescriptorRef, Descriptor, Footer,
Header,
HashTreeDescriptor, Header, KernelCmdlineDescriptor,
},
stream::{self, PSeekFile, Reopen},
util,
Expand Down Expand Up @@ -194,6 +194,121 @@ fn write_raw_and_update(
Ok(raw_file)
}

/// Compute the kernel command line arguments to allow the kernel to set up the
/// dm-verity block device without dm-init or userspace helpers. This is handled
/// by init/do_mounts_dm.c in older Pixel devices (and ChromiumOS).
fn compute_dm_verity_cmdline(descriptor: &HashTreeDescriptor) -> String {
use std::fmt::Write;

let mut result = String::new();

// Number of device mapper devices.
result.push_str("dm=\"1");
// Block device name.
result.push_str(" vroot");
// Block device UUID.
result.push_str(" none");
// Block device write mode.
result.push_str(" ro");
// Number of device mapper targets.
result.push_str(" 1,");
// Starting sector.
result.push('0');
// Sector count.
write!(&mut result, " {}", descriptor.image_size / 512).unwrap();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't using unwrap() result in the to panic if it fails? I suggest using unwrap_or_default(),unwrap_or(), or better using a map() instead.. 😅

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally yes, but formatting into a String is guaranteed to never fail (unless the system runs out of memory, but Rust will panic in that case anyway).

// dm-verity version.
write!(&mut result, " verity {}", descriptor.dm_verity_version).unwrap();
// Data block device (replaced by bootloader at runtime).
result.push_str(" PARTUUID=$(ANDROID_SYSTEM_PARTUUID)");
// Hash block device (replaced by bootloader at runtime).
result.push_str(" PARTUUID=$(ANDROID_SYSTEM_PARTUUID)");
// Data block size.
write!(&mut result, " {}", descriptor.data_block_size).unwrap();
// Hash block size.
write!(&mut result, " {}", descriptor.hash_block_size).unwrap();
// Number of data blocks.
write!(
&mut result,
" {}",
descriptor.image_size / u64::from(descriptor.data_block_size),
)
.unwrap();
// Hash starting block (in units of the hash block size).
write!(
&mut result,
" {}",
descriptor.image_size / u64::from(descriptor.hash_block_size),
)
.unwrap();
// Hash algorithm.
write!(&mut result, " {}", descriptor.hash_algorithm).unwrap();
// Root digest.
write!(&mut result, " {}", &hex::encode(&descriptor.root_digest)).unwrap();
// Salt.
write!(&mut result, " {}", &hex::encode(&descriptor.salt)).unwrap();

// Number of optional arguments.
let num_optional_args = if descriptor.fec_num_roots != 0 { 10 } else { 2 }
+ u8::from(descriptor.flags & HashTreeDescriptor::FLAG_CHECK_AT_MOST_ONCE != 0);
write!(&mut result, " {num_optional_args}").unwrap();

if descriptor.flags & HashTreeDescriptor::FLAG_CHECK_AT_MOST_ONCE != 0 {
// [n + 1] Only check blocks once instead of on each access.
result.push_str(" check_at_most_once");
}

// [0] Corruption handling mode (replaced by bootloader at runtime).
result.push_str(" $(ANDROID_VERITY_MODE)");
// [1] Force return zeros and skip validation for blocks expected to contain
// only zeros.
result.push_str(" ignore_zero_blocks");

if descriptor.fec_num_roots != 0 {
// [2-3] Enable FEC (replaced by bootloader at runtime).
result.push_str(" use_fec_from_device PARTUUID=$(ANDROID_SYSTEM_PARTUUID)");
// [4-5] Number of parity bytes per FEC codeword.
write!(&mut result, " fec_roots {}", descriptor.fec_num_roots).unwrap();

let fec_block_offset = descriptor.fec_offset / u64::from(descriptor.data_block_size);

// [6-7] Number of data blocks covered by FEC.
write!(&mut result, " fec_blocks {fec_block_offset}").unwrap();
// [8-9] Starting block (in data block size units) of FEC.
write!(&mut result, " fec_start {fec_block_offset}").unwrap();
}

// Root filesystem block device.
result.push_str("\" root=/dev/dm-0");

result
}

/// Update the dm-verity kernel command line descriptor to match the hash tree
/// descriptor. This is a no-op if there's no matching existing kernel command
/// line descriptor to update. Returns whether the descriptor was updated.
fn update_dm_verity_cmdline(info: &mut AvbInfo) -> Result<bool> {
assert!(info.footer.is_some(), "Not an appended image");

let new_cmdline = match info.header.appended_descriptor()? {
AppendedDescriptorRef::HashTree(d) => compute_dm_verity_cmdline(d),
AppendedDescriptorRef::Hash(_) => return Ok(false),
};

for d in &mut info.header.descriptors {
if let Descriptor::KernelCmdline(d) = d {
if d.flags & KernelCmdlineDescriptor::FLAG_USE_ONLY_IF_HASHTREE_NOT_DISABLED != 0
&& d.cmdline.starts_with("dm=")
&& d.cmdline != new_cmdline
{
d.cmdline = new_cmdline;
return Ok(true);
}
}
}

Ok(false)
}

/// Sign or clear header signatures based on whether the original header was
/// signed. If the original header was signed and is unchanged, then the
/// original signature is used as-is. If the force option is specified, then
Expand Down Expand Up @@ -481,7 +596,11 @@ fn pack_subcommand(cli: &PackCli, cancel_signal: &AtomicBool) -> Result<()> {
format!("Failed to open raw image for reading: {:?}", cli.input_raw)
})?;

write_raw_and_update(&cli.output, &mut reader, &mut info, cancel_signal)?
let file = write_raw_and_update(&cli.output, &mut reader, &mut info, cancel_signal)?;

update_dm_verity_cmdline(&mut info)?;

file
} else {
File::create(&cli.output)
.map(PSeekFile::new)
Expand Down Expand Up @@ -512,6 +631,8 @@ fn repack_subcommand(cli: &RepackCli, cancel_signal: &AtomicBool) -> Result<()>
d.update(&file, &file, cancel_signal)?;
}

update_dm_verity_cmdline(&mut info)?;

file
} else {
File::create(&cli.output)
Expand Down
8 changes: 8 additions & 0 deletions avbroot/src/format/avb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,9 @@ impl fmt::Debug for HashTreeDescriptor {
}

impl HashTreeDescriptor {
pub const FLAG_DO_NOT_USE_AB: u32 = 1 << 0;
pub const FLAG_CHECK_AT_MOST_ONCE: u32 = 1 << 1;

/// Calculate the hash tree digests for a single level of the tree. If the
/// reader's position is block-aligned and `image_size` is a multiple of the
/// block size, then this function can also be used to calculate the digests
Expand Down Expand Up @@ -1053,6 +1056,11 @@ pub struct KernelCmdlineDescriptor {
pub cmdline: String,
}

impl KernelCmdlineDescriptor {
pub const FLAG_USE_ONLY_IF_HASHTREE_NOT_DISABLED: u32 = 1 << 0;
pub const FLAG_USE_ONLY_IF_HASHTREE_DISABLED: u32 = 1 << 1;
}

impl DescriptorTag for KernelCmdlineDescriptor {
const TAG: u64 = 3;
}
Expand Down