mirror of
https://github.com/chenxiaolong/avbroot.git
synced 2026-07-03 14:05:11 +02:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 461392c226 | |||
| 077a80f4ce | |||
| 1e43930f3c | |||
| d00e53fb2a | |||
| 0b5f3b30cf | |||
| 10c2a0969f | |||
| 83218a747d | |||
| 6fb7d568cd | |||
| 6cbf690e17 | |||
| e097f98404 | |||
| d5605eabc0 | |||
| d1bcf2da80 | |||
| bbf8e28a28 | |||
| dc93b17888 | |||
| f83a408dc4 | |||
| 798fddc4d5 | |||
| dfed06dcf8 | |||
| 02fd92a640 | |||
| dd4faf72ae | |||
| adbe249edb | |||
| b7028b13a2 | |||
| 8122b0eece | |||
| 0613de4ee4 | |||
| 1a18eb7f85 | |||
| 1e1ca9dcbf | |||
| 6924783f48 | |||
| 98071daa33 | |||
| 8e1adae947 | |||
| d6705f4f00 | |||
| a648df1695 |
@@ -30,14 +30,14 @@ runs:
|
||||
key: e2e-${{ github.sha }}-${{ runner.os }}
|
||||
fail-on-cache-miss: true
|
||||
path: |
|
||||
target/release/e2e
|
||||
target/release/e2e.exe
|
||||
target/output/e2e
|
||||
target/output/e2e.exe
|
||||
|
||||
- name: Downloading device image for ${{ inputs.device }}
|
||||
if: ${{ ! steps.cache-img.outputs.cache-hit }}
|
||||
shell: sh
|
||||
working-directory: e2e
|
||||
run: ../target/release/e2e download --stripped -d ${{ inputs.device }}
|
||||
run: ../target/output/e2e download --stripped -d ${{ inputs.device }}
|
||||
|
||||
- if: ${{ ! steps.cache-img.outputs.cache-hit }}
|
||||
name: Creating sparse archive from image
|
||||
|
||||
@@ -21,11 +21,11 @@ runs:
|
||||
key: e2e-${{ github.sha }}-${{ runner.os }}
|
||||
fail-on-cache-miss: true
|
||||
path: |
|
||||
target/release/e2e
|
||||
target/release/e2e.exe
|
||||
target/output/e2e
|
||||
target/output/e2e.exe
|
||||
|
||||
- name: Downloading Magisk
|
||||
if: ${{ ! steps.cache-magisk.outputs.cache-hit }}
|
||||
shell: sh
|
||||
working-directory: e2e
|
||||
run: ../target/release/e2e download --magisk
|
||||
run: ../target/output/e2e download --magisk
|
||||
|
||||
+35
-18
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: -C strip=symbols
|
||||
RUSTFLAGS: -C strip=symbols -C target-feature=+crt-static
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -40,28 +40,35 @@ jobs:
|
||||
| sed -E "s/^v//g;s/([^-]*-g)/r\1/;s/-/./g" \
|
||||
>> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Get Rust LLVM target triple
|
||||
- name: Get Rust target triple
|
||||
id: get_target
|
||||
shell: bash
|
||||
env:
|
||||
RUSTC_BOOTSTRAP: '1'
|
||||
run: |
|
||||
echo -n 'name=' >> "${GITHUB_OUTPUT}"
|
||||
rustc -Z unstable-options --print target-spec-json \
|
||||
| jq -r '."llvm-target"' \
|
||||
>> "${GITHUB_OUTPUT}"
|
||||
rustc -vV | sed -n 's|host: ||p' >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Cache Rust dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy --release --workspace --features static
|
||||
shell: bash
|
||||
run: |
|
||||
cargo clippy --release --workspace --features static \
|
||||
--target ${{ steps.get_target.outputs.name }}
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release --workspace --features static
|
||||
shell: bash
|
||||
run: |
|
||||
cargo build --release --workspace --features static \
|
||||
--target ${{ steps.get_target.outputs.name }}
|
||||
|
||||
- name: Tests
|
||||
run: cargo test --release --workspace --features static
|
||||
shell: bash
|
||||
run: |
|
||||
cargo test --release --workspace --features static \
|
||||
--target ${{ steps.get_target.outputs.name }}
|
||||
|
||||
- name: Archive documentation
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -71,22 +78,32 @@ jobs:
|
||||
LICENSE
|
||||
README.md
|
||||
|
||||
# Due to https://github.com/rust-lang/rust/issues/78210, we have to use
|
||||
# the --target option, which puts all output files in a different path.
|
||||
# Symlink that path to the normal output directory so that we don't need
|
||||
# to specify the Rust triple everywhere.
|
||||
- name: Symlink target directory
|
||||
shell: bash
|
||||
run: |
|
||||
rm -rf target/output
|
||||
ln -s ${{ steps.get_target.outputs.name }}/release target/output
|
||||
|
||||
# This is separate so we can have a flat directory structure.
|
||||
- name: Archive executable
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: avbroot-${{ steps.get_version.outputs.version }}-${{ steps.get_target.outputs.name }}
|
||||
path: |
|
||||
target/release/avbroot
|
||||
target/release/avbroot.exe
|
||||
target/output/avbroot
|
||||
target/output/avbroot.exe
|
||||
|
||||
- name: Cache e2e executable
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: e2e-${{ github.sha }}-${{ runner.os }}
|
||||
path: |
|
||||
target/release/e2e
|
||||
target/release/e2e.exe
|
||||
target/output/e2e
|
||||
target/output/e2e.exe
|
||||
|
||||
setup:
|
||||
name: Prepare workflow data
|
||||
@@ -108,8 +125,8 @@ jobs:
|
||||
key: e2e-${{ github.sha }}-${{ runner.os }}
|
||||
fail-on-cache-miss: true
|
||||
path: |
|
||||
target/release/e2e
|
||||
target/release/e2e.exe
|
||||
target/output/e2e
|
||||
target/output/e2e.exe
|
||||
|
||||
- name: Loading test config
|
||||
id: load-config
|
||||
@@ -117,7 +134,7 @@ jobs:
|
||||
run: |
|
||||
echo 'config-path=e2e/e2e.toml' >> "${GITHUB_OUTPUT}"
|
||||
echo -n 'device-list=' >> "${GITHUB_OUTPUT}"
|
||||
../target/release/e2e list \
|
||||
../target/output/e2e list \
|
||||
| jq -cnR '[inputs | select(length > 0)]' \
|
||||
>> "${GITHUB_OUTPUT}"
|
||||
|
||||
@@ -209,10 +226,10 @@ jobs:
|
||||
key: e2e-${{ github.sha }}-${{ runner.os }}
|
||||
fail-on-cache-miss: true
|
||||
path: |
|
||||
target/release/e2e
|
||||
target/release/e2e.exe
|
||||
target/output/e2e
|
||||
target/output/e2e.exe
|
||||
|
||||
# Finally run tests
|
||||
- name: Run test for ${{ matrix.device }}
|
||||
working-directory: e2e
|
||||
run: ../target/release/e2e test --stripped -d ${{ matrix.device }}
|
||||
run: ../target/output/e2e test --stripped -d ${{ matrix.device }}
|
||||
|
||||
@@ -7,6 +7,20 @@
|
||||
to update the actual links at the bottom of the file.
|
||||
-->
|
||||
|
||||
### Unreleased
|
||||
|
||||
* Add support for AVB 2.0 format 1.3.0 (for Android 15) ([PR #210])
|
||||
* Add new `avbroot key decode-avb` command for converting AVB-encoded public keys to the standard PKCS8-encoded format ([PR #219])
|
||||
* Adjust AVB sanity check to validate `*_dlkm` and `odm` specifically because some devices have an unprotected `odm_ext` partition ([PR #220])
|
||||
* Add new `--otacerts-partition` option to override the autodetected partition for replacing `otacerts.zip` ([Issue #218], [PR #221])
|
||||
* Build precompiled executables as statically linked executables ([Issue #222], [PR #224], [PR #227])
|
||||
* Limit critical partition check to bootloader-verified partitions ([Issue #223], [PR #226])
|
||||
|
||||
Behind-the-scenes changes:
|
||||
|
||||
* Fix lint warnings introduced in Rust 1.74.0 ([PR #211])
|
||||
* Temporarily silence [RUSTSEC-2023-0071](https://rustsec.org/advisories/RUSTSEC-2023-0071) warning in cargo-deny ([PR #214])
|
||||
|
||||
### Version 2.3.3
|
||||
|
||||
* Add support for XZ-compressed ramdisks ([Issue #203], [PR #207])
|
||||
@@ -117,6 +131,9 @@ Behind-the-scenes changes:
|
||||
[Issue #166]: https://github.com/chenxiaolong/avbroot/issues/166
|
||||
[Issue #201]: https://github.com/chenxiaolong/avbroot/issues/201
|
||||
[Issue #203]: https://github.com/chenxiaolong/avbroot/issues/203
|
||||
[Issue #218]: https://github.com/chenxiaolong/avbroot/issues/218
|
||||
[Issue #222]: https://github.com/chenxiaolong/avbroot/issues/222
|
||||
[Issue #223]: https://github.com/chenxiaolong/avbroot/issues/223
|
||||
[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
|
||||
@@ -166,3 +183,12 @@ Behind-the-scenes changes:
|
||||
[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
|
||||
[PR #210]: https://github.com/chenxiaolong/avbroot/pull/210
|
||||
[PR #211]: https://github.com/chenxiaolong/avbroot/pull/211
|
||||
[PR #214]: https://github.com/chenxiaolong/avbroot/pull/214
|
||||
[PR #219]: https://github.com/chenxiaolong/avbroot/pull/219
|
||||
[PR #220]: https://github.com/chenxiaolong/avbroot/pull/220
|
||||
[PR #221]: https://github.com/chenxiaolong/avbroot/pull/221
|
||||
[PR #224]: https://github.com/chenxiaolong/avbroot/pull/224
|
||||
[PR #226]: https://github.com/chenxiaolong/avbroot/pull/226
|
||||
[PR #227]: https://github.com/chenxiaolong/avbroot/pull/227
|
||||
|
||||
@@ -13,7 +13,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 support this, as well as most OnePlus devices. 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. 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.
|
||||
|
||||
* 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)).
|
||||
|
||||
## Patches
|
||||
|
||||
|
||||
+83
-16
@@ -117,7 +117,7 @@ impl MagiskRootPatcher {
|
||||
// RULESDEVICE config option, which stored the writable block device as an
|
||||
// rdev major/minor pair, which was not consistent across reboots and was
|
||||
// replaced by PREINITDEVICE
|
||||
const VERS_SUPPORTED: &[Range<u32>] = &[25102..25207, 25211..26500];
|
||||
const VERS_SUPPORTED: &'static [Range<u32>] = &[25102..25207, 25211..26500];
|
||||
const VER_PREINIT_DEVICE: Range<u32> =
|
||||
25211..Self::VERS_SUPPORTED[Self::VERS_SUPPORTED.len() - 1].end;
|
||||
const VER_RANDOM_SEED: Range<u32> = 25211..26103;
|
||||
@@ -407,7 +407,7 @@ pub struct OtaCertPatcher {
|
||||
}
|
||||
|
||||
impl OtaCertPatcher {
|
||||
const OTACERTS_PATH: &[u8] = b"system/etc/security/otacerts.zip";
|
||||
const OTACERTS_PATH: &'static [u8] = b"system/etc/security/otacerts.zip";
|
||||
|
||||
pub fn new(cert: Certificate) -> Self {
|
||||
Self { cert }
|
||||
@@ -452,25 +452,28 @@ impl OtaCertPatcher {
|
||||
Ok(certificates)
|
||||
}
|
||||
|
||||
/// Create a new otacerts archive. The old certs are ignored since flashing
|
||||
/// a stock OTA will render the device unbootable.
|
||||
fn create_zip(cert: &Certificate) -> Result<Vec<u8>> {
|
||||
let raw_writer = Cursor::new(Vec::new());
|
||||
let mut writer = ZipWriter::new(raw_writer);
|
||||
let options = FileOptions::default().compression_method(CompressionMethod::Stored);
|
||||
writer.start_file("ota.x509.pem", options)?;
|
||||
|
||||
crypto::write_pem_cert(&mut writer, cert)?;
|
||||
|
||||
let raw_writer = writer.finish()?;
|
||||
|
||||
Ok(raw_writer.into_inner())
|
||||
}
|
||||
|
||||
fn patch_ramdisk(&self, data: &mut Vec<u8>, cancel_signal: &AtomicBool) -> Result<bool> {
|
||||
let (mut entries, ramdisk_format) = load_ramdisk(data, cancel_signal)?;
|
||||
let Some(entry) = entries.iter_mut().find(|e| e.path == Self::OTACERTS_PATH) else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
// Create a new otacerts archive. The old certs are ignored since
|
||||
// flashing a stock OTA will render the device unbootable.
|
||||
{
|
||||
let raw_writer = Cursor::new(Vec::new());
|
||||
let mut writer = ZipWriter::new(raw_writer);
|
||||
let options = FileOptions::default().compression_method(CompressionMethod::Stored);
|
||||
writer.start_file("ota.x509.pem", options)?;
|
||||
|
||||
crypto::write_pem_cert(&mut writer, &self.cert)?;
|
||||
|
||||
let raw_writer = writer.finish()?;
|
||||
entry.data = CpioEntryData::Data(raw_writer.into_inner());
|
||||
}
|
||||
entry.data = CpioEntryData::Data(Self::create_zip(&self.cert)?);
|
||||
|
||||
// Repack ramdisk.
|
||||
*data = save_ramdisk(&entries, ramdisk_format, cancel_signal)?;
|
||||
@@ -511,6 +514,70 @@ impl BootImagePatcher for OtaCertPatcher {
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace /init with a wrapper that will bind mount otacerts.zip containing
|
||||
/// the custom OTA certificate into the system partition.
|
||||
pub struct InitWrapperPatcher {
|
||||
cert: Certificate,
|
||||
}
|
||||
|
||||
impl InitWrapperPatcher {
|
||||
pub fn new(cert: Certificate) -> Self {
|
||||
Self { cert }
|
||||
}
|
||||
|
||||
fn patch_ramdisk(&self, data: &mut Vec<u8>, cancel_signal: &AtomicBool) -> Result<bool> {
|
||||
let (mut entries, ramdisk_format) = load_ramdisk(data, cancel_signal)?;
|
||||
|
||||
if let Some(entry) = entries.iter_mut().find(|e| e.path == b"init") {
|
||||
entry.path = b"avbroot/init.orig".to_vec();
|
||||
} else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
entries.push(CpioEntry::new_directory(b"avbroot", 0o755));
|
||||
entries.push(CpioEntry::new_file(b"avbroot/otacerts.zip", 0o644,
|
||||
CpioEntryData::Data(OtaCertPatcher::create_zip(&self.cert)?)));
|
||||
|
||||
// TODO
|
||||
// How do we pick ABI
|
||||
let init_data = std::fs::read("/home/chenxiaolong/git/github/avbroot/init/libs/arm64-v8a/init").unwrap();
|
||||
entries.push(CpioEntry::new_file(b"init", 0o750, CpioEntryData::Data(init_data)));
|
||||
|
||||
cpio::sort(&mut entries);
|
||||
|
||||
*data = save_ramdisk(&entries, ramdisk_format, cancel_signal)?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl BootImagePatcher for InitWrapperPatcher {
|
||||
fn patch(&self, boot_image: &mut BootImage, cancel_signal: &AtomicBool) -> Result<()> {
|
||||
let patched_any = match boot_image {
|
||||
BootImage::V0Through2(b) => self.patch_ramdisk(&mut b.ramdisk, cancel_signal)?,
|
||||
BootImage::V3Through4(b) => self.patch_ramdisk(&mut b.ramdisk, cancel_signal)?,
|
||||
BootImage::VendorV3Through4(b) => {
|
||||
let mut patched = false;
|
||||
|
||||
for ramdisk in &mut b.ramdisks {
|
||||
if self.patch_ramdisk(ramdisk, cancel_signal)? {
|
||||
patched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
patched
|
||||
}
|
||||
};
|
||||
|
||||
if !patched_any {
|
||||
return Err(Error::Validation("No ramdisk contains init".to_owned()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace the boot image with a prepatched boot image if it is compatible.
|
||||
///
|
||||
/// An image is compatible if all the non-size-related header fields are
|
||||
@@ -528,7 +595,7 @@ impl PrepatchedImagePatcher {
|
||||
const MAX_LEVEL: u8 = 2;
|
||||
|
||||
// We compile without Unicode support so we have to use [0-9] instead of \d.
|
||||
const VERSION_REGEX: &str = r"Linux version ([0-9]+\.[0-9]+).[0-9]+-(android[0-9]+)-([0-9]+)-";
|
||||
const VERSION_REGEX: &'static str = r"Linux version ([0-9]+\.[0-9]+).[0-9]+-(android[0-9]+)-([0-9]+)-";
|
||||
|
||||
pub fn new(
|
||||
prepatched: &Path,
|
||||
|
||||
@@ -71,6 +71,16 @@ pub fn key_main(cli: &KeyCli) -> Result<()> {
|
||||
fs::write(&c.output, encoded)
|
||||
.with_context(|| format!("Failed to write public key: {:?}", c.output))?;
|
||||
}
|
||||
KeyCommand::DecodeAvb(c) => {
|
||||
let encoded = fs::read(&c.key)
|
||||
.with_context(|| format!("Failed to load AVB public key: {:?}", c.key))?;
|
||||
|
||||
let public_key = avb::decode_public_key(&encoded)
|
||||
.context("Failed to decode public key as AVB format")?;
|
||||
|
||||
crypto::write_pem_public_key_file(&c.output, &public_key)
|
||||
.with_context(|| format!("Failed to write public key: {:?}", c.output))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -152,11 +162,24 @@ struct ExtractAvbCli {
|
||||
passphrase: PassphraseGroup,
|
||||
}
|
||||
|
||||
/// Convert an AVB-encoded public key to a PKCS8-encoded public key.
|
||||
#[derive(Debug, Parser)]
|
||||
struct DecodeAvbCli {
|
||||
/// Path to output PKCS8-encoded public key.
|
||||
#[arg(short, long, value_name = "FILE", value_parser)]
|
||||
output: PathBuf,
|
||||
|
||||
/// Path to AVB-encoded public key.
|
||||
#[arg(short, long, value_name = "FILE", value_parser)]
|
||||
key: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum KeyCommand {
|
||||
GenerateKey(GenerateKeyCli),
|
||||
GenerateCert(GenerateCertCli),
|
||||
ExtractAvb(ExtractAvbCli),
|
||||
DecodeAvb(DecodeAvbCli),
|
||||
}
|
||||
|
||||
/// Generate and convert keys.
|
||||
|
||||
+60
-31
@@ -28,7 +28,7 @@ use x509_cert::Certificate;
|
||||
use zip::{write::FileOptions, CompressionMethod, ZipArchive, ZipWriter};
|
||||
|
||||
use crate::{
|
||||
boot::{self, BootImagePatcher, MagiskRootPatcher, OtaCertPatcher, PrepatchedImagePatcher},
|
||||
boot::{self, BootImagePatcher, MagiskRootPatcher, OtaCertPatcher, PrepatchedImagePatcher, InitWrapperPatcher},
|
||||
cli::{self, status, warning},
|
||||
crypto::{self, PassphraseSource},
|
||||
format::{
|
||||
@@ -115,8 +115,8 @@ pub fn get_partitions_by_type(manifest: &DeltaArchiveManifest) -> Result<HashMap
|
||||
/// partitions.
|
||||
pub fn get_required_images(
|
||||
manifest: &DeltaArchiveManifest,
|
||||
boot_partition: &str,
|
||||
with_root: bool,
|
||||
rootpatch_partition: Option<&str>,
|
||||
otacerts_partition: Option<&str>,
|
||||
) -> Result<HashMap<String, String>> {
|
||||
let all_partitions = manifest
|
||||
.partitions
|
||||
@@ -127,18 +127,23 @@ pub fn get_required_images(
|
||||
let mut images = HashMap::new();
|
||||
|
||||
for (k, v) in &by_type {
|
||||
if k == "@otacerts" || k.starts_with("@vbmeta:") {
|
||||
if k == "@otacerts" || k == "@gki_ramdisk" || k.starts_with("@vbmeta:") {
|
||||
images.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if with_root {
|
||||
if by_type.contains_key(boot_partition) {
|
||||
images.insert("@rootpatch".to_owned(), by_type[boot_partition].clone());
|
||||
} else if all_partitions.contains(boot_partition) {
|
||||
images.insert("@rootpatch".to_owned(), boot_partition.to_owned());
|
||||
} else {
|
||||
bail!("Boot partition not found: {boot_partition}");
|
||||
for (k, v) in [
|
||||
("@rootpatch", rootpatch_partition),
|
||||
("@otacerts", otacerts_partition),
|
||||
] {
|
||||
if let Some(name) = v {
|
||||
if by_type.contains_key(name) {
|
||||
images.insert(k.to_owned(), by_type[name].clone());
|
||||
} else if all_partitions.contains(name) {
|
||||
images.insert(k.to_owned(), name.to_owned());
|
||||
} else {
|
||||
bail!("{k} partition not found: {name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,6 +214,12 @@ fn patch_boot_images(
|
||||
.push(p);
|
||||
}
|
||||
|
||||
// We want our init wrapper to be the entry point, so do this last.
|
||||
boot_patchers
|
||||
.entry(&required_images["@gki_ramdisk"])
|
||||
.or_default()
|
||||
.push(Box::new(InitWrapperPatcher::new(cert_ota.clone())));
|
||||
|
||||
status!(
|
||||
"Patching boot images: {}",
|
||||
joined(sorted(boot_patchers.keys()))
|
||||
@@ -266,19 +277,12 @@ fn load_vbmeta_images(
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Check if a partition is critical to AVB's chain of trust. This is not
|
||||
/// foolproof and uses a heuristic based on AOSP's boot process. OEM-specific
|
||||
/// partitions may be equally important, but it's infeasible to list them all.
|
||||
/// Check if a partition is critical to AVB's chain of trust and is meant to be
|
||||
/// validated by the bootloader instead of Android. dm-verity partitions cannot
|
||||
/// be statically checked without causing false positives because the fstab
|
||||
/// might be using the avb_keys=/path/to/pubkey option.
|
||||
fn is_critical_to_avb(name: &str) -> bool {
|
||||
name.ends_with("boot")
|
||||
|| name.starts_with("odm")
|
||||
|| name.starts_with("system")
|
||||
|| name.starts_with("vbmeta")
|
||||
|| name.starts_with("vendor")
|
||||
|| name == "dtbo"
|
||||
|| name == "product"
|
||||
|| name == "pvmfw"
|
||||
|| name == "recovery"
|
||||
name.ends_with("boot") || name.starts_with("vbmeta")
|
||||
}
|
||||
|
||||
/// Check that all critical partitions within the payload are protected by a
|
||||
@@ -633,7 +637,8 @@ fn patch_ota_payload(
|
||||
payload: &(dyn ReadSeekReopen + Sync),
|
||||
writer: impl Write,
|
||||
external_images: &HashMap<String, PathBuf>,
|
||||
boot_partition: &str,
|
||||
rootpatch_partition: Option<&str>,
|
||||
otacerts_partition: Option<&str>,
|
||||
root_patcher: Option<Box<dyn BootImagePatcher + Send>>,
|
||||
clear_vbmeta_flags: bool,
|
||||
key_avb: &RsaPrivateKey,
|
||||
@@ -669,8 +674,8 @@ fn patch_ota_payload(
|
||||
// don't need to be modified.
|
||||
let required_images = get_required_images(
|
||||
&header_locked.manifest,
|
||||
boot_partition,
|
||||
root_patcher.is_some(),
|
||||
rootpatch_partition,
|
||||
otacerts_partition,
|
||||
)?;
|
||||
let vbmeta_images = required_images
|
||||
.iter()
|
||||
@@ -810,7 +815,8 @@ fn patch_ota_zip(
|
||||
zip_reader: &mut ZipArchive<impl Read + Seek>,
|
||||
mut zip_writer: &mut ZipWriter<impl Write>,
|
||||
external_images: &HashMap<String, PathBuf>,
|
||||
boot_partition: &str,
|
||||
rootpatch_partition: Option<&str>,
|
||||
otacerts_partition: Option<&str>,
|
||||
mut root_patch: Option<Box<dyn BootImagePatcher + Send>>,
|
||||
clear_vbmeta_flags: bool,
|
||||
key_avb: &RsaPrivateKey,
|
||||
@@ -930,7 +936,8 @@ fn patch_ota_zip(
|
||||
&payload_reader,
|
||||
&mut writer,
|
||||
external_images,
|
||||
boot_partition,
|
||||
rootpatch_partition,
|
||||
otacerts_partition,
|
||||
// There's only one payload in the OTA.
|
||||
root_patch.take(),
|
||||
clear_vbmeta_flags,
|
||||
@@ -1137,7 +1144,8 @@ pub fn patch_subcommand(cli: &PatchCli, cancel_signal: &AtomicBool) -> Result<()
|
||||
&mut zip_reader,
|
||||
&mut zip_writer,
|
||||
&external_images,
|
||||
&cli.boot_partition,
|
||||
Some(&cli.boot_partition),
|
||||
cli.otacerts_partition.as_deref(),
|
||||
root_patcher,
|
||||
cli.clear_vbmeta_flags,
|
||||
&key_avb,
|
||||
@@ -1238,7 +1246,11 @@ pub fn extract_subcommand(cli: &ExtractCli, cancel_signal: &AtomicBool) -> Resul
|
||||
.cloned(),
|
||||
);
|
||||
} else {
|
||||
let images = get_required_images(&header.manifest, &cli.boot_partition, true)?;
|
||||
let images = get_required_images(
|
||||
&header.manifest,
|
||||
Some(&cli.boot_partition),
|
||||
cli.otacerts_partition.as_deref(),
|
||||
)?;
|
||||
|
||||
if cli.boot_only {
|
||||
unique_images.insert(images["@rootpatch"].clone());
|
||||
@@ -1340,7 +1352,8 @@ pub fn verify_subcommand(cli: &VerifyCli, cancel_signal: &AtomicBool) -> Result<
|
||||
status!("Checking ramdisk's otacerts.zip");
|
||||
|
||||
let boot_image = {
|
||||
let partitions_by_type = get_partitions_by_type(&header.manifest)?;
|
||||
let partitions_by_type =
|
||||
get_required_images(&header.manifest, None, cli.otacerts_partition.as_deref())?;
|
||||
let path = format!("{}.img", partitions_by_type["@otacerts"]);
|
||||
let file = temp_dir
|
||||
.open(&path)
|
||||
@@ -1558,6 +1571,14 @@ pub struct PatchCli {
|
||||
help_heading = HEADING_OTHER
|
||||
)]
|
||||
pub boot_partition: String,
|
||||
|
||||
/// OTA certificates partition name.
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "PARTITION",
|
||||
help_heading = HEADING_OTHER
|
||||
)]
|
||||
pub otacerts_partition: Option<String>,
|
||||
}
|
||||
|
||||
/// Extract partition images from an OTA zip's payload.
|
||||
@@ -1582,6 +1603,10 @@ pub struct ExtractCli {
|
||||
/// Boot partition name.
|
||||
#[arg(long, value_name = "PARTITION", default_value = "@gki_ramdisk")]
|
||||
pub boot_partition: String,
|
||||
|
||||
/// OTA certificates partition name.
|
||||
#[arg(long, value_name = "PARTITION")]
|
||||
pub otacerts_partition: Option<String>,
|
||||
}
|
||||
|
||||
/// Verify signatures of an OTA.
|
||||
@@ -1606,6 +1631,10 @@ pub struct VerifyCli {
|
||||
/// valid, not that they are trusted.
|
||||
#[arg(long, value_name = "FILE", value_parser)]
|
||||
pub public_key_avb: Option<PathBuf>,
|
||||
|
||||
/// OTA certificates partition name.
|
||||
#[arg(long, value_name = "PARTITION")]
|
||||
pub otacerts_partition: Option<String>,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
|
||||
@@ -225,6 +225,23 @@ pub fn write_pem_cert_file(path: &Path, cert: &Certificate) -> Result<()> {
|
||||
write_pem_cert(writer, cert)
|
||||
}
|
||||
|
||||
/// Write PEM-encoded PKCS8 public key to a writer.
|
||||
pub fn write_pem_public_key(mut writer: impl Write, key: &RsaPublicKey) -> Result<()> {
|
||||
let data = key.to_public_key_pem(LineEnding::LF)?;
|
||||
|
||||
writer.write_all(data.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write PEM-encoded PKCS8 public key to a file.
|
||||
pub fn write_pem_public_key_file(path: &Path, key: &RsaPublicKey) -> Result<()> {
|
||||
let file = File::create(path)?;
|
||||
let writer = BufWriter::new(file);
|
||||
|
||||
write_pem_public_key(writer, key)
|
||||
}
|
||||
|
||||
/// Read PEM-encoded PKCS8 private key from a reader.
|
||||
pub fn read_pem_key(mut reader: impl Read, source: &PassphraseSource) -> Result<RsaPrivateKey> {
|
||||
let mut data = String::new();
|
||||
|
||||
@@ -35,7 +35,7 @@ use crate::{
|
||||
};
|
||||
|
||||
pub const VERSION_MAJOR: u32 = 1;
|
||||
pub const VERSION_MINOR: u32 = 2;
|
||||
pub const VERSION_MINOR: u32 = 3;
|
||||
pub const VERSION_SUB: u32 = 0;
|
||||
|
||||
pub const FOOTER_VERSION_MAJOR: u32 = 1;
|
||||
@@ -1109,8 +1109,13 @@ pub struct ChainPartitionDescriptor {
|
||||
pub partition_name: String,
|
||||
#[serde(with = "hex")]
|
||||
pub public_key: Vec<u8>,
|
||||
pub flags: u32,
|
||||
#[serde(with = "hex")]
|
||||
pub reserved: [u8; 64],
|
||||
pub reserved: [u8; 60],
|
||||
}
|
||||
|
||||
impl ChainPartitionDescriptor {
|
||||
pub const FLAG_DO_NOT_USE_AB: u32 = 1 << 0;
|
||||
}
|
||||
|
||||
impl fmt::Debug for ChainPartitionDescriptor {
|
||||
@@ -1119,6 +1124,7 @@ impl fmt::Debug for ChainPartitionDescriptor {
|
||||
.field("rollback_index_location", &self.rollback_index_location)
|
||||
.field("partition_name", &self.partition_name)
|
||||
.field("public_key", &hex::encode(&self.public_key))
|
||||
.field("flags", &self.flags)
|
||||
.field("reserved", &hex::encode(self.reserved))
|
||||
.finish()
|
||||
}
|
||||
@@ -1142,7 +1148,9 @@ impl<R: Read> FromReader<R> for ChainPartitionDescriptor {
|
||||
return Err(Error::FieldOutOfBounds("public_key_len"));
|
||||
}
|
||||
|
||||
let mut reserved = [0u8; 64];
|
||||
let flags = reader.read_u32::<BigEndian>()?;
|
||||
|
||||
let mut reserved = [0u8; 60];
|
||||
reader.read_exact(&mut reserved)?;
|
||||
|
||||
// Not NULL-terminated.
|
||||
@@ -1157,6 +1165,7 @@ impl<R: Read> FromReader<R> for ChainPartitionDescriptor {
|
||||
rollback_index_location,
|
||||
partition_name,
|
||||
public_key,
|
||||
flags,
|
||||
reserved,
|
||||
};
|
||||
|
||||
@@ -1177,6 +1186,7 @@ impl<W: Write> ToWriter<W> for ChainPartitionDescriptor {
|
||||
writer.write_u32::<BigEndian>(self.rollback_index_location)?;
|
||||
writer.write_u32::<BigEndian>(self.partition_name.len() as u32)?;
|
||||
writer.write_u32::<BigEndian>(self.public_key.len() as u32)?;
|
||||
writer.write_u32::<BigEndian>(self.flags)?;
|
||||
writer.write_all(&self.reserved)?;
|
||||
writer.write_all(self.partition_name.as_bytes())?;
|
||||
writer.write_all(&self.public_key)?;
|
||||
|
||||
@@ -3,6 +3,30 @@ vulnerability = "deny"
|
||||
unmaintained = "deny"
|
||||
yanked = "deny"
|
||||
notice = "deny"
|
||||
ignore = [
|
||||
# https://rustsec.org/advisories/RUSTSEC-2023-0071
|
||||
#
|
||||
# This is a side-channel vulnerability where secrets can be leaked to an
|
||||
# attacker that is able to measure the timing of a large number of RSA
|
||||
# operations. As of 2023-12-03, there is no released version of the rsa
|
||||
# crate that contains a fix.
|
||||
#
|
||||
# For avbroot specifically, this vulnerability is not too critical for a
|
||||
# couple reasons:
|
||||
#
|
||||
# 1. avbroot performs RSA signing only at the end of lengthy processes
|
||||
# that involve a lot of disk I/O. It's very expensive to run avbroot
|
||||
# the millions of times needed to capture a sufficient amount of timing
|
||||
# data.
|
||||
# 2. During a single run of avbroot, it will only perform RSA signing a
|
||||
# handful of times. To get sufficient measurements, the attacker would
|
||||
# need to rerun avbroot. If they are able to rerun avbroot, then they
|
||||
# are also able to just read and steal the private key directly.
|
||||
#
|
||||
# avbroot has no network capabilities, so this is not inherently remotely
|
||||
# exploitable.
|
||||
"RUSTSEC-2023-0071",
|
||||
]
|
||||
|
||||
[licenses]
|
||||
include-dev = true
|
||||
|
||||
+1
-1
@@ -148,7 +148,7 @@ fn download_range(
|
||||
.read_timeout(TIMEOUT)
|
||||
.header(
|
||||
"Range",
|
||||
&format!("bytes={}-{}", initial_range.start, initial_range.end - 1),
|
||||
format!("bytes={}-{}", initial_range.start, initial_range.end - 1),
|
||||
)
|
||||
.send()
|
||||
.and_then(|r| r.error_for_status())
|
||||
|
||||
+1
-1
@@ -117,7 +117,7 @@ fn strip_image(
|
||||
.context("Failed to load OTA payload header")?;
|
||||
|
||||
let required_images =
|
||||
avbroot::cli::ota::get_required_images(&header.manifest, "@gki_ramdisk", true)?
|
||||
avbroot::cli::ota::get_required_images(&header.manifest, Some("@gki_ramdisk"), None)?
|
||||
.into_values()
|
||||
.collect::<HashSet<_>>();
|
||||
let mut data_holes = vec![];
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/libs/
|
||||
/obj/
|
||||
@@ -0,0 +1,7 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := init
|
||||
LOCAL_SRC_FILES := init.c
|
||||
LOCAL_LDFLAGS := -static
|
||||
include $(BUILD_EXECUTABLE)
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Andrew Gunnerson
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef DEBUG_PREFIX
|
||||
#define DEBUG_PREFIX ""
|
||||
#endif
|
||||
|
||||
#define AVBROOT_DIR DEBUG_PREFIX "/avbroot"
|
||||
#define DEV_DIR DEBUG_PREFIX "/dev"
|
||||
#define SAFE_DIR DEBUG_PREFIX "/acct"
|
||||
#define STAGE1_DIR DEBUG_PREFIX "/first_stage_ramdisk"
|
||||
|
||||
#define DEV_KMSG DEV_DIR "/kmsg"
|
||||
#define DEV_NULL DEV_DIR "/null"
|
||||
|
||||
#define INIT DEBUG_PREFIX "/init"
|
||||
#define INIT_ORIG AVBROOT_DIR "/init.orig"
|
||||
|
||||
#define OTACERTS DEBUG_PREFIX "/system/etc/security/otacerts.zip"
|
||||
#define OTACERTS_AVBROOT AVBROOT_DIR "/otacerts.zip"
|
||||
#define OTACERTS_TMPFS SAFE_DIR "/otacerts.zip"
|
||||
|
||||
#define LOG(level, fmt, ...) \
|
||||
fprintf(stderr, "<%d>[%d] " fmt, level, getpid(), ##__VA_ARGS__)
|
||||
#define LOGE(...) LOG(3, __VA_ARGS__)
|
||||
#define LOGI(...) LOG(6, __VA_ARGS__)
|
||||
|
||||
// Best effort attempt to output to the kernel log.
|
||||
static void prepare_output()
|
||||
{
|
||||
mknod(DEV_NULL, S_IFCHR | 0666, makedev(1, 3));
|
||||
mknod(DEV_KMSG, S_IFCHR | 0600, makedev(1, 11));
|
||||
|
||||
int fd = open(DEV_NULL, O_RDWR);
|
||||
if (fd >= 0) {
|
||||
dup2(fd, STDIN_FILENO);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
fd = open(DEV_KMSG, O_WRONLY);
|
||||
if (fd >= 0) {
|
||||
dup2(fd, STDOUT_FILENO);
|
||||
dup2(fd, STDERR_FILENO);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
unlink(DEV_NULL);
|
||||
unlink(DEV_KMSG);
|
||||
|
||||
setlinebuf(stdout);
|
||||
}
|
||||
|
||||
static void auto_close(int *fd)
|
||||
{
|
||||
if (*fd >= 0) {
|
||||
int saved_errno = errno;
|
||||
close(*fd);
|
||||
errno = saved_errno;
|
||||
}
|
||||
}
|
||||
|
||||
static int copy_file(const char *source, const char *target)
|
||||
{
|
||||
__attribute__((cleanup(auto_close))) int fd_source =
|
||||
open(source, O_RDONLY | O_CLOEXEC);
|
||||
if (fd_source < 0) {
|
||||
LOGE("%s: Failed to open file: %s\n", source, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct stat sb;
|
||||
if (fstat(fd_source, &sb) < 0) {
|
||||
LOGE("%s: Failed to stat file: %s\n", source, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
__attribute__((cleanup(auto_close))) int fd_target =
|
||||
open(target, O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC,
|
||||
sb.st_mode & ~S_IFMT);
|
||||
if (fd_target < 0) {
|
||||
LOGE("%s: Failed to open file: %s\n", target, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint64_t remain = sb.st_size;
|
||||
|
||||
while (remain > 0) {
|
||||
size_t to_copy = remain > 0x7ffff000 ? 0x7ffff000 : remain;
|
||||
|
||||
ssize_t n = sendfile(fd_target, fd_source, NULL, to_copy);
|
||||
if (n < 0) {
|
||||
LOGE("%s -> %s: Failed to copy data: %s\n",
|
||||
source, target, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
remain -= n;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Mount a tmpfs at SAFE_DIR and copy the files we need to it. AOSP init will
|
||||
// preserve mount points when switching roots (first /first_stage_ramdisk and
|
||||
// then the system partition), so we'll be able to access the files during the
|
||||
// stage 1 -> stage 2 transition. The safe directory must be a directory that
|
||||
// exists in the system partition and is unused for stage 1 init.
|
||||
static int prepare_safe_dir()
|
||||
{
|
||||
int flags = MS_NOSUID | MS_NODEV | MS_NOEXEC;
|
||||
|
||||
if (mount("avbroot", SAFE_DIR, "tmpfs", flags, "mode=755") < 0) {
|
||||
LOGE("%s: Failed to mount tmpfs: %s\n", SAFE_DIR, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (copy_file(OTACERTS_AVBROOT, OTACERTS_TMPFS) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (mount(NULL, SAFE_DIR, NULL, MS_REMOUNT | MS_RDONLY | flags, NULL) < 0) {
|
||||
LOGE("%s: Failed to remount read-only: %s\n",
|
||||
SAFE_DIR, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Trace the parent process across execve() calls until otacerts.zip exists.
|
||||
// Then, bind mount the replacement and detach. Only the parent (TID 1) needs to
|
||||
// be traced. All other threads and child processes are irrelevant since we only
|
||||
// care about the transition point between stage 1 and stage 2 init.
|
||||
static int trace_parent()
|
||||
{
|
||||
bool first_group_stop = true;
|
||||
|
||||
pid_t parent_pid = getppid();
|
||||
LOGI("Tracing parent PID: %d\n", parent_pid);
|
||||
|
||||
long options = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC;
|
||||
|
||||
if (ptrace(PTRACE_SEIZE, parent_pid, NULL, options) < 0) {
|
||||
LOGE("Failed to trace process: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
int status;
|
||||
if (waitpid(parent_pid, &status, __WALL | __WNOTHREAD) == -1) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
} else {
|
||||
LOGE("waitpid failed: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __GLIBC__
|
||||
enum __ptrace_request
|
||||
#else
|
||||
int
|
||||
#endif
|
||||
action = PTRACE_CONT;
|
||||
int forward_signal = 0;
|
||||
|
||||
if (WIFEXITED(status)) {
|
||||
LOGE("%d exited with status %d\n", parent_pid, WEXITSTATUS(status));
|
||||
break;
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
LOGE("%d killed by signal %d\n", parent_pid, WTERMSIG(status));
|
||||
break;
|
||||
} else if (WIFSTOPPED(status)) {
|
||||
int ptrace_event = status >> 16;
|
||||
int signal = WSTOPSIG(status);
|
||||
|
||||
switch (ptrace_event) {
|
||||
case PTRACE_EVENT_EXEC: {
|
||||
LOGI("Tracee is about to exec\n");
|
||||
|
||||
// If /first_stage_ramdisk exists, then init hasn't switched
|
||||
// roots yet. otacerts.zip may still exist on devices that
|
||||
// use shared ramdisks for normal and recovery boot.
|
||||
if ((access(STAGE1_DIR, F_OK) != 0 && errno == ENOENT)
|
||||
&& access(OTACERTS, F_OK) == 0) {
|
||||
LOGI("Conditions satisfied; applying override\n");
|
||||
|
||||
action = PTRACE_DETACH;
|
||||
|
||||
if (mount(OTACERTS_TMPFS, OTACERTS, NULL,
|
||||
MS_BIND | MS_RDONLY, "") < 0) {
|
||||
LOGE("Failed to bind mount %s -> %s: %s\n",
|
||||
OTACERTS_TMPFS, OTACERTS, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (umount2(SAFE_DIR, MNT_DETACH) < 0) {
|
||||
LOGE("Failed to detach mount %s: %s\n",
|
||||
SAFE_DIR, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
LOGE("Conditions not yet satisfied\n");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PTRACE_EVENT_STOP: {
|
||||
if (signal == SIGSTOP || signal == SIGTSTP
|
||||
|| signal == SIGTTIN || signal == SIGTTOU) {
|
||||
if (first_group_stop) {
|
||||
LOGI("Resuming tracee\n");
|
||||
kill(parent_pid, SIGCONT);
|
||||
first_group_stop = false;
|
||||
} else {
|
||||
action = PTRACE_LISTEN;
|
||||
}
|
||||
} else {
|
||||
// We get spurious SIGTRAP signals when SIGCONT'ing a
|
||||
// process. rr seem to be running into this as well:
|
||||
// https://github.com/mozilla/rr/issues/2095
|
||||
// strace handles PTRACE_EVENT_STOP + non-group-stop signal
|
||||
// by restarting the process with PTRACE_SYSCALL:
|
||||
// https://github.com/strace/strace/blob/b1e1eb7731e50900bb4591a3a71b96ab37e106a8/strace.c#L2360
|
||||
// https://github.com/strace/strace/blob/b1e1eb7731e50900bb4591a3a71b96ab37e106a8/strace.c#L2408-L2409
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
if (signal == (SIGTRAP | 0x80)) {
|
||||
// Syscall enter/exit stop
|
||||
} else {
|
||||
// Signal delivery stop
|
||||
forward_signal = signal;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ptrace(action, parent_pid, NULL, forward_signal) < 0) {
|
||||
LOGE("Failed to perform action %d (signal %d): %s\n",
|
||||
action, forward_signal, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (action == PTRACE_DETACH) {
|
||||
LOGI("Detaching tracee\n");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LOGE("Invalid waitpid status: 0x%x\n", status);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int prepare_tracing()
|
||||
{
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
LOGE("Failed to fork: %s\n", strerror(errno));
|
||||
return -1;
|
||||
} else if (pid == 0) {
|
||||
int ret = trace_parent();
|
||||
if (ret < 0) {
|
||||
// Make sure parent doesn't hang forever.
|
||||
kill(getppid(), SIGCONT);
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
_exit(EXIT_SUCCESS);
|
||||
} else {
|
||||
LOGI("Waiting for tracer to be ready\n");
|
||||
kill(getpid(), SIGSTOP);
|
||||
LOGI("Tracer is ready\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[], char *envp[])
|
||||
{
|
||||
(void) argc;
|
||||
|
||||
prepare_output();
|
||||
|
||||
if (rename(INIT_ORIG, INIT) < 0) {
|
||||
LOGE("Failed to restore original init: %s\n", strerror(errno));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (prepare_safe_dir() < 0) {
|
||||
LOGE("Failed to set up safe directory %s\n", SAFE_DIR);
|
||||
} else if (prepare_tracing() < 0) {
|
||||
LOGE("Failed to set up tracer child process\n");
|
||||
} else {
|
||||
LOGI("Exec hook is ready\n");
|
||||
}
|
||||
|
||||
LOGI("Executing %s\n", INIT);
|
||||
|
||||
execve(INIT, argv, envp);
|
||||
LOGE("Failed to exec %s: %s\n", INIT, strerror(errno));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
Reference in New Issue
Block a user