Compare commits

..

7 Commits

Author SHA1 Message Date
Andrew Gunnerson a7c872be3e Version 2.3.3
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-11-15 18:59:43 -05:00
Andrew Gunnerson cf1ab6ecca CHANGELOG.md: Add entry for PR #208
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-11-15 18:58:53 -05:00
Andrew Gunnerson 7364e8d725 Merge pull request #208 from chenxiaolong/descriptors
cli/ota: Merge property and kernel cmdline descriptors from child into parent
2023-11-15 18:57:26 -05:00
Andrew Gunnerson 935a86e72c cli/ota: Merge property and kernel cmdline descriptors from child into parent
Some devices use the legacy Android/ChromiumOS-specific `dm=` kernel
command line option to configure dm-verity without userspace helpers.
These options are specified in kernel command line descriptors in the
system partition's vbmeta header, which need to be merged into the
parent vbmeta image for the bootloader to see them.

This commit adds support for merging property descriptors and kernel
command line descriptors. Property descriptors are merged based on an
exact string match of the property key. Kernel command line descriptors
are merged based on the (non-empty) text before the first equal sign.

Issue: #203

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-11-15 18:39:55 -05:00
Andrew Gunnerson a9a6107043 CHANGELOG.md: Add entry for PR #207
Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-11-15 17:46:49 -05:00
Andrew Gunnerson 3c1d5a8bb3 Merge pull request #207 from chenxiaolong/xz
compression: Add support for XZ-compressed ramdisks
2023-11-15 17:11:23 -05:00
Andrew Gunnerson 26ec8098e5 compression: Add support for XZ-compressed ramdisks
`lineage-20.0-20231109-nightly-taimen-signed.zip` is an example of an
OTA that uses XZ-compressed ramdisks.

Issue: #203

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-11-15 16:37:00 -05:00
7 changed files with 167 additions and 53 deletions
+7
View File
@@ -7,6 +7,11 @@
to update the actual links at the bottom of the file.
-->
### Version 2.3.3
* Add support for XZ-compressed ramdisks ([Issue #203], [PR #207])
* Merge property and kernel command line AVB descriptors when replacing partitions ([Issue #203], [PR #208])
### Version 2.3.2
* Improve error messages when using `--replace` with an image that has the wrong AVB descriptor type ([Issue #201], [PR #202])
@@ -159,3 +164,5 @@ Behind-the-scenes changes:
[PR #202]: https://github.com/chenxiaolong/avbroot/pull/202
[PR #205]: https://github.com/chenxiaolong/avbroot/pull/205
[PR #206]: https://github.com/chenxiaolong/avbroot/pull/206
[PR #207]: https://github.com/chenxiaolong/avbroot/pull/207
[PR #208]: https://github.com/chenxiaolong/avbroot/pull/208
Generated
+4 -4
View File
@@ -121,7 +121,7 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "avbroot"
version = "2.3.2"
version = "2.3.3"
dependencies = [
"anyhow",
"assert_matches",
@@ -565,7 +565,7 @@ dependencies = [
[[package]]
name = "e2e"
version = "2.3.2"
version = "2.3.3"
dependencies = [
"anyhow",
"attohttpc",
@@ -679,7 +679,7 @@ dependencies = [
[[package]]
name = "fuzz"
version = "2.3.2"
version = "2.3.3"
dependencies = [
"avbroot",
"honggfuzz",
@@ -2104,7 +2104,7 @@ dependencies = [
[[package]]
name = "xtask"
version = "2.3.2"
version = "2.3.3"
dependencies = [
"anyhow",
"clap",
+1 -1
View File
@@ -4,7 +4,7 @@ members = ["avbroot", "e2e", "fuzz", "xtask"]
resolver = "2"
[workspace.package]
version = "2.3.2"
version = "2.3.3"
license = "GPL-3.0-only"
edition = "2021"
repository = "https://github.com/chenxiaolong/avbroot"
+124 -42
View File
@@ -400,6 +400,128 @@ fn get_vbmeta_patch_order(
Ok(order)
}
/// Copy the hash or hashtree descriptor from the child image header into the
/// parent image header if the child is unsigned or update the parent's chain
/// descriptor if the child is signed. The existing descriptor in the parent
/// must have the same type as the child.
fn update_security_descriptors(
parent_header: &mut Header,
child_header: &Header,
parent_name: &str,
child_name: &str,
) -> Result<()> {
// This can't fail since the descriptor must have existed for the dependency
// to exist.
let parent_descriptor = parent_header
.descriptors
.iter_mut()
.find(|d| d.partition_name() == Some(child_name))
.unwrap();
let parent_type = parent_descriptor.type_name();
if child_header.public_key.is_empty() {
// vbmeta is unsigned. Copy the child's existing descriptor.
let Some(child_descriptor) = child_header
.descriptors
.iter()
.find(|d| d.partition_name() == Some(child_name))
else {
bail!("{child_name} has no descriptor for itself");
};
let child_type = child_descriptor.type_name();
match (parent_descriptor, child_descriptor) {
(Descriptor::Hash(pd), Descriptor::Hash(cd)) => {
*pd = cd.clone();
}
(Descriptor::HashTree(pd), Descriptor::HashTree(cd)) => {
*pd = cd.clone();
}
_ => {
bail!("{child_name} descriptor ({child_type}) does not match entry in {parent_name} ({parent_type})");
}
}
} else {
// vbmeta is signed; Use a chain descriptor.
match parent_descriptor {
Descriptor::ChainPartition(pd) => {
pd.public_key = child_header.public_key.clone();
}
_ => {
bail!("{child_name} descriptor ({parent_type}) in {parent_name} must be a chain descriptor");
}
}
}
Ok(())
}
/// Get the text before the first equal sign in the kernel command line if it is
/// not empty.
fn cmdline_prefix(cmdline: &str) -> Option<&str> {
let Some((prefix, _)) = cmdline.split_once('=') else {
return None;
};
if prefix.is_empty() {
return None;
}
Some(prefix)
}
/// Merge property descriptors and kernel command line descriptors from the
/// child into the parent. The property descriptors are matched based on the
/// entire property key. The kernel command line descriptors are matched based
/// on the non-empty text left of the first equal sign (if it exists).
///
/// This is a no-op if the child is signed because it is expected to be chain
/// loaded by the parent.
fn update_metadata_descriptors(parent_header: &mut Header, child_header: &Header) {
if !child_header.public_key.is_empty() {
return;
}
for child_descriptor in &child_header.descriptors {
match child_descriptor {
Descriptor::Property(cd) => {
let parent_property = parent_header.descriptors.iter_mut().find_map(|d| match d {
Descriptor::Property(p) if p.key == cd.key => Some(p),
_ => None,
});
if let Some(pd) = parent_property {
pd.value = cd.value.clone();
} else {
parent_header
.descriptors
.push(Descriptor::Property(cd.clone()));
}
}
Descriptor::KernelCmdline(cd) => {
let Some(prefix) = cmdline_prefix(&cd.cmdline) else {
continue;
};
let parent_property = parent_header.descriptors.iter_mut().find_map(|d| match d {
Descriptor::KernelCmdline(p) if cmdline_prefix(&p.cmdline) == Some(prefix) => {
Some(p)
}
_ => None,
});
if let Some(pd) = parent_property {
pd.cmdline = cd.cmdline.clone();
} else {
parent_header
.descriptors
.push(Descriptor::KernelCmdline(cd.clone()));
}
}
_ => {}
}
}
}
/// Update vbmeta headers.
///
/// * If [`Header::flags`] is non-zero, then an error is returned because the
@@ -438,52 +560,12 @@ fn update_vbmeta_headers(
}
for dep in deps.iter() {
// This can't fail since the descriptor must have existed for the
// dependency to exist.
let parent_descriptor = parent_header
.descriptors
.iter_mut()
.find(|d| d.partition_name() == Some(dep))
.unwrap();
let reader = images.get_mut(dep).unwrap();
let (header, _, _) = avb::load_image(reader)
.with_context(|| format!("Failed to load vbmeta footer from image: {dep}"))?;
let pd_type = parent_descriptor.type_name();
if header.public_key.is_empty() {
// vbmeta is unsigned. Use the existing descriptor.
let Some(descriptor) = header
.descriptors
.iter()
.find(|d| d.partition_name() == Some(dep))
else {
bail!("{name} has no descriptor for itself");
};
let d_type = descriptor.type_name();
match (parent_descriptor, descriptor) {
(Descriptor::Hash(pd), Descriptor::Hash(d)) => {
*pd = d.clone();
}
(Descriptor::HashTree(pd), Descriptor::HashTree(d)) => {
*pd = d.clone();
}
_ => {
bail!("{dep} descriptor ({d_type}) does not match entry in {name} ({pd_type})");
}
}
} else {
// vbmeta is signed; Use a chain descriptor.
match parent_descriptor {
Descriptor::ChainPartition(d) => {
d.public_key = header.public_key;
}
_ => {
bail!("{dep} descriptor ({pd_type}) in {name} must be a chain descriptor");
}
}
}
update_security_descriptors(parent_header, &header, name, dep)?;
update_metadata_descriptors(parent_header, &header);
}
// Only sign and rewrite the image if we need to. Some vbmeta images may
+27 -2
View File
@@ -10,14 +10,22 @@ use flate2::{read::GzDecoder, write::GzEncoder, Compression};
use lz4_flex::frame::FrameDecoder;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use xz2::{
read::XzDecoder,
stream::{Check, Stream},
write::XzEncoder,
};
static GZIP_MAGIC: &[u8; 2] = b"\x1f\x8b";
static LZ4_LEGACY_MAGIC: &[u8; 4] = b"\x02\x21\x4c\x18";
static XZ_MAGIC: &[u8; 6] = b"\xfd\x37\x7a\x58\x5a\x00";
#[derive(Debug, Error)]
pub enum Error {
#[error("Unknown compression format")]
UnknownFormat,
#[error("XZ stream error")]
XzStream(#[from] xz2::stream::Error),
#[error("I/O error")]
Io(#[from] io::Error),
}
@@ -102,25 +110,29 @@ pub enum CompressedFormat {
None,
Gzip,
Lz4Legacy,
Xz,
}
pub enum CompressedReader<R: Read> {
None(R),
Gzip(GzDecoder<R>),
Lz4(FrameDecoder<R>),
Xz(XzDecoder<R>),
}
impl<R: Read + Seek> CompressedReader<R> {
pub fn new(mut reader: R, raw_if_unknown: bool) -> Result<Self> {
let mut magic = [0u8; 4];
let mut magic = [0u8; 6];
reader.read_exact(&mut magic)?;
reader.rewind()?;
if &magic[0..2] == GZIP_MAGIC {
Ok(Self::Gzip(GzDecoder::new(reader)))
} else if &magic == LZ4_LEGACY_MAGIC {
} else if &magic[0..4] == LZ4_LEGACY_MAGIC {
Ok(Self::Lz4(FrameDecoder::new(reader)))
} else if &magic == XZ_MAGIC {
Ok(Self::Xz(XzDecoder::new(reader)))
} else if raw_if_unknown {
Ok(Self::None(reader))
} else {
@@ -133,6 +145,7 @@ impl<R: Read + Seek> CompressedReader<R> {
Self::None(_) => CompressedFormat::None,
Self::Gzip(_) => CompressedFormat::Gzip,
Self::Lz4(_) => CompressedFormat::Lz4Legacy,
Self::Xz(_) => CompressedFormat::Xz,
}
}
@@ -141,6 +154,7 @@ impl<R: Read + Seek> CompressedReader<R> {
Self::None(r) => r,
Self::Gzip(r) => r.into_inner(),
Self::Lz4(r) => r.into_inner(),
Self::Xz(r) => r.into_inner(),
}
}
}
@@ -151,6 +165,7 @@ impl<R: Read> Read for CompressedReader<R> {
Self::None(r) => r.read(buf),
Self::Gzip(r) => r.read(buf),
Self::Lz4(r) => r.read(buf),
Self::Xz(r) => r.read(buf),
}
}
}
@@ -159,6 +174,7 @@ pub enum CompressedWriter<W: Write> {
None(W),
Gzip(GzEncoder<W>),
Lz4Legacy(Lz4LegacyEncoder<W>),
Xz(XzEncoder<W>),
}
impl<W: Write> CompressedWriter<W> {
@@ -169,6 +185,11 @@ impl<W: Write> CompressedWriter<W> {
Ok(Self::Gzip(GzEncoder::new(writer, Compression::default())))
}
CompressedFormat::Lz4Legacy => Ok(Self::Lz4Legacy(Lz4LegacyEncoder::new(writer)?)),
CompressedFormat::Xz => {
// Some kernels are compiled without support for the default CRC64.
let stream = Stream::new_easy_encoder(6, Check::Crc32)?;
Ok(Self::Xz(XzEncoder::new_stream(writer, stream)))
}
}
}
@@ -177,6 +198,7 @@ impl<W: Write> CompressedWriter<W> {
Self::None(_) => CompressedFormat::None,
Self::Gzip(_) => CompressedFormat::Gzip,
Self::Lz4Legacy(_) => CompressedFormat::Lz4Legacy,
Self::Xz(_) => CompressedFormat::Xz,
}
}
@@ -185,6 +207,7 @@ impl<W: Write> CompressedWriter<W> {
Self::None(w) => Ok(w),
Self::Gzip(w) => w.finish(),
Self::Lz4Legacy(w) => w.finish(),
Self::Xz(w) => w.finish(),
}
}
}
@@ -195,6 +218,7 @@ impl<W: Write> Write for CompressedWriter<W> {
Self::None(w) => w.write(buf),
Self::Gzip(w) => w.write(buf),
Self::Lz4Legacy(w) => w.write(buf),
Self::Xz(w) => w.write(buf),
}
}
@@ -203,6 +227,7 @@ impl<W: Write> Write for CompressedWriter<W> {
Self::None(w) => w.flush(),
Self::Gzip(w) => w.flush(),
Self::Lz4Legacy(w) => w.flush(),
Self::Xz(w) => w.flush(),
}
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
id=com.chiller3.avbroot.clearotacerts
name=clearotacerts
version=v2.3.2
versionCode=131842
version=v2.3.3
versionCode=131843
author=chenxiaolong
description=Block A/B OTAs by clearing verification certificates
+2 -2
View File
@@ -1,6 +1,6 @@
id=com.chiller3.avbroot.oemunlockonboot
name=oemunlockonboot
version=v2.3.2
versionCode=131842
version=v2.3.3
versionCode=131843
author=chenxiaolong
description=Enable OEM unlocking on every boot