From c313812129c57b33510e70726b8695149ea66aa3 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Wed, 20 Aug 2025 22:25:19 -0700 Subject: [PATCH] Simplify magiskboot FFI --- native/src/Android.mk | 1 - native/src/boot/bootimg.cpp | 100 ++++++++++++++++------------- native/src/boot/bootimg.hpp | 7 +- native/src/boot/cli.rs | 97 +++++++++++++++++----------- native/src/boot/format.cpp | 72 --------------------- native/src/boot/format.hpp | 15 +---- native/src/boot/format.rs | 35 ++++++---- native/src/boot/lib.rs | 50 +++++++-------- native/src/boot/magiskboot.hpp | 16 +---- native/src/boot/sign.rs | 114 ++++++++++++++++----------------- 10 files changed, 230 insertions(+), 277 deletions(-) delete mode 100644 native/src/boot/format.cpp diff --git a/native/src/Android.mk b/native/src/Android.mk index 6e0e00ea1..b80b9c9d5 100644 --- a/native/src/Android.mk +++ b/native/src/Android.mk @@ -87,7 +87,6 @@ LOCAL_STATIC_LIBRARIES := \ LOCAL_SRC_FILES := \ boot/bootimg.cpp \ - boot/format.cpp \ boot/boot-rs.cpp LOCAL_LDFLAGS := -static diff --git a/native/src/boot/bootimg.cpp b/native/src/boot/bootimg.cpp index cdf5ebe9c..d993b398c 100644 --- a/native/src/boot/bootimg.cpp +++ b/native/src/boot/bootimg.cpp @@ -48,6 +48,43 @@ static bool check_env(const char *name) { return val != nullptr && val == "true"sv; } +FileFormat check_fmt(const void *buf, size_t len) { + if (CHECKED_MATCH(CHROMEOS_MAGIC)) { + return FileFormat::CHROMEOS; + } else if (CHECKED_MATCH(BOOT_MAGIC)) { + return FileFormat::AOSP; + } else if (CHECKED_MATCH(VENDOR_BOOT_MAGIC)) { + return FileFormat::AOSP_VENDOR; + } else if (CHECKED_MATCH(GZIP1_MAGIC) || CHECKED_MATCH(GZIP2_MAGIC)) { + return FileFormat::GZIP; + } else if (CHECKED_MATCH(LZOP_MAGIC)) { + return FileFormat::LZOP; + } else if (CHECKED_MATCH(XZ_MAGIC)) { + return FileFormat::XZ; + } else if (len >= 13 && memcmp(buf, "\x5d\x00\x00", 3) == 0 + && (((char *)buf)[12] == '\xff' || ((char *)buf)[12] == '\x00')) { + return FileFormat::LZMA; + } else if (CHECKED_MATCH(BZIP_MAGIC)) { + return FileFormat::BZIP2; + } else if (CHECKED_MATCH(LZ41_MAGIC) || CHECKED_MATCH(LZ42_MAGIC)) { + return FileFormat::LZ4; + } else if (CHECKED_MATCH(LZ4_LEG_MAGIC)) { + return FileFormat::LZ4_LEGACY; + } else if (CHECKED_MATCH(MTK_MAGIC)) { + return FileFormat::MTK; + } else if (CHECKED_MATCH(DTB_MAGIC)) { + return FileFormat::DTB; + } else if (CHECKED_MATCH(DHTB_MAGIC)) { + return FileFormat::DHTB; + } else if (CHECKED_MATCH(TEGRABLOB_MAGIC)) { + return FileFormat::BLOB; + } else if (len >= 0x28 && memcmp(&((char *)buf)[0x24], ZIMAGE_MAGIC, 4) == 0) { + return FileFormat::ZIMAGE; + } else { + return FileFormat::UNKNOWN; + } +} + void dyn_img_hdr::print() const { uint32_t ver = header_version(); fprintf(stderr, "%-*s [%u]\n", PADDING, "HEADER_VER", ver); @@ -472,7 +509,7 @@ bool boot_img::parse_image(const uint8_t *p, FileFormat type) { fprintf(stderr, "! Could not find zImage piggy, keeping raw kernel\n"); } } - fprintf(stderr, "%-*s [%s]\n", PADDING, "KERNEL_FMT", fmt2name[k_fmt]); + fprintf(stderr, "%-*s [%s]\n", PADDING, "KERNEL_FMT", fmt2name(k_fmt)); } if (auto size = hdr->ramdisk_size()) { if (hdr->vendor_ramdisk_table_size()) { @@ -493,7 +530,7 @@ bool boot_img::parse_image(const uint8_t *p, FileFormat type) { fprintf(stderr, "%-*s name=[%s] type=[%s] size=[%u] fmt=[%s]\n", PADDING, "VND_RAMDISK", it.ramdisk_name, vendor_ramdisk_type(it.ramdisk_type), - it.ramdisk_size, fmt2name[fmt]); + it.ramdisk_size, fmt2name(fmt)); } } else { r_fmt = check_fmt_lg(ramdisk, size); @@ -507,12 +544,12 @@ bool boot_img::parse_image(const uint8_t *p, FileFormat type) { hdr->ramdisk_size() -= sizeof(mtk_hdr); r_fmt = check_fmt_lg(ramdisk, hdr->ramdisk_size()); } - fprintf(stderr, "%-*s [%s]\n", PADDING, "RAMDISK_FMT", fmt2name[r_fmt]); + fprintf(stderr, "%-*s [%s]\n", PADDING, "RAMDISK_FMT", fmt2name(r_fmt)); } } if (auto size = hdr->extra_size()) { e_fmt = check_fmt_lg(extra, size); - fprintf(stderr, "%-*s [%s]\n", PADDING, "EXTRA_FMT", fmt2name[e_fmt]); + fprintf(stderr, "%-*s [%s]\n", PADDING, "EXTRA_FMT", fmt2name(e_fmt)); } if (tail.sz()) { @@ -523,8 +560,7 @@ bool boot_img::parse_image(const uint8_t *p, FileFormat type) { } else if (tail.sz() >= 16 && BUFFER_MATCH(tail.buf(), LG_BUMP_MAGIC)) { fprintf(stderr, "LG_BUMP_IMAGE\n"); flags[LG_BUMP_FLAG] = true; - } else if (!(tail.sz() >= 4 && BUFFER_MATCH(tail.buf(), AVB_MAGIC)) && verify()) { - // Check if the image is avb 1.0 signed + } else if (verify()) { fprintf(stderr, "AVB1_SIGNED\n"); flags[AVB1_SIGNED_FLAG] = true; } @@ -532,13 +568,13 @@ bool boot_img::parse_image(const uint8_t *p, FileFormat type) { // Find AVB footer const void *footer = tail.buf() + tail.sz() - sizeof(AvbFooter); if (BUFFER_MATCH(footer, AVB_FOOTER_MAGIC)) { - avb_footer = reinterpret_cast(footer); + avb_footer = static_cast(footer); // Double check if meta header exists const void *meta = base_addr + __builtin_bswap64(avb_footer->vbmeta_offset); if (BUFFER_MATCH(meta, AVB_MAGIC)) { fprintf(stderr, "VBMETA\n"); flags[AVB_FLAG] = true; - vbmeta = reinterpret_cast(meta); + vbmeta = static_cast(meta); } } } @@ -547,10 +583,6 @@ bool boot_img::parse_image(const uint8_t *p, FileFormat type) { return true; } -bool boot_img::verify(const char *cert) const { - return rust::verify_boot_image(*this, cert); -} - int split_image_dtb(rust::Utf8CStr filename, bool skip_decomp) { mmap_data img(filename.data()); @@ -801,7 +833,7 @@ void repack(rust::Utf8CStr src_img, rust::Utf8CStr out_img, bool skip_comp) { // A v4 boot image ramdisk will have to be merged with other vendor ramdisks, // and they have to use the exact same compression method. v4 GKIs are required to // use lz4 (legacy), so hardcode the format here. - fprintf(stderr, "RAMDISK_FMT: [%s] -> [%s]\n", fmt2name[r_fmt], fmt2name[FileFormat::LZ4_LEGACY]); + fprintf(stderr, "RAMDISK_FMT: [%s] -> [%s]\n", fmt2name(r_fmt), fmt2name(FileFormat::LZ4_LEGACY)); r_fmt = FileFormat::LZ4_LEGACY; } if (!skip_comp && !COMPRESSED_ANY(check_fmt(m.buf(), m.sz())) && COMPRESSED(r_fmt)) { @@ -987,7 +1019,7 @@ void repack(rust::Utf8CStr src_img, rust::Utf8CStr out_img, bool skip_comp) { // Sign the image after we finish patching the boot image if (boot.flags[AVB1_SIGNED_FLAG]) { byte_view payload(out.buf() + off.header, off.total - off.header); - auto sig = rust::sign_boot_image(payload, "/boot", nullptr, nullptr); + auto sig = sign_payload(payload); if (!sig.empty()) { lseek(fd, off.total, SEEK_SET); xwrite(fd, sig.data(), sig.size()); @@ -997,33 +1029,15 @@ void repack(rust::Utf8CStr src_img, rust::Utf8CStr out_img, bool skip_comp) { close(fd); } -int verify(rust::Utf8CStr image, const char *cert) { - const boot_img boot(image.data()); - if (cert == nullptr) { - // Boot image parsing already checks if the image is signed - return boot.flags[AVB1_SIGNED_FLAG] ? 0 : 1; - } else { - // Provide a custom certificate and re-verify - return boot.verify(cert) ? 0 : 1; - } -} - -int sign(rust::Utf8CStr image, rust::Utf8CStr name, const char *cert, const char *key) { - const boot_img boot(image.data()); - auto sig = rust::sign_boot_image(boot.payload, name.data(), cert, key); - if (sig.empty()) - return 1; - - auto eof = boot.tail.buf() - boot.map.buf(); - int fd = xopen(image.data(), O_WRONLY | O_CLOEXEC); - if (lseek(fd, eof, SEEK_SET) != eof || xwrite(fd, sig.data(), sig.size()) != sig.size()) { - close(fd); - return 1; - } - if (auto off = lseek(fd, 0, SEEK_CUR); off < boot.map.sz()) { - // Wipe out rest of tail - write_zero(fd, boot.map.sz() - off); - } - close(fd); - return 0; +void cleanup() { + unlink(HEADER_FILE); + unlink(KERNEL_FILE); + unlink(RAMDISK_FILE); + unlink(SECOND_FILE); + unlink(KER_DTB_FILE); + unlink(EXTRA_FILE); + unlink(RECV_DTBO_FILE); + unlink(DTB_FILE); + unlink(BOOTCONFIG_FILE); + rm_rf(VND_RAMDISK_DIR); } diff --git a/native/src/boot/bootimg.hpp b/native/src/boot/bootimg.hpp index 2e09f4afd..e167251ea 100644 --- a/native/src/boot/bootimg.hpp +++ b/native/src/boot/bootimg.hpp @@ -676,7 +676,12 @@ struct boot_img { std::pair create_hdr(const uint8_t *addr, FileFormat type); // Rust FFI + static std::unique_ptr create(rust::Utf8CStr name) { return std::make_unique(name.c_str()); } rust::Slice get_payload() const { return payload; } rust::Slice get_tail() const { return tail; } - bool verify(const char *cert = nullptr) const; + bool is_signed() const { return flags[AVB1_SIGNED_FLAG]; } + uint64_t tail_off() const { return tail.buf() - map.buf(); } + + // Implemented in Rust + bool verify() const noexcept; }; diff --git a/native/src/boot/cli.rs b/native/src/boot/cli.rs index dd6864d31..30688f50b 100644 --- a/native/src/boot/cli.rs +++ b/native/src/boot/cli.rs @@ -1,16 +1,17 @@ use crate::compress::{compress, decompress}; use crate::cpio::{cpio_commands, print_cpio_usage}; use crate::dtb::{DtbAction, dtb_commands, print_dtb_usage}; -use crate::ffi::{FileFormat, cleanup, repack, sign, split_image_dtb, unpack, verify}; +use crate::ffi::{BootImage, FileFormat, cleanup, repack, split_image_dtb, unpack}; use crate::patch::hexpatch; use crate::payload::extract_boot_from_payload; -use crate::sign::sha1_hash; +use crate::sign::{sha1_hash, sign_boot_image}; use argh::FromArgs; use base::{ - CmdArgs, EarlyExitExt, LoggedResult, MappedFile, ResultExt, Utf8CStr, cmdline_logging, cstr, - libc::umask, log_err, + CmdArgs, EarlyExitExt, LoggedResult, MappedFile, ResultExt, Utf8CStr, WriteExt, + cmdline_logging, cstr, libc, libc::umask, log_err, }; use std::ffi::c_char; +use std::io::{Seek, SeekFrom, Write}; use std::str::FromStr; #[derive(FromArgs)] @@ -159,7 +160,7 @@ fn print_usage(cmd: &str) { eprintln!( r#"MagiskBoot - Boot Image Modification Tool -Usage: {} [args...] +Usage: {0} [args...] Supported actions: unpack [-n] [-h] @@ -259,6 +260,44 @@ Supported actions: ); } +fn verify_cmd(image: &Utf8CStr, cert: Option<&Utf8CStr>) -> bool { + let image = BootImage::new(image); + match cert { + None => { + // Boot image parsing already checks if the image is signed + image.is_signed() + } + Some(_) => { + // Provide a custom certificate and re-verify + image.verify(cert).is_ok() + } + } +} + +fn sign_cmd( + image: &Utf8CStr, + name: Option<&Utf8CStr>, + cert: Option<&Utf8CStr>, + key: Option<&Utf8CStr>, +) -> LoggedResult<()> { + let img = BootImage::new(image); + let name = name.unwrap_or(cstr!("/boot")); + let sig = sign_boot_image(img.payload(), name, cert, key)?; + let tail_off = img.tail_off(); + drop(img); + let mut fd = image.open(libc::O_WRONLY | libc::O_CLOEXEC)?; + fd.seek(SeekFrom::Start(tail_off))?; + fd.write_all(&sig)?; + let current = fd.stream_position()?; + let eof = fd.seek(SeekFrom::End(0))?; + if eof > current { + // Zero out rest of the file + fd.seek(SeekFrom::Start(current))?; + fd.write_zeros((eof - current) as usize)?; + } + Ok(()) +} + #[unsafe(no_mangle)] pub extern "C" fn main(argc: i32, argv: *const *const c_char, _envp: *const *const c_char) -> i32 { cmdline_logging(); @@ -302,40 +341,24 @@ pub extern "C" fn main(argc: i32, argv: *const *const c_char, _envp: *const *con no_compress, ); } - Action::Verify(Verify { - ref mut img, - ref mut cert, - }) => { - return unsafe { - verify( - Utf8CStr::from_string(img), - cert.as_mut() - .map(|x| Utf8CStr::from_string(x).as_ptr()) - .unwrap_or(std::ptr::null()), - ) + Action::Verify(Verify { mut img, mut cert }) => { + return if verify_cmd( + Utf8CStr::from_string(&mut img), + cert.as_mut().map(Utf8CStr::from_string), + ) { + 0 + } else { + 1 }; } - Action::Sign(Sign { - ref mut img, - ref mut args, - }) => { - let (pem, pk8) = match args.get_mut(1..=2) { - Some([pem, pk8]) => ( - Utf8CStr::from_string(pem).as_ptr(), - Utf8CStr::from_string(pk8).as_ptr(), - ), - _ => (std::ptr::null(), std::ptr::null()), - }; - return unsafe { - sign( - Utf8CStr::from_string(img), - args.first_mut() - .map(Utf8CStr::from_string) - .unwrap_or(cstr!("/boot")), - pem, - pk8, - ) - }; + Action::Sign(Sign { mut img, mut args }) => { + let mut iter = args.iter_mut(); + sign_cmd( + Utf8CStr::from_string(&mut img), + iter.next().map(Utf8CStr::from_string), + iter.next().map(Utf8CStr::from_string), + iter.next().map(Utf8CStr::from_string), + )?; } Action::Extract(Extract { ref payload, diff --git a/native/src/boot/format.cpp b/native/src/boot/format.cpp deleted file mode 100644 index 5f1e5fb7e..000000000 --- a/native/src/boot/format.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "boot-rs.hpp" -#include "format.hpp" - -Fmt2Name fmt2name; - -#define CHECKED_MATCH(s) (len >= (sizeof(s) - 1) && BUFFER_MATCH(buf, s)) - -FileFormat check_fmt(const void *buf, size_t len) { - if (CHECKED_MATCH(CHROMEOS_MAGIC)) { - return FileFormat::CHROMEOS; - } else if (CHECKED_MATCH(BOOT_MAGIC)) { - return FileFormat::AOSP; - } else if (CHECKED_MATCH(VENDOR_BOOT_MAGIC)) { - return FileFormat::AOSP_VENDOR; - } else if (CHECKED_MATCH(GZIP1_MAGIC) || CHECKED_MATCH(GZIP2_MAGIC)) { - return FileFormat::GZIP; - } else if (CHECKED_MATCH(LZOP_MAGIC)) { - return FileFormat::LZOP; - } else if (CHECKED_MATCH(XZ_MAGIC)) { - return FileFormat::XZ; - } else if (len >= 13 && memcmp(buf, "\x5d\x00\x00", 3) == 0 - && (((char *)buf)[12] == '\xff' || ((char *)buf)[12] == '\x00')) { - return FileFormat::LZMA; - } else if (CHECKED_MATCH(BZIP_MAGIC)) { - return FileFormat::BZIP2; - } else if (CHECKED_MATCH(LZ41_MAGIC) || CHECKED_MATCH(LZ42_MAGIC)) { - return FileFormat::LZ4; - } else if (CHECKED_MATCH(LZ4_LEG_MAGIC)) { - return FileFormat::LZ4_LEGACY; - } else if (CHECKED_MATCH(MTK_MAGIC)) { - return FileFormat::MTK; - } else if (CHECKED_MATCH(DTB_MAGIC)) { - return FileFormat::DTB; - } else if (CHECKED_MATCH(DHTB_MAGIC)) { - return FileFormat::DHTB; - } else if (CHECKED_MATCH(TEGRABLOB_MAGIC)) { - return FileFormat::BLOB; - } else if (len >= 0x28 && memcmp(&((char *)buf)[0x24], ZIMAGE_MAGIC, 4) == 0) { - return FileFormat::ZIMAGE; - } else { - return FileFormat::UNKNOWN; - } -} - -const char *Fmt2Name::operator[](FileFormat fmt) { - switch (fmt) { - case FileFormat::GZIP: - return "gzip"; - case FileFormat::ZOPFLI: - return "zopfli"; - case FileFormat::LZOP: - return "lzop"; - case FileFormat::XZ: - return "xz"; - case FileFormat::LZMA: - return "lzma"; - case FileFormat::BZIP2: - return "bzip2"; - case FileFormat::LZ4: - return "lz4"; - case FileFormat::LZ4_LEGACY: - return "lz4_legacy"; - case FileFormat::LZ4_LG: - return "lz4_lg"; - case FileFormat::DTB: - return "dtb"; - case FileFormat::ZIMAGE: - return "zimage"; - default: - return "raw"; - } -} diff --git a/native/src/boot/format.hpp b/native/src/boot/format.hpp index c48ff91f0..65eb178b8 100644 --- a/native/src/boot/format.hpp +++ b/native/src/boot/format.hpp @@ -1,7 +1,5 @@ #pragma once -#include - enum class FileFormat : ::std::uint8_t; #define COMPRESSED(fmt) ((+fmt) >= +FileFormat::GZIP && (+fmt) < +FileFormat::LZOP) @@ -9,6 +7,7 @@ enum class FileFormat : ::std::uint8_t; #define BUFFER_MATCH(buf, s) (memcmp(buf, s, sizeof(s) - 1) == 0) #define BUFFER_CONTAIN(buf, sz, s) (memmem(buf, sz, s, sizeof(s) - 1) != nullptr) +#define CHECKED_MATCH(s) (len >= (sizeof(s) - 1) && BUFFER_MATCH(buf, s)) #define BOOT_MAGIC "ANDROID!" #define VENDOR_BOOT_MAGIC "VNDRBOOT" @@ -41,20 +40,8 @@ enum class FileFormat : ::std::uint8_t; #define AVB_MAGIC "AVB0" #define ZIMAGE_MAGIC "\x18\x28\x6f\x01" -class Fmt2Name { -public: - const char *operator[](FileFormat fmt); -}; - -class Name2Fmt { -public: - FileFormat operator[](std::string_view name); -}; - FileFormat check_fmt(const void *buf, size_t len); static inline FileFormat check_fmt(rust::Slice bytes) { return check_fmt(bytes.data(), bytes.size()); } - -extern Fmt2Name fmt2name; diff --git a/native/src/boot/format.rs b/native/src/boot/format.rs index bdb7f80ba..520459a3d 100644 --- a/native/src/boot/format.rs +++ b/native/src/boot/format.rs @@ -1,4 +1,5 @@ use crate::ffi::FileFormat; +use base::{Utf8CStr, cstr, libc}; use std::fmt::{Display, Formatter}; use std::str::FromStr; @@ -22,23 +23,33 @@ impl FromStr for FileFormat { impl Display for FileFormat { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(self.to_cstr()) + } +} + +impl FileFormat { + fn to_cstr(&self) -> &'static Utf8CStr { match *self { - Self::GZIP => write!(f, "gzip"), - Self::ZOPFLI => write!(f, "zopfli"), - Self::LZOP => write!(f, "lzop"), - Self::XZ => write!(f, "xz"), - Self::LZMA => write!(f, "lzma"), - Self::BZIP2 => write!(f, "bzip2"), - Self::LZ4 => write!(f, "lz4"), - Self::LZ4_LEGACY => write!(f, "lz4_legacy"), - Self::LZ4_LG => write!(f, "lz4_lg"), - Self::DTB => write!(f, "dtb"), - Self::ZIMAGE => write!(f, "zimage"), - _ => write!(f, "raw"), + Self::GZIP => cstr!("gzip"), + Self::ZOPFLI => cstr!("zopfli"), + Self::LZOP => cstr!("lzop"), + Self::XZ => cstr!("xz"), + Self::LZMA => cstr!("lzma"), + Self::BZIP2 => cstr!("bzip2"), + Self::LZ4 => cstr!("lz4"), + Self::LZ4_LEGACY => cstr!("lz4_legacy"), + Self::LZ4_LG => cstr!("lz4_lg"), + Self::DTB => cstr!("dtb"), + Self::ZIMAGE => cstr!("zimage"), + _ => cstr!("raw"), } } } +pub fn fmt2name(fmt: FileFormat) -> *const libc::c_char { + fmt.to_cstr().as_ptr() +} + impl FileFormat { pub fn ext(&self) -> &'static str { match *self { diff --git a/native/src/boot/lib.rs b/native/src/boot/lib.rs index 61d22b746..82b67f6ce 100644 --- a/native/src/boot/lib.rs +++ b/native/src/boot/lib.rs @@ -5,7 +5,8 @@ pub use base; use compress::{compress_bytes, decompress_bytes}; -use sign::{SHA, get_sha, sha256_hash, sign_boot_image, verify_boot_image}; +use format::fmt2name; +use sign::{SHA, get_sha, sha256_hash, sign_payload_for_cxx}; use std::env; mod compress; @@ -59,25 +60,10 @@ pub mod ffi { include!("format.hpp"); fn check_fmt(buf: &[u8]) -> FileFormat; - include!("bootimg.hpp"); - #[cxx_name = "boot_img"] - type BootImage; - #[cxx_name = "get_payload"] - fn payload(self: &BootImage) -> &[u8]; - #[cxx_name = "get_tail"] - fn tail(self: &BootImage) -> &[u8]; - include!("magiskboot.hpp"); fn cleanup(); fn unpack(image: Utf8CStrRef, skip_decomp: bool, hdr: bool) -> i32; fn repack(src_img: Utf8CStrRef, out_img: Utf8CStrRef, skip_comp: bool); - unsafe fn verify(image: Utf8CStrRef, cert: *const c_char) -> i32; - unsafe fn sign( - image: Utf8CStrRef, - name: Utf8CStrRef, - cert: *const c_char, - key: *const c_char, - ) -> i32; fn split_image_dtb(filename: Utf8CStrRef, skip_decomp: bool) -> i32; } @@ -91,18 +77,32 @@ pub mod ffi { fn compress_bytes(format: FileFormat, in_bytes: &[u8], out_fd: i32); fn decompress_bytes(format: FileFormat, in_bytes: &[u8], out_fd: i32); + fn fmt2name(fmt: FileFormat) -> *const c_char; + + #[cxx_name = "sign_payload"] + fn sign_payload_for_cxx(payload: &[u8]) -> Vec; } - #[namespace = "rust"] - #[allow(unused_unsafe)] + // BootImage FFI + unsafe extern "C++" { + include!("bootimg.hpp"); + #[cxx_name = "boot_img"] + type BootImage; + + #[cxx_name = "get_payload"] + fn payload(self: &BootImage) -> &[u8]; + #[cxx_name = "get_tail"] + fn tail(self: &BootImage) -> &[u8]; + fn is_signed(self: &BootImage) -> bool; + fn tail_off(self: &BootImage) -> u64; + + #[Self = BootImage] + #[cxx_name = "create"] + fn new(img: Utf8CStrRef) -> UniquePtr; + } extern "Rust" { - unsafe fn sign_boot_image( - payload: &[u8], - name: *const c_char, - cert: *const c_char, - key: *const c_char, - ) -> Vec; - unsafe fn verify_boot_image(img: &BootImage, cert: *const c_char) -> bool; + #[cxx_name = "verify"] + fn verify_for_cxx(self: &BootImage) -> bool; } } diff --git a/native/src/boot/magiskboot.hpp b/native/src/boot/magiskboot.hpp index ba850dee8..5ea05ce89 100644 --- a/native/src/boot/magiskboot.hpp +++ b/native/src/boot/magiskboot.hpp @@ -14,19 +14,5 @@ int unpack(rust::Utf8CStr image, bool skip_decomp = false, bool hdr = false); void repack(rust::Utf8CStr src_img, rust::Utf8CStr out_img, bool skip_comp = false); -int verify(rust::Utf8CStr image, const char *cert); -int sign(rust::Utf8CStr image, rust::Utf8CStr name, const char *cert, const char *key); int split_image_dtb(rust::Utf8CStr filename, bool skip_decomp = false); - -inline void cleanup() { - unlink(HEADER_FILE); - unlink(KERNEL_FILE); - unlink(RAMDISK_FILE); - unlink(SECOND_FILE); - unlink(KER_DTB_FILE); - unlink(EXTRA_FILE); - unlink(RECV_DTBO_FILE); - unlink(DTB_FILE); - unlink(BOOTCONFIG_FILE); - rm_rf(VND_RAMDISK_DIR); -} +void cleanup(); diff --git a/native/src/boot/sign.rs b/native/src/boot/sign.rs index 08b4d6f17..5fab4bcac 100644 --- a/native/src/boot/sign.rs +++ b/native/src/boot/sign.rs @@ -25,8 +25,7 @@ use x509_cert::der::Any; use x509_cert::der::asn1::{OctetString, PrintableString}; use x509_cert::spki::AlgorithmIdentifier; -use base::libc::c_char; -use base::{LoggedResult, MappedFile, ResultExt, StrErr, Utf8CStr, log_err}; +use base::{LoggedError, LoggedResult, MappedFile, ResultExt, Utf8CStr, cstr, log_err}; use crate::ffi::BootImage; @@ -265,23 +264,27 @@ impl BootSignature { } } -pub fn verify_boot_image(img: &BootImage, cert: *const c_char) -> bool { - let res: LoggedResult<()> = try { - let tail = img.tail(); +impl BootImage { + pub fn verify(&self, cert: Option<&Utf8CStr>) -> LoggedResult<()> { + let tail = self.tail(); + if tail.starts_with(b"AVB0") { + return Err(LoggedError::default()); + } + // Don't use BootSignature::from_der because tail might have trailing zeros let mut reader = SliceReader::new(tail)?; let mut sig = BootSignature::decode(&mut reader)?; - match unsafe { Utf8CStr::from_ptr(cert) } { - Ok(s) => { - let pem = MappedFile::open(s)?; - sig.certificate = Certificate::from_pem(pem)?; - } - Err(StrErr::NullPointerError) => {} - Err(e) => Err(e)?, + if let Some(s) = cert { + let pem = MappedFile::open(s)?; + sig.certificate = Certificate::from_pem(pem)?; }; - sig.verify(img.payload())?; - }; - res.is_ok() + + sig.verify(self.payload()).log() + } + + pub fn verify_for_cxx(&self) -> bool { + self.verify(None).is_ok() + } } enum Bytes { @@ -303,47 +306,44 @@ const VERITY_PK8: &[u8] = include_bytes!("../../../tools/keys/verity.pk8"); pub fn sign_boot_image( payload: &[u8], - name: *const c_char, - cert: *const c_char, - key: *const c_char, -) -> Vec { - let res: LoggedResult> = try { - // Process arguments - let name = unsafe { Utf8CStr::from_ptr(name) }?; - let cert = match unsafe { Utf8CStr::from_ptr(cert) } { - Ok(s) => Bytes::Mapped(MappedFile::open(s)?), - Err(StrErr::NullPointerError) => Bytes::Slice(VERITY_PEM), - Err(e) => Err(e)?, - }; - let key = match unsafe { Utf8CStr::from_ptr(key) } { - Ok(s) => Bytes::Mapped(MappedFile::open(s)?), - Err(StrErr::NullPointerError) => Bytes::Slice(VERITY_PK8), - Err(e) => Err(e)?, - }; - - // Parse cert and private key - let cert = Certificate::from_pem(cert)?; - let mut signer = Signer::from_private_key(key.as_ref())?; - - // Sign image - let attr = AuthenticatedAttributes { - target: PrintableString::new(name.as_bytes())?, - length: payload.len() as u64, - }; - signer.update(payload); - signer.update(attr.to_der()?.as_slice()); - let sig = signer.sign()?; - - // Create BootSignature DER - let alg_id = cert.signature_algorithm().clone(); - let sig = BootSignature { - format_version: 1, - certificate: cert, - algorithm_identifier: alg_id, - authenticated_attributes: attr, - signature: OctetString::new(sig)?, - }; - sig.to_der()? + name: &Utf8CStr, + cert: Option<&Utf8CStr>, + key: Option<&Utf8CStr>, +) -> LoggedResult> { + let cert = match cert { + Some(s) => Bytes::Mapped(MappedFile::open(s)?), + None => Bytes::Slice(VERITY_PEM), }; - res.unwrap_or_default() + let key = match key { + Some(s) => Bytes::Mapped(MappedFile::open(s)?), + None => Bytes::Slice(VERITY_PK8), + }; + + // Parse cert and private key + let cert = Certificate::from_pem(cert)?; + let mut signer = Signer::from_private_key(key.as_ref())?; + + // Sign image + let attr = AuthenticatedAttributes { + target: PrintableString::new(name.as_bytes())?, + length: payload.len() as u64, + }; + signer.update(payload); + signer.update(attr.to_der()?.as_slice()); + let sig = signer.sign()?; + + // Create BootSignature DER + let alg_id = cert.signature_algorithm().clone(); + let sig = BootSignature { + format_version: 1, + certificate: cert, + algorithm_identifier: alg_id, + authenticated_attributes: attr, + signature: OctetString::new(sig)?, + }; + sig.to_der().log() +} + +pub fn sign_payload_for_cxx(payload: &[u8]) -> Vec { + sign_boot_image(payload, cstr!("/boot"), None, None).unwrap_or_default() }