mirror of
https://github.com/chenxiaolong/avbroot.git
synced 2026-07-03 14:05:11 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 461392c226 |
+80
-13
@@ -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
|
||||
|
||||
@@ -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::{
|
||||
@@ -127,7 +127,7 @@ 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());
|
||||
}
|
||||
}
|
||||
@@ -214,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()))
|
||||
|
||||
@@ -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