Magisk/native/src/boot/payload.rs

213 lines
6.6 KiB
Rust
Raw Normal View History

2023-05-05 01:49:33 +00:00
use std::fs::File;
use std::io::{BufReader, Read, Seek, SeekFrom, Write};
use std::os::fd::{AsRawFd, FromRawFd};
2023-05-05 01:49:33 +00:00
use anyhow::{anyhow, Context};
2023-05-05 01:49:33 +00:00
use byteorder::{BigEndian, ReadBytesExt};
use protobuf::{EnumFull, Message};
use base::libc::c_char;
2023-05-26 21:07:11 +00:00
use base::{ptr_to_str_result, ReadSeekExt, StrErr};
2023-05-06 06:57:34 +00:00
use base::{ResultExt, WriteExt};
2023-05-05 01:49:33 +00:00
use crate::ffi;
use crate::update_metadata::install_operation::Type;
use crate::update_metadata::DeltaArchiveManifest;
macro_rules! bad_payload {
($msg:literal) => {
anyhow!(concat!("invalid payload: ", $msg))
};
($($args:tt)*) => {
anyhow!("invalid payload: {}", format_args!($($args)*))
2023-05-05 01:49:33 +00:00
};
}
const PAYLOAD_MAGIC: &str = "CrAU";
2023-05-05 01:49:33 +00:00
fn do_extract_boot_from_payload(
in_path: &str,
2023-05-26 21:07:11 +00:00
partition_name: Option<&str>,
out_path: Option<&str>,
) -> anyhow::Result<()> {
let mut reader = BufReader::new(if in_path == "-" {
unsafe { File::from_raw_fd(0) }
} else {
File::open(in_path).with_context(|| format!("cannot open '{in_path}'"))?
});
2023-05-05 01:49:33 +00:00
let buf = &mut [0u8; 4];
reader.read_exact(buf)?;
if buf != PAYLOAD_MAGIC.as_bytes() {
return Err(bad_payload!("invalid magic"));
2023-05-05 01:49:33 +00:00
}
let version = reader.read_u64::<BigEndian>()?;
if version != 2 {
return Err(bad_payload!("unsupported version: {}", version));
2023-05-05 01:49:33 +00:00
}
let manifest_len = reader.read_u64::<BigEndian>()? as usize;
2023-05-05 01:49:33 +00:00
if manifest_len == 0 {
return Err(bad_payload!("manifest length is zero"));
2023-05-05 01:49:33 +00:00
}
let manifest_sig_len = reader.read_u32::<BigEndian>()?;
if manifest_sig_len == 0 {
return Err(bad_payload!("manifest signature length is zero"));
2023-05-05 01:49:33 +00:00
}
let mut buf = Vec::new();
buf.resize(manifest_len, 0u8);
2023-05-05 01:49:33 +00:00
let manifest = {
let manifest = &mut buf[..manifest_len];
reader.read_exact(manifest)?;
2023-06-09 13:43:26 +00:00
DeltaArchiveManifest::parse_from_bytes(manifest)?
};
2023-05-05 01:49:33 +00:00
if !manifest.has_minor_version() || manifest.minor_version() != 0 {
return Err(bad_payload!(
2023-05-05 01:49:33 +00:00
"delta payloads are not supported, please use a full payload file"
));
}
let block_size = manifest.block_size() as u64;
2023-05-26 21:07:11 +00:00
let partition = match partition_name {
None => {
let boot = manifest
.partitions
.iter()
2023-05-26 21:07:11 +00:00
.find(|p| p.partition_name() == "init_boot");
let boot = match boot {
Some(boot) => Some(boot),
None => manifest
.partitions
.iter()
2023-05-26 21:07:11 +00:00
.find(|p| p.partition_name() == "boot"),
};
boot.ok_or(anyhow!("boot partition not found"))?
}
2023-05-26 21:07:11 +00:00
Some(name) => manifest
.partitions
.iter()
2023-05-26 21:07:11 +00:00
.find(|p| p.partition_name() == name)
.ok_or(anyhow!("partition '{name}' not found"))?,
2023-05-05 01:49:33 +00:00
};
let out_str: String;
let out_path = match out_path {
None => {
2023-05-26 21:07:11 +00:00
out_str = format!("{}.img", partition.partition_name());
out_str.as_str()
}
2023-05-26 21:07:11 +00:00
Some(s) => s,
};
2023-05-05 01:49:33 +00:00
2023-05-26 21:07:11 +00:00
let mut out_file =
File::create(out_path).with_context(|| format!("cannot write to '{out_path}'"))?;
// Skip the manifest signature
reader.skip(manifest_sig_len as usize)?;
2023-05-05 01:49:33 +00:00
2023-05-26 21:07:11 +00:00
// Sort the install operations with data_offset so we will only ever need to seek forward
// This makes it possible to support non-seekable input file descriptors
let mut operations = partition.operations.clone();
operations.sort_by_key(|e| e.data_offset.unwrap_or(0));
let mut curr_data_offset: u64 = 0;
2023-05-26 21:07:11 +00:00
for operation in operations.iter() {
2023-05-05 01:49:33 +00:00
let data_len = operation
.data_length
.ok_or(bad_payload!("data length not found"))? as usize;
2023-05-05 01:49:33 +00:00
let data_offset = operation
.data_offset
.ok_or(bad_payload!("data offset not found"))?;
2023-05-05 01:49:33 +00:00
let data_type = operation
.type_
.ok_or(bad_payload!("operation type not found"))?
2023-05-05 01:49:33 +00:00
.enum_value()
.map_err(|_| bad_payload!("operation type not valid"))?;
2023-05-05 01:49:33 +00:00
buf.resize(data_len, 0u8);
let data = &mut buf[..data_len];
2023-05-05 01:49:33 +00:00
2023-05-26 21:07:11 +00:00
// Skip to the next offset and read data
let skip = data_offset - curr_data_offset;
reader.skip(skip as usize)?;
reader.read_exact(data)?;
2023-05-26 21:07:11 +00:00
curr_data_offset = data_offset + data_len as u64;
2023-05-05 01:49:33 +00:00
let out_offset = operation
.dst_extents
.get(0)
.ok_or(bad_payload!("dst extents not found"))?
2023-05-05 01:49:33 +00:00
.start_block
.ok_or(bad_payload!("start block not found"))?
2023-05-05 01:49:33 +00:00
* block_size;
match data_type {
Type::REPLACE => {
out_file.seek(SeekFrom::Start(out_offset))?;
2023-06-09 13:43:26 +00:00
out_file.write_all(data)?;
2023-05-05 01:49:33 +00:00
}
Type::ZERO => {
for ext in operation.dst_extents.iter() {
let out_seek = ext
.start_block
.ok_or(bad_payload!("start block not found"))?
* block_size;
let num_blocks = ext.num_blocks.ok_or(bad_payload!("num blocks not found"))?;
2023-05-05 01:49:33 +00:00
out_file.seek(SeekFrom::Start(out_seek))?;
out_file.write_zeros(num_blocks as usize)?;
}
}
Type::REPLACE_BZ | Type::REPLACE_XZ => {
out_file.seek(SeekFrom::Start(out_offset))?;
if !ffi::decompress(data, out_file.as_raw_fd()) {
return Err(bad_payload!("decompression failed"));
2023-05-05 01:49:33 +00:00
}
}
_ => {
return Err(bad_payload!(
2023-05-05 01:49:33 +00:00
"unsupported operation type: {}",
data_type.descriptor().name()
));
}
};
}
Ok(())
}
pub fn extract_boot_from_payload(
in_path: *const c_char,
partition: *const c_char,
out_path: *const c_char,
) -> bool {
fn inner(
in_path: *const c_char,
partition: *const c_char,
out_path: *const c_char,
) -> anyhow::Result<()> {
let in_path = ptr_to_str_result(in_path)?;
let partition = match ptr_to_str_result(partition) {
Ok(s) => Some(s),
Err(StrErr::NullPointerError) => None,
Err(e) => Err(e)?,
};
let out_path = match ptr_to_str_result(out_path) {
Ok(s) => Some(s),
Err(StrErr::NullPointerError) => None,
Err(e) => Err(e)?,
};
do_extract_boot_from_payload(in_path, partition, out_path)
.context("Failed to extract from payload")?;
Ok(())
}
inner(in_path, partition, out_path).log().is_ok()
2023-05-05 01:49:33 +00:00
}