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

Add support for outputting zip files with no data descriptors #337

Merged
merged 1 commit into from
Aug 21, 2024
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,12 @@ avbroot ota extract \
--all
```

### Zip write mode

By default, avbroot uses streaming writes for the output OTA during patching. This means it computes the sha256 digest for the digital signature as the file is being written. This mode causes the zip file to contain data descriptors, which is part of the zip standard and works on the vast majority of devices. However, some devices may have broken zip file parsers and fail to properly read OTA zip files containing data descriptors. If this is the case, pass in `--zip-mode seekable` when patching.

The seekable mode writes zip files without data descriptors, but as the name implies, requires seeking around the file instead of writing it sequentially. The sha256 digest for the digital signature is computed after the zip file has been fully written.

### Signing with an external program

avbroot supports delegating all RSA signing operations to an external program with the `--signing-helper` option. When using this option, the `--key-avb` and `--key-ota` options must be given a public key instead of a private key.
Expand Down
60 changes: 46 additions & 14 deletions avbroot/src/cli/ota.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use crate::{
crypto::{self, PassphraseSource, RsaSigningKey},
format::{
avb::{self, Descriptor, Header},
ota::{self, SigningWriter, ZipEntry},
ota::{self, SigningWriter, ZipEntry, ZipMode},
padding,
payload::{self, PayloadHeader, PayloadWriter, VabcAlgo},
},
Expand All @@ -47,8 +47,8 @@ use crate::{
build::tools::releasetools::OtaMetadata, chromeos_update_engine::DeltaArchiveManifest,
},
stream::{
self, CountingWriter, FromReader, HashingWriter, HolePunchingWriter, PSeekFile,
ReadSeekReopen, Reopen, SectionReader, ToWriter, WriteSeekReopen,
self, CountingWriter, FromReader, HashingWriter, PSeekFile, ReadSeekReopen, Reopen,
SectionReader, ToWriter, WriteSeekReopen,
},
util,
};
Expand Down Expand Up @@ -916,6 +916,7 @@ fn patch_ota_zip(
external_images: &HashMap<String, PathBuf>,
mut boot_patchers: Vec<Box<dyn BootImagePatch + Sync>>,
clear_vbmeta_flags: bool,
zip_mode: ZipMode,
key_avb: &RsaSigningKey,
key_ota: &RsaSigningKey,
cert_ota: &Certificate,
Expand Down Expand Up @@ -1078,14 +1079,24 @@ fn patch_ota_zip(

info!("Generating new OTA metadata");

let data_descriptor_size = if last_entry_used_zip64 { 24 } else { 16 };
let data_descriptor_size = match zip_mode {
ZipMode::Streaming => {
if last_entry_used_zip64 {
24
} else {
16
}
}
ZipMode::Seekable => 0,
};
let metadata = ota::add_metadata(
&entries,
zip_writer,
// Offset where next entry would begin.
entries.last().map(|e| e.offset + e.size).unwrap() + data_descriptor_size,
&metadata.unwrap(),
payload_metadata_size.unwrap(),
zip_mode,
)
.context("Failed to write new OTA metadata")?;

Expand Down Expand Up @@ -1313,10 +1324,16 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
)
.context("Failed to open temporary output file")?;
let temp_path = temp_writer.path().to_owned();
let hole_punching_writer = HolePunchingWriter::new(temp_writer);
let buffered_writer = BufWriter::new(hole_punching_writer);
let signing_writer = SigningWriter::new(buffered_writer);
let mut zip_writer = ZipWriter::new_streaming(signing_writer);
let mut zip_writer = match cli.zip_mode {
ZipMode::Streaming => {
let signing_writer = SigningWriter::new_streaming(temp_writer);
ZipWriter::new_streaming(signing_writer)
}
ZipMode::Seekable => {
let signing_writer = SigningWriter::new_seekable(temp_writer);
ZipWriter::new(signing_writer)
}
};

let (metadata, payload_metadata_size) = patch_ota_zip(
&raw_reader,
Expand All @@ -1325,6 +1342,7 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
&external_images,
boot_patchers,
cli.clear_vbmeta_flags,
cli.zip_mode,
&key_avb,
&key_ota,
&cert_ota,
Expand All @@ -1335,13 +1353,9 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
let signing_writer = zip_writer
.finish()
.context("Failed to finalize output zip")?;
let buffered_writer = signing_writer
.finish(&key_ota, &cert_ota)
let mut temp_writer = signing_writer
.finish(&key_ota, &cert_ota, cancel_signal)
.context("Failed to sign output zip")?;
let hole_punching_writer = buffered_writer
.into_inner()
.context("Failed to flush output zip")?;
let mut temp_writer = hole_punching_writer.into_inner();
temp_writer.flush().context("Failed to flush output zip")?;

// We do a lot of low-level hackery. Reopen and verify offsets.
Expand Down Expand Up @@ -1915,6 +1929,24 @@ pub struct PatchCli {
#[arg(long, help_heading = HEADING_OTHER)]
pub clear_vbmeta_flags: bool,

/// Zip creation mode for the output OTA zip.
///
/// The streaming mode produces zip files that contain data descriptors.
/// The zip file is hashed as it is being written. This mode is the default
/// and works with the vast majority of devices.
///
/// The seekable mode produces zip files that do not use data descriptors.
/// The zip file is reread and hashed after it has been fully written. The
/// output file is more likely to be compatible with devices that have
/// broken zip file parsers.
#[arg(
long,
value_name = "MODE",
default_value_t = ZipMode::Streaming,
help_heading = HEADING_OTHER
)]
pub zip_mode: ZipMode,

/// (Deprecated: no longer needed)
#[arg(
long,
Expand Down
Loading