mirror of
https://github.com/chenxiaolong/avbroot.git
synced 2026-07-03 14:05:11 +02:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a7c872be3e | |||
| cf1ab6ecca | |||
| 7364e8d725 | |||
| 935a86e72c | |||
| a9a6107043 | |||
| 3c1d5a8bb3 | |||
| 26ec8098e5 |
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user