mirror of
https://github.com/chenxiaolong/avbroot.git
synced 2026-07-03 14:05:11 +02:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f9343aa542 | |||
| 0391d4e2c3 | |||
| c18800dc44 | |||
| 2db90f83c2 | |||
| 34915e256f | |||
| 61392eb9d6 | |||
| 029cb4264e | |||
| 98a7fc811d | |||
| f4f4ec8e0d | |||
| 99b316f55e | |||
| 50ee90b61a | |||
| 2e8bd9f9d4 | |||
| 3fa96714c4 | |||
| 7a2530a199 | |||
| f84df86ef5 | |||
| 0c064981dd | |||
| 5645183ecc | |||
| bc7358a8d9 | |||
| d47c14ab12 |
@@ -7,6 +7,16 @@
|
||||
to update the actual links at the bottom of the file.
|
||||
-->
|
||||
|
||||
### Version 3.2.2
|
||||
|
||||
* Add new `--recompute-size` option to `avbroot avb pack` to automatically recompute the image size for resizable images ([Discussion #294], [PR #296])
|
||||
* Add new `--output-info` option to `avbroot avb pack` to write a new `avb.toml` file containing computed values ([PR #297])
|
||||
* Add support for upcoming Magisk Canary 27003 ([Issue #301], [PR #268])
|
||||
|
||||
### Version 3.2.1
|
||||
|
||||
* Increase hash tree and FEC size limits to accommodate partition images up to 8 GiB ([Issue #291], [PR #293])
|
||||
|
||||
### Version 3.2.0
|
||||
|
||||
* Fix potential infinite loop when interrupting avbroot at the right moment to a bug in the bzip2-rs library ([Issue #285], [PR #287])
|
||||
@@ -177,6 +187,7 @@ Behind-the-scenes changes:
|
||||
[Discussion #195]: https://github.com/chenxiaolong/avbroot/discussions/195
|
||||
[Discussion #235]: https://github.com/chenxiaolong/avbroot/discussions/235
|
||||
[Discussion #286]: https://github.com/chenxiaolong/avbroot/discussions/286
|
||||
[Discussion #294]: https://github.com/chenxiaolong/avbroot/discussions/294
|
||||
[Issue #138]: https://github.com/chenxiaolong/avbroot/issues/138
|
||||
[Issue #144]: https://github.com/chenxiaolong/avbroot/issues/144
|
||||
[Issue #145]: https://github.com/chenxiaolong/avbroot/issues/145
|
||||
@@ -194,6 +205,8 @@ Behind-the-scenes changes:
|
||||
[Issue #265]: https://github.com/chenxiaolong/avbroot/issues/265
|
||||
[Issue #278]: https://github.com/chenxiaolong/avbroot/issues/278
|
||||
[Issue #285]: https://github.com/chenxiaolong/avbroot/issues/285
|
||||
[Issue #291]: https://github.com/chenxiaolong/avbroot/issues/291
|
||||
[Issue #301]: https://github.com/chenxiaolong/avbroot/issues/301
|
||||
[PR #130]: https://github.com/chenxiaolong/avbroot/pull/130
|
||||
[PR #132]: https://github.com/chenxiaolong/avbroot/pull/132
|
||||
[PR #133]: https://github.com/chenxiaolong/avbroot/pull/133
|
||||
@@ -272,9 +285,13 @@ Behind-the-scenes changes:
|
||||
[PR #256]: https://github.com/chenxiaolong/avbroot/pull/256
|
||||
[PR #257]: https://github.com/chenxiaolong/avbroot/pull/257
|
||||
[PR #261]: https://github.com/chenxiaolong/avbroot/pull/261
|
||||
[PR #268]: https://github.com/chenxiaolong/avbroot/pull/268
|
||||
[PR #276]: https://github.com/chenxiaolong/avbroot/pull/276
|
||||
[PR #277]: https://github.com/chenxiaolong/avbroot/pull/277
|
||||
[PR #279]: https://github.com/chenxiaolong/avbroot/pull/279
|
||||
[PR #287]: https://github.com/chenxiaolong/avbroot/pull/287
|
||||
[PR #288]: https://github.com/chenxiaolong/avbroot/pull/288
|
||||
[PR #289]: https://github.com/chenxiaolong/avbroot/pull/289
|
||||
[PR #293]: https://github.com/chenxiaolong/avbroot/pull/293
|
||||
[PR #296]: https://github.com/chenxiaolong/avbroot/pull/296
|
||||
[PR #297]: https://github.com/chenxiaolong/avbroot/pull/297
|
||||
|
||||
Generated
+4
-4
@@ -109,7 +109,7 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||
|
||||
[[package]]
|
||||
name = "avbroot"
|
||||
version = "3.2.0"
|
||||
version = "3.2.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_matches",
|
||||
@@ -534,7 +534,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "e2e"
|
||||
version = "3.2.0"
|
||||
version = "3.2.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"avbroot",
|
||||
@@ -628,7 +628,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "fuzz"
|
||||
version = "3.2.0"
|
||||
version = "3.2.2"
|
||||
dependencies = [
|
||||
"avbroot",
|
||||
"honggfuzz",
|
||||
@@ -2068,7 +2068,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "3.2.0"
|
||||
version = "3.2.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ members = ["avbroot", "e2e", "fuzz", "xtask"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "3.2.0"
|
||||
version = "3.2.2"
|
||||
license = "GPL-3.0-only"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/chenxiaolong/avbroot"
|
||||
|
||||
@@ -32,6 +32,10 @@ This subcommand packs a new AVB image from the `avb.toml` file and, for appended
|
||||
|
||||
Note that if the image is an appended image and its hash or hash tree descriptor uses an insecure algorithm, like `sha1`, then it will automatically be promoted to `sha256`.
|
||||
|
||||
By default, for appended vbmeta images, the output image size will match the size of the original image that was unpacked. This size is specified by the `image_size` field in `avb.toml`. If the image is resizable (eg. `system`), then passing in `--recompute-size` will cause the `image_size` field to be ignored and the smallest possible output file that fits the raw image and AVB metadata will be built. This avoids wasting space if `raw.img` shrunk or allows the packing to work at all if `raw.img` grew. **Do not use this option for non-resizable images** (eg. `boot`) or else the device won't be able to boot.
|
||||
|
||||
When packing an image, several of the fields in `avb.toml` may potentially be recomputed. To write a TOML file containing the new values, use `--output-info <output TOML>`. It is safe to overwrite the existing `avb.toml` if desired.
|
||||
|
||||
### Repacking an AVB image
|
||||
|
||||
```bash
|
||||
|
||||
@@ -15,9 +15,9 @@ Having a good understanding of how AVB and A/B OTAs work is recommended prior to
|
||||
* `payload.bin` exists
|
||||
* `META-INF/com/android/metadata` (Android 10-11) or `META-INF/com/android/metadata.pb` (Android 12+) exists
|
||||
|
||||
* The device must support using a custom public key for the bootloader's root of trust. This is normally done via the `fastboot flash avb_custom_key` command. All Pixel devices with unlockable bootloaders since the Pixel 2 support this. Other devices may support it as well, but there's no easy way to check without just trying it.
|
||||
* The device must support using a custom public key for the bootloader's root of trust. This is normally done via the `fastboot flash avb_custom_key` command.
|
||||
|
||||
* NOTE: Some OnePlus devices have a broken implementation where a custom public key can be set, but the device won't boot despite having proper signatures. Downgrading the bootloader to the version shipped with Android 11 might potentially help. This problem has been reported across multiple OnePlus models ([#186](https://github.com/chenxiaolong/avbroot/issues/186), [#195](https://github.com/chenxiaolong/avbroot/discussions/195), [#212](https://github.com/chenxiaolong/avbroot/issues/212)).
|
||||
A list of devices known to work can be found in the issue tracker at [#299](https://github.com/chenxiaolong/avbroot/issues/299).
|
||||
|
||||
## Patches
|
||||
|
||||
|
||||
+2
-2
@@ -13,9 +13,9 @@ avbroot – это программа для модификации OTA-обра
|
||||
* наличие файла `payload.bin` (обычно находится в корне архива)
|
||||
* наличие файла `META-INF/com/android/metadata` (Android 10-11) или `META-INF/com/android/metadata.pb` (Android 12+)
|
||||
|
||||
* Устройство должно поддерживать установку пользовательского публичного ключа для подтверждения статуса доверия загрузчика. Обычно это делается с помощью команды `fastboot flash avb_custom_key`. Все устройства Pixel с разблокируемым загрузчиком, начиная с Pixel 2, поддерживают эту функцию. Другие устройства тоже могут поддерживать её, но достоверно убедиться в этом можно только проверив лично.
|
||||
* Устройство должно поддерживать установку пользовательского публичного ключа для подтверждения статуса доверия загрузчика. Обычно это производится с помощью команды `fastboot flash avb_custom_key`.
|
||||
|
||||
* ПРИМЕЧАНИЕ: Некоторые девайсы от OnePlus имеют некорректную реализацию, где возможна установка кастомного публичного ключа, но устройство все равно не будет загружаться, несмотря на наличие корректной подписи. Понижение загрузчика до версии, поставляемой вместе с Android 11, потенциально может помочь. Уже сообщалось о наличии этой проблемы на нескольких устройствах OnePlus ([#186,](https://github.com/chenxiaolong/avbroot/issues/186) [#195,](https://github.com/chenxiaolong/avbroot/discussions/195) [#212](https://github.com/chenxiaolong/avbroot/issues/212)).
|
||||
Список девайсов, на которых проверялась совместимость с указанным выше функционалом, находится здесь: [#299.](https://github.com/chenxiaolong/avbroot/issues/299)
|
||||
|
||||
## Патчи
|
||||
|
||||
|
||||
+34
-8
@@ -57,16 +57,22 @@ fn read_avb_image(path: &Path) -> Result<(AvbInfo, BufReader<File>)> {
|
||||
Ok((info, reader))
|
||||
}
|
||||
|
||||
fn write_avb_image(file: PSeekFile, info: &mut AvbInfo) -> Result<()> {
|
||||
fn write_avb_image(file: PSeekFile, info: &mut AvbInfo, recompute_size: bool) -> Result<()> {
|
||||
let mut writer = BufWriter::new(file);
|
||||
|
||||
if let Some(f) = &mut info.footer {
|
||||
avb::write_appended_image(&mut writer, &info.header, f, info.image_size)
|
||||
.context("Failed to write appended AVB image")?;
|
||||
info.image_size = if let Some(f) = &mut info.footer {
|
||||
let image_size = if recompute_size {
|
||||
None
|
||||
} else {
|
||||
Some(info.image_size)
|
||||
};
|
||||
|
||||
avb::write_appended_image(&mut writer, &info.header, f, image_size)
|
||||
.context("Failed to write appended AVB image")?
|
||||
} else {
|
||||
avb::write_root_image(&mut writer, &info.header, 4096)
|
||||
.context("Failed to write root AVB image")?;
|
||||
}
|
||||
.context("Failed to write root AVB image")?
|
||||
};
|
||||
|
||||
writer.flush().context("Failed to flush writes")?;
|
||||
|
||||
@@ -625,12 +631,16 @@ fn pack_subcommand(cli: &PackCli, cancel_signal: &AtomicBool) -> Result<()> {
|
||||
|
||||
sign_or_clear(&mut info, &orig_header, &cli.key)?;
|
||||
|
||||
write_avb_image(file, &mut info)?;
|
||||
write_avb_image(file, &mut info, cli.recompute_size)?;
|
||||
|
||||
// We display the info at the very end after both the header and footer are
|
||||
// updated so that incorrect/incomplete information isn't shown.
|
||||
display_info(&cli.display, &info);
|
||||
|
||||
if let Some(path) = &cli.output_info {
|
||||
write_info(path, &info)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -659,7 +669,7 @@ fn repack_subcommand(cli: &RepackCli, cancel_signal: &AtomicBool) -> Result<()>
|
||||
|
||||
sign_or_clear(&mut info, &orig_header, &cli.key)?;
|
||||
|
||||
write_avb_image(file, &mut info)?;
|
||||
write_avb_image(file, &mut info, false)?;
|
||||
|
||||
// We display the info at the very end after both the header and footer are
|
||||
// updated so that incorrect/incomplete information isn't shown.
|
||||
@@ -827,12 +837,28 @@ struct PackCli {
|
||||
#[arg(long, value_name = "FILE", value_parser, default_value = "avb.toml")]
|
||||
input_info: PathBuf,
|
||||
|
||||
/// Path to output AVB info TOML.
|
||||
///
|
||||
/// If specified, the AVB info containing all recomputed fields will be
|
||||
/// written to this file. This can point to the same file as --input-info.
|
||||
#[arg(long, value_name = "FILE", value_parser)]
|
||||
output_info: Option<PathBuf>,
|
||||
|
||||
/// Path to input raw image.
|
||||
///
|
||||
/// Appended AVB images require a raw image.
|
||||
#[arg(long, value_name = "FILE", value_parser, default_value = "raw.img")]
|
||||
input_raw: PathBuf,
|
||||
|
||||
/// Recompute image size.
|
||||
///
|
||||
/// By default, it is assumed that the image has a fixed size specified by
|
||||
/// the image_size top-level field in the AVB info TOML. If flag is passed,
|
||||
/// then that field is ignored and the smallest possible output image will
|
||||
/// be created.
|
||||
#[arg(long)]
|
||||
recompute_size: bool,
|
||||
|
||||
#[command(flatten)]
|
||||
key: KeyGroup,
|
||||
|
||||
|
||||
+112
-95
@@ -1,10 +1,10 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Andrew Gunnerson
|
||||
* SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
use std::{
|
||||
cmp, fmt,
|
||||
fmt,
|
||||
io::{self, Cursor, Read, Seek, SeekFrom, Write},
|
||||
ops::Range,
|
||||
str,
|
||||
@@ -29,8 +29,8 @@ use crate::{
|
||||
padding,
|
||||
},
|
||||
stream::{
|
||||
self, CountingReader, FromReader, ReadDiscardExt, ReadSeekReopen, ReadStringExt, ToWriter,
|
||||
WriteSeekReopen, WriteStringExt, WriteZerosExt,
|
||||
self, CountingReader, CountingWriter, FromReader, ReadDiscardExt, ReadSeekReopen,
|
||||
ReadStringExt, ToWriter, WriteSeekReopen, WriteStringExt, WriteZerosExt,
|
||||
},
|
||||
util,
|
||||
};
|
||||
@@ -50,13 +50,30 @@ pub const FOOTER_MAGIC: [u8; 4] = *b"AVBf";
|
||||
/// for early fail. No individual field can actually be this size.
|
||||
pub const HEADER_MAX_SIZE: u64 = 64 * 1024;
|
||||
|
||||
/// Maximum hash tree size. The current limit equals the hash tree size for a
|
||||
/// 4GiB image using SHA512 digests and a block size of 4096.
|
||||
pub const HASH_TREE_MAX_SIZE: u64 = 68_177_920;
|
||||
/// Maximum hash tree size. The current limit equals the hash tree size for an
|
||||
/// 8GiB image using SHA256 digests and a block size of 4096. This is equal to:
|
||||
///
|
||||
/// ```rust
|
||||
/// use avbroot::format::hashtree::HashTree;
|
||||
/// let size = HashTree::new(4096, &ring::digest::SHA256, b"")
|
||||
/// .compute_level_offsets(8 * 1024 * 1024 * 1024)
|
||||
/// .unwrap()
|
||||
/// .first()
|
||||
/// .map(|r| r.end)
|
||||
/// .unwrap_or(0);
|
||||
/// ```
|
||||
pub const HASH_TREE_MAX_SIZE: u64 = 67_637_248;
|
||||
|
||||
/// Maximum FEC data size. The current limit equals the FEC data size for a 4GiB
|
||||
/// image using 2 parity bytes per codeword.
|
||||
pub const FEC_DATA_MAX_SIZE: u64 = 33_959_936;
|
||||
/// Maximum FEC data size. The current limit equals the FEC data size for an
|
||||
/// 8GiB image using 2 parity bytes per codeword. This is equal to:
|
||||
///
|
||||
/// ```rust
|
||||
/// use avbroot::format::fec::Fec;
|
||||
/// let size = Fec::new(8 * 1024 * 1024 * 1024, 4096, 2)
|
||||
/// .unwrap()
|
||||
/// .fec_size();
|
||||
/// ```
|
||||
pub const FEC_DATA_MAX_SIZE: u64 = 67_911_680;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
@@ -107,8 +124,10 @@ pub enum Error {
|
||||
RsaSign(#[source] rsa::Error),
|
||||
#[error("Failed to RSA verify signature")]
|
||||
RsaVerify(#[source] rsa::Error),
|
||||
#[error("{0} byte image size is too small to fit header or footer")]
|
||||
ImageSizeTooSmall(u64),
|
||||
#[error("{0} byte image size is too small to fit header")]
|
||||
TooSmallForHeader(u64),
|
||||
#[error("{0} byte image size is too small to fit footer")]
|
||||
TooSmallForFooter(u64),
|
||||
#[error("Hash tree error")]
|
||||
HashTree(#[from] hashtree::Error),
|
||||
#[error("FEC error")]
|
||||
@@ -1834,97 +1853,95 @@ pub fn load_image(mut reader: impl Read + Seek) -> Result<(Header, Option<Footer
|
||||
Ok((header, footer, image_size))
|
||||
}
|
||||
|
||||
/// Write a vbmeta header to the specified writer. If a footer is specified, it
|
||||
/// will be used as the basis of the newly written footer, with the original
|
||||
/// image size, vbmeta header offset, and vbmeta header size fields updated
|
||||
/// appropriately.
|
||||
///
|
||||
/// The writer must not have an existing vbmeta header or footer.
|
||||
fn write_image_internal(
|
||||
mut writer: impl Write + Seek,
|
||||
header: &Header,
|
||||
footer: Option<&mut Footer>,
|
||||
image_size: Option<u64>,
|
||||
block_size: u64,
|
||||
) -> Result<()> {
|
||||
let eof_image_size = if footer.is_some() {
|
||||
match header.appended_descriptor()? {
|
||||
AppendedDescriptorRef::HashTree(d) => d
|
||||
.image_size
|
||||
.checked_add(d.tree_size)
|
||||
.and_then(|s| s.checked_add(d.fec_size))
|
||||
.ok_or_else(|| Error::FieldOutOfBounds("eof_image_size"))?,
|
||||
AppendedDescriptorRef::Hash(d) => d.image_size,
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
writer.seek(SeekFrom::Start(eof_image_size))?;
|
||||
|
||||
// The header must be block-aligned.
|
||||
let vbmeta_offset = if block_size > 0 {
|
||||
let padding_size = padding::write_zeros(&mut writer, block_size)?;
|
||||
eof_image_size
|
||||
.checked_add(padding_size)
|
||||
.ok_or_else(|| Error::FieldOutOfBounds("vbmeta_offset"))?
|
||||
} else {
|
||||
eof_image_size
|
||||
};
|
||||
|
||||
header.to_writer(&mut writer)?;
|
||||
let vbmeta_end = writer.stream_position()?;
|
||||
|
||||
if let Some(s) = image_size {
|
||||
let footer_space = if footer.is_some() {
|
||||
cmp::max(block_size, Footer::SIZE as u64)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if s < footer_space || vbmeta_end > s - footer_space {
|
||||
return Err(Error::ImageSizeTooSmall(s));
|
||||
}
|
||||
}
|
||||
|
||||
if block_size > 0 {
|
||||
padding::write_zeros(&mut writer, block_size)?;
|
||||
}
|
||||
|
||||
if let Some(f) = footer {
|
||||
let footer_offset = image_size.unwrap() - Footer::SIZE as u64;
|
||||
writer.seek(SeekFrom::Start(footer_offset))?;
|
||||
|
||||
let original_image_size = match header.appended_descriptor()? {
|
||||
AppendedDescriptorRef::HashTree(d) => d.image_size,
|
||||
AppendedDescriptorRef::Hash(d) => d.image_size,
|
||||
};
|
||||
|
||||
f.original_image_size = original_image_size;
|
||||
f.vbmeta_offset = vbmeta_offset;
|
||||
f.vbmeta_size = vbmeta_end - vbmeta_offset;
|
||||
|
||||
f.to_writer(&mut writer)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write a vbmeta header to the specified writer. This is meant for writing
|
||||
/// vbmeta partition images, not appended vbmeta images. The writer must refer
|
||||
/// to an empty file.
|
||||
pub fn write_root_image(writer: impl Write + Seek, header: &Header, block_size: u64) -> Result<()> {
|
||||
write_image_internal(writer, header, None, None, block_size)
|
||||
/// to an empty file. Returns the size of the new file.
|
||||
pub fn write_root_image(writer: impl Write, header: &Header, block_size: u64) -> Result<u64> {
|
||||
let mut counting_writer = CountingWriter::new(writer);
|
||||
|
||||
header.to_writer(&mut counting_writer)?;
|
||||
padding::write_zeros(&mut counting_writer, block_size)?;
|
||||
|
||||
Ok(counting_writer.stream_position()?)
|
||||
}
|
||||
|
||||
/// Write a vbmeta header and footer to the specified writer. This is meant for
|
||||
/// appending vbmeta data to existing partition data, not writing vbmeta images.
|
||||
/// If `image_size` is specified, then the writer is guaranteed to not grow
|
||||
/// past that size and an error is returned if the header and footer won't fit.
|
||||
/// Otherwise, the writer will grow to the necessary size. Returns the size of
|
||||
/// the new file.
|
||||
pub fn write_appended_image(
|
||||
writer: impl Write + Seek,
|
||||
mut writer: impl Write + Seek,
|
||||
header: &Header,
|
||||
footer: &mut Footer,
|
||||
image_size: u64,
|
||||
) -> Result<()> {
|
||||
image_size: Option<u64>,
|
||||
) -> Result<u64> {
|
||||
// avbtool hardcodes a 4096 block size for appended non-sparse images.
|
||||
write_image_internal(writer, header, Some(footer), Some(image_size), 4096)
|
||||
const BLOCK_SIZE: u64 = 4096;
|
||||
|
||||
// Logical image size, excluding the AVB header and footer.
|
||||
let logical_image_size = match header.appended_descriptor()? {
|
||||
AppendedDescriptorRef::HashTree(d) => d
|
||||
.image_size
|
||||
.checked_add(d.tree_size)
|
||||
.and_then(|s| s.checked_add(d.fec_size))
|
||||
.ok_or_else(|| Error::FieldOutOfBounds("logical_image_size"))?,
|
||||
AppendedDescriptorRef::Hash(d) => d.image_size,
|
||||
};
|
||||
|
||||
writer.seek(SeekFrom::Start(logical_image_size))?;
|
||||
|
||||
// The header start offset must be block aligned.
|
||||
let header_offset = {
|
||||
let padding_size = padding::write_zeros(&mut writer, BLOCK_SIZE)?;
|
||||
logical_image_size
|
||||
.checked_add(padding_size)
|
||||
.ok_or_else(|| Error::FieldOutOfBounds("header_offset"))?
|
||||
};
|
||||
|
||||
// The header lives at the beginning of the empty space.
|
||||
let mut header_buf = Cursor::new(Vec::new());
|
||||
header.to_writer(&mut header_buf)?;
|
||||
let header_size = header_buf.stream_position()?;
|
||||
let header_padding = padding::write_zeros(&mut header_buf, BLOCK_SIZE)?;
|
||||
let header_end_padded = header_offset
|
||||
.checked_add(header_size)
|
||||
.and_then(|s| s.checked_add(header_padding))
|
||||
.ok_or_else(|| Error::FieldOutOfBounds("header_end_padded"))?;
|
||||
|
||||
if let Some(s) = image_size {
|
||||
if header_end_padded > s {
|
||||
return Err(Error::TooSmallForHeader(s));
|
||||
}
|
||||
}
|
||||
|
||||
writer.write_all(&header_buf.into_inner())?;
|
||||
|
||||
// The footer lives in its own separate block at the end of the empty space.
|
||||
let footer_end = if let Some(s) = image_size {
|
||||
if s - header_end_padded < BLOCK_SIZE {
|
||||
return Err(Error::TooSmallForFooter(s));
|
||||
}
|
||||
|
||||
s
|
||||
} else {
|
||||
header_end_padded
|
||||
.checked_add(BLOCK_SIZE)
|
||||
.ok_or_else(|| Error::FieldOutOfBounds("footer_end"))?
|
||||
};
|
||||
|
||||
let footer_offset = footer_end - Footer::SIZE as u64;
|
||||
writer.seek(SeekFrom::Start(footer_offset))?;
|
||||
|
||||
footer.original_image_size = match header.appended_descriptor()? {
|
||||
AppendedDescriptorRef::HashTree(d) => d.image_size,
|
||||
AppendedDescriptorRef::Hash(d) => d.image_size,
|
||||
};
|
||||
footer.vbmeta_offset = header_offset;
|
||||
footer.vbmeta_size = header_size;
|
||||
|
||||
footer.to_writer(&mut writer)?;
|
||||
|
||||
Ok(footer_end)
|
||||
}
|
||||
|
||||
@@ -166,8 +166,8 @@ impl Fec {
|
||||
return Err(Error::UnsupportedParity(parity));
|
||||
}
|
||||
|
||||
let blocks = util::div_ceil(file_size, u64::from(block_size));
|
||||
let rounds = util::div_ceil(blocks, u64::from(rs_k));
|
||||
let blocks = file_size.div_ceil(u64::from(block_size));
|
||||
let rounds = blocks.div_ceil(u64::from(rs_k));
|
||||
|
||||
// Check upfront so we don't need to do checked multiplication later.
|
||||
rounds
|
||||
@@ -196,7 +196,7 @@ impl Fec {
|
||||
|
||||
/// Get the size of the FEC data needed to cover the entire file.
|
||||
#[inline]
|
||||
fn fec_size(&self) -> usize {
|
||||
pub fn fec_size(&self) -> usize {
|
||||
usize::from(self.parity()) * self.rounds as usize * self.block_size as usize
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ impl Fec {
|
||||
let end_block = if range.end % block_size == 0 {
|
||||
range.end / block_size
|
||||
} else {
|
||||
util::div_ceil(range.end, block_size)
|
||||
range.end.div_ceil(block_size)
|
||||
};
|
||||
|
||||
for block in start_block..end_block {
|
||||
|
||||
@@ -72,14 +72,14 @@ impl HashTree {
|
||||
/// tree data. The items are returned with the bottom level's offsets first
|
||||
/// in the list. Note that the bottom level is stored at the end of the hash
|
||||
/// tree data.
|
||||
fn compute_level_offsets(&self, image_size: u64) -> Result<Vec<Range<usize>>> {
|
||||
pub fn compute_level_offsets(&self, image_size: u64) -> Result<Vec<Range<usize>>> {
|
||||
let algorithm = self.salted_context.algorithm();
|
||||
let digest_size = algorithm.output_len().next_power_of_two();
|
||||
let mut ranges = vec![];
|
||||
let mut level_size = image_size;
|
||||
|
||||
while level_size > u64::from(self.block_size) {
|
||||
let blocks = util::div_ceil(level_size, u64::from(self.block_size));
|
||||
let blocks = level_size.div_ceil(u64::from(self.block_size));
|
||||
level_size = blocks
|
||||
.checked_mul(digest_size as u64)
|
||||
.and_then(|s| padding::round(s, u64::from(self.block_size)))
|
||||
@@ -124,7 +124,7 @@ impl HashTree {
|
||||
let end_block = if range.end % block_size == 0 {
|
||||
range.end / block_size
|
||||
} else {
|
||||
util::div_ceil(range.end, block_size)
|
||||
range.end.div_ceil(block_size)
|
||||
};
|
||||
|
||||
result.push(start_block..end_block);
|
||||
|
||||
@@ -918,7 +918,7 @@ pub fn compress_image(
|
||||
});
|
||||
}
|
||||
|
||||
let chunks_total = util::div_ceil(file_size, CHUNK_SIZE);
|
||||
let chunks_total = file_size.div_ceil(CHUNK_SIZE);
|
||||
let mut bytes_compressed = 0;
|
||||
let mut context_uncompressed = Context::new(&ring::digest::SHA256);
|
||||
let mut operations = vec![];
|
||||
@@ -1059,7 +1059,7 @@ pub fn compress_modified_image(
|
||||
return Err(Error::ExtentsNotInOrder);
|
||||
}
|
||||
|
||||
let groups_total = util::div_ceil(operations.len(), OPERATION_GROUP);
|
||||
let groups_total = operations.len().div_ceil(OPERATION_GROUP);
|
||||
let mut bytes_compressed = 0;
|
||||
let mut context_uncompressed = Context::new(&ring::digest::SHA256);
|
||||
let mut modified_operations = vec![];
|
||||
|
||||
+49
-22
@@ -70,7 +70,11 @@ pub enum Error {
|
||||
#[error("XZ stream error")]
|
||||
XzStream(#[from] liblzma::stream::Error),
|
||||
#[error("Zip error")]
|
||||
Zip(#[from] ZipError),
|
||||
Zip(#[source] ZipError),
|
||||
#[error("Zip error for entry name: {0:?}")]
|
||||
ZipEntryName(String, #[source] ZipError),
|
||||
#[error("Zip error for entry index #{0}")]
|
||||
ZipEntryIndex(usize, #[source] ZipError),
|
||||
#[error("I/O error")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("File I/O error")]
|
||||
@@ -157,6 +161,13 @@ impl MagiskRootPatcher {
|
||||
const VER_XZ_BACKUP: Range<u32> =
|
||||
26403..Self::VERS_SUPPORTED[Self::VERS_SUPPORTED.len() - 1].end;
|
||||
|
||||
const ZIP_LIBMAGISK: &'static str = "lib/arm64-v8a/libmagisk.so";
|
||||
const ZIP_LIBMAGISK32: &'static str = "lib/armeabi-v7a/libmagisk32.so";
|
||||
const ZIP_LIBMAGISK64: &'static str = "lib/arm64-v8a/libmagisk64.so";
|
||||
const ZIP_MAGISKINIT: &'static str = "lib/arm64-v8a/libmagiskinit.so";
|
||||
const ZIP_STUB: &'static str = "assets/stub.apk";
|
||||
const ZIP_UTIL_FUNCTIONS: &'static str = "assets/util_functions.sh";
|
||||
|
||||
pub fn new(
|
||||
path: &Path,
|
||||
preinit_device: Option<&str>,
|
||||
@@ -208,8 +219,10 @@ impl MagiskRootPatcher {
|
||||
fn get_version(path: &Path) -> Result<u32> {
|
||||
let reader = File::open(path).map_err(|e| Error::File(path.to_owned(), e))?;
|
||||
let reader = BufReader::new(reader);
|
||||
let mut zip = ZipArchive::new(reader)?;
|
||||
let entry = zip.by_name("assets/util_functions.sh")?;
|
||||
let mut zip = ZipArchive::new(reader).map_err(Error::Zip)?;
|
||||
let entry = zip
|
||||
.by_name(Self::ZIP_UTIL_FUNCTIONS)
|
||||
.map_err(|e| Error::ZipEntryName(Self::ZIP_UTIL_FUNCTIONS.to_owned(), e))?;
|
||||
let mut entry = BufReader::new(entry);
|
||||
let mut line = String::new();
|
||||
|
||||
@@ -384,7 +397,7 @@ impl BootImagePatch for MagiskRootPatcher {
|
||||
fn patch(&self, boot_image: &mut BootImage, cancel_signal: &AtomicBool) -> Result<()> {
|
||||
let zip_reader =
|
||||
File::open(&self.apk_path).map_err(|e| Error::File(self.apk_path.clone(), e))?;
|
||||
let mut zip = ZipArchive::new(BufReader::new(zip_reader))?;
|
||||
let mut zip = ZipArchive::new(BufReader::new(zip_reader)).map_err(Error::Zip)?;
|
||||
|
||||
// Load the first ramdisk. If it doesn't exist, we have to generate one
|
||||
// from scratch.
|
||||
@@ -413,7 +426,9 @@ impl BootImagePatch for MagiskRootPatcher {
|
||||
|
||||
// Add magiskinit.
|
||||
{
|
||||
let mut zip_entry = zip.by_name("lib/arm64-v8a/libmagiskinit.so")?;
|
||||
let mut zip_entry = zip
|
||||
.by_name(Self::ZIP_MAGISKINIT)
|
||||
.map_err(|e| Error::ZipEntryName(Self::ZIP_MAGISKINIT.to_owned(), e))?;
|
||||
let mut data = vec![];
|
||||
zip_entry.read_to_end(&mut data)?;
|
||||
|
||||
@@ -424,28 +439,33 @@ impl BootImagePatch for MagiskRootPatcher {
|
||||
));
|
||||
}
|
||||
|
||||
// Add xz-compressed magisk32 and magisk64. We currently unconditionally
|
||||
// include magisk32 because the boot image itself doesn't contain
|
||||
// sufficient information to determine if a device is 64-bit only.
|
||||
let mut xz_files = HashMap::<&str, &[u8]>::new();
|
||||
xz_files.insert(
|
||||
"lib/armeabi-v7a/libmagisk32.so",
|
||||
b"overlay.d/sbin/magisk32.xz",
|
||||
);
|
||||
xz_files.insert(
|
||||
"lib/arm64-v8a/libmagisk64.so",
|
||||
b"overlay.d/sbin/magisk64.xz",
|
||||
);
|
||||
if zip.file_names().any(|n| n == Self::ZIP_LIBMAGISK) {
|
||||
// Newer Magisk versions only include a single binary for the target
|
||||
// ABI in the ramdisk. fb5ee86615ed3df830e8538f8b39b1b133caea34.
|
||||
debug!("Single libmagisk");
|
||||
xz_files.insert(Self::ZIP_LIBMAGISK, b"overlay.d/sbin/magisk.xz");
|
||||
} else {
|
||||
// Older Magisk versions include the 64-bit binary and, optionally,
|
||||
// the 32-bit binary if the device supports it. We unconditionally
|
||||
// include the magisk32 because the boot image itself doesn't have
|
||||
// sufficient information to determine if a device is 64-bit only.
|
||||
debug!("Split libmagisk32/libmagisk64");
|
||||
xz_files.insert(Self::ZIP_LIBMAGISK32, b"overlay.d/sbin/magisk32.xz");
|
||||
xz_files.insert(Self::ZIP_LIBMAGISK64, b"overlay.d/sbin/magisk64.xz");
|
||||
}
|
||||
|
||||
// Add stub apk, which only exists after Magisk commit
|
||||
// ad0e6511e11ebec65aa9b5b916e1397342850319.
|
||||
if zip.file_names().any(|n| n == "assets/stub.apk") {
|
||||
if zip.file_names().any(|n| n == Self::ZIP_STUB) {
|
||||
debug!("Magisk stub found");
|
||||
xz_files.insert("assets/stub.apk", b"overlay.d/sbin/stub.xz");
|
||||
xz_files.insert(Self::ZIP_STUB, b"overlay.d/sbin/stub.xz");
|
||||
}
|
||||
|
||||
for (source, target) in xz_files {
|
||||
let reader = zip.by_name(source)?;
|
||||
let reader = zip
|
||||
.by_name(source)
|
||||
.map_err(|e| Error::ZipEntryName(source.to_owned(), e))?;
|
||||
let buf = Self::xz_compress(reader, cancel_signal)?;
|
||||
|
||||
entries.push(CpioEntry::new_file(target, 0o644, CpioEntryData::Data(buf)));
|
||||
@@ -564,10 +584,12 @@ impl OtaCertPatcher {
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut zip = ZipArchive::new(Cursor::new(&data))?;
|
||||
let mut zip = ZipArchive::new(Cursor::new(&data)).map_err(Error::Zip)?;
|
||||
|
||||
for index in 0..zip.len() {
|
||||
let zip_entry = zip.by_index(index)?;
|
||||
let zip_entry = zip
|
||||
.by_index(index)
|
||||
.map_err(|e| Error::ZipEntryIndex(index, e))?;
|
||||
if !zip_entry.name().ends_with(".x509.pem") {
|
||||
debug!("Skipping invalid entry path: {}", zip_entry.name());
|
||||
continue;
|
||||
@@ -1184,7 +1206,12 @@ pub fn patch_boot_images<'a>(
|
||||
info.header.sign(key)?;
|
||||
}
|
||||
|
||||
avb::write_appended_image(writer, &info.header, &mut info.footer, info.image_size)?;
|
||||
avb::write_appended_image(
|
||||
writer,
|
||||
&info.header,
|
||||
&mut info.footer,
|
||||
Some(info.image_size),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Andrew Gunnerson
|
||||
* SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
@@ -129,7 +129,7 @@ pub fn patch_system_image(
|
||||
return Err(Error::NoHashTreeDescriptor);
|
||||
};
|
||||
|
||||
let num_chunks = util::div_ceil(footer.original_image_size, CHUNK_SIZE);
|
||||
let num_chunks = footer.original_image_size.div_ceil(CHUNK_SIZE);
|
||||
trace!("Parallel heuristics search for otacerts.zip with {num_chunks} chunks");
|
||||
|
||||
let modified_ranges = (0..num_chunks)
|
||||
@@ -207,7 +207,7 @@ pub fn patch_system_image(
|
||||
}
|
||||
|
||||
let writer = output.reopen_boxed()?;
|
||||
avb::write_appended_image(writer, &header, &mut footer, image_size)?;
|
||||
avb::write_appended_image(writer, &header, &mut footer, Some(image_size))?;
|
||||
|
||||
let AppendedDescriptorMut::HashTree(descriptor) = header.appended_descriptor_mut()? else {
|
||||
return Err(Error::NoHashTreeDescriptor);
|
||||
|
||||
@@ -49,16 +49,6 @@ pub fn parent_path(path: &Path) -> &Path {
|
||||
Path::new(".")
|
||||
}
|
||||
|
||||
/// Since Rust's built-in .div_ceil() is still nightly-only.
|
||||
pub fn div_ceil<T: PrimInt>(dividend: T, divisor: T) -> T {
|
||||
dividend / divisor
|
||||
+ if dividend % divisor != T::zero() {
|
||||
T::one()
|
||||
} else {
|
||||
T::zero()
|
||||
}
|
||||
}
|
||||
|
||||
/// Sort and merge overlapping intervals.
|
||||
pub fn merge_overlapping<T>(sections: &[Range<T>]) -> Vec<Range<T>>
|
||||
where
|
||||
|
||||
+112
-5
@@ -228,7 +228,7 @@ fn round_trip_appended_hash_image() {
|
||||
assert_eq!(header.verify().unwrap().unwrap(), key.to_public_key());
|
||||
|
||||
// Write vbmeta structures.
|
||||
avb::write_appended_image(&mut writer, &header, &mut footer, image_size).unwrap();
|
||||
avb::write_appended_image(&mut writer, &header, &mut footer, Some(image_size)).unwrap();
|
||||
let data = writer.into_inner();
|
||||
|
||||
// Verify checksum of the output.
|
||||
@@ -254,8 +254,8 @@ fn round_trip_appended_hash_image() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_appended_hash_tree_image() {
|
||||
let image_size = 28672;
|
||||
fn round_trip_appended_hash_tree_image_fixed_size() {
|
||||
let image_size = 32768;
|
||||
let raw_data: [u8; 8192] = repeat_array(b"foobar");
|
||||
let mut header = Header {
|
||||
required_libavb_version_major: 1,
|
||||
@@ -334,7 +334,114 @@ fn round_trip_appended_hash_tree_image() {
|
||||
assert_eq!(header.verify().unwrap().unwrap(), key.to_public_key());
|
||||
|
||||
// Write vbmeta structures.
|
||||
avb::write_appended_image(&mut writer, &header, &mut footer, image_size).unwrap();
|
||||
avb::write_appended_image(&mut writer, &header, &mut footer, Some(image_size)).unwrap();
|
||||
let mut data = Vec::new();
|
||||
writer.rewind().unwrap();
|
||||
writer.read_to_end(&mut data).unwrap();
|
||||
|
||||
// Verify checksum of the output.
|
||||
assert_eq!(
|
||||
ring::digest::digest(&ring::digest::SHA512, &data).as_ref(),
|
||||
[
|
||||
0xb5, 0x56, 0x65, 0x81, 0x5a, 0x16, 0x65, 0xa9, 0xa6, 0xc6, 0x9e, 0x41, 0x89, 0x9f,
|
||||
0xe9, 0xbc, 0xea, 0x59, 0x4d, 0x14, 0x8a, 0x9e, 0x2b, 0x13, 0xa0, 0x3a, 0x8e, 0xd4,
|
||||
0x59, 0xcd, 0x74, 0xe7, 0x99, 0xbd, 0xa3, 0x58, 0x4b, 0x84, 0xf2, 0x04, 0xe2, 0x12,
|
||||
0x48, 0xfe, 0x4f, 0x67, 0x1f, 0x2a, 0xaa, 0x22, 0x51, 0x19, 0x83, 0x95, 0xa8, 0x03,
|
||||
0xf5, 0x87, 0x12, 0x05, 0x8e, 0x14, 0xd9, 0xbd
|
||||
],
|
||||
);
|
||||
|
||||
// Parse the generated image.
|
||||
let mut reader = Cursor::new(&data);
|
||||
let (new_header, new_footer, new_image_size) = avb::load_image(&mut reader).unwrap();
|
||||
let new_footer = new_footer.unwrap();
|
||||
|
||||
assert_eq!(new_header, header);
|
||||
assert_eq!(new_footer, footer);
|
||||
assert_eq!(new_image_size, image_size);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_appended_hash_tree_image_minimum_size() {
|
||||
let raw_data: [u8; 8192] = repeat_array(b"foobar");
|
||||
let mut header = Header {
|
||||
required_libavb_version_major: 1,
|
||||
required_libavb_version_minor: 0,
|
||||
algorithm_type: AlgorithmType::Sha256Rsa4096,
|
||||
hash: vec![], // autogenerated
|
||||
signature: vec![], // autogenerated
|
||||
public_key: vec![], // autogenerated
|
||||
public_key_metadata: vec![
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
|
||||
0x0e, 0x0f,
|
||||
],
|
||||
descriptors: vec![
|
||||
Descriptor::Property(PropertyDescriptor {
|
||||
key: "foobar".to_owned(),
|
||||
value: b"Invalid UTF-8: \xFF".to_vec(),
|
||||
}),
|
||||
Descriptor::HashTree(HashTreeDescriptor {
|
||||
dm_verity_version: 1,
|
||||
image_size: raw_data.len() as u64,
|
||||
tree_offset: 0, // autogenerated
|
||||
tree_size: 0, // autogenerated
|
||||
data_block_size: 4096,
|
||||
hash_block_size: 4096,
|
||||
fec_num_roots: 2,
|
||||
fec_offset: 0, // autogenerated
|
||||
fec_size: 0, // autogenerated
|
||||
hash_algorithm: "sha256".to_owned(),
|
||||
partition_name: "vbmeta_appended_hash_tree".to_owned(),
|
||||
salt: [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef].repeat(4),
|
||||
root_digest: vec![], // autogenerated
|
||||
flags: 0,
|
||||
reserved: repeat_array(&[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]),
|
||||
}),
|
||||
],
|
||||
rollback_index: 1677974400,
|
||||
flags: 0,
|
||||
rollback_index_location: 0,
|
||||
release_string: repeat_str("MaxLength", 48),
|
||||
reserved: repeat_array(&[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]),
|
||||
};
|
||||
let mut footer = Footer {
|
||||
version_major: 1,
|
||||
version_minor: 0,
|
||||
original_image_size: 0, // autogenerated
|
||||
vbmeta_offset: 0, // autogenerated
|
||||
vbmeta_size: 0, // autogenerated
|
||||
reserved: repeat_array(&[0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]),
|
||||
};
|
||||
|
||||
let mut writer = SharedCursor::default();
|
||||
let cancel_signal = AtomicBool::new(false);
|
||||
|
||||
// Write the raw partition data.
|
||||
writer.write_all(&raw_data).unwrap();
|
||||
|
||||
// Generate and write the hash tree and FEC data.
|
||||
match header.appended_descriptor_mut().unwrap() {
|
||||
AppendedDescriptorMut::HashTree(d) => {
|
||||
d.update(&writer, &writer, None, &cancel_signal).unwrap();
|
||||
}
|
||||
AppendedDescriptorMut::Hash(_) => panic!("Expected hash tree descriptor"),
|
||||
}
|
||||
|
||||
// Verify the hash tree and FEC data.
|
||||
match header.appended_descriptor_mut().unwrap() {
|
||||
AppendedDescriptorMut::HashTree(d) => {
|
||||
d.verify(&writer, &cancel_signal).unwrap();
|
||||
}
|
||||
AppendedDescriptorMut::Hash(_) => panic!("Expected hash tree descriptor"),
|
||||
}
|
||||
|
||||
// Sign the header.
|
||||
let key = get_test_key();
|
||||
header.sign(&key).unwrap();
|
||||
assert_eq!(header.verify().unwrap().unwrap(), key.to_public_key());
|
||||
|
||||
// Write vbmeta structures.
|
||||
avb::write_appended_image(&mut writer, &header, &mut footer, None).unwrap();
|
||||
let mut data = Vec::new();
|
||||
writer.rewind().unwrap();
|
||||
writer.read_to_end(&mut data).unwrap();
|
||||
@@ -358,5 +465,5 @@ fn round_trip_appended_hash_tree_image() {
|
||||
|
||||
assert_eq!(new_header, header);
|
||||
assert_eq!(new_footer, footer);
|
||||
assert_eq!(new_image_size, image_size);
|
||||
assert_eq!(new_image_size, 28672);
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Andrew Gunnerson
|
||||
* SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson
|
||||
* SPDX-FileCopyrightText: 2023 Pascal Roeleven
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
@@ -196,7 +196,7 @@ fn append_avb(
|
||||
// Give enough free space for changes from patching.
|
||||
.max(1024 * 1024);
|
||||
|
||||
avb::write_appended_image(file, &header, &mut footer, full_image_size)?;
|
||||
avb::write_appended_image(file, &header, &mut footer, Some(full_image_size))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user