mirror of
https://github.com/topjohnwu/Magisk.git
synced 2026-09-23 06:35:16 -04:00
Support dynamic zImage piggy payload sizes during repacking by updating the zImage decompressor stub headers, symbol tables, and GOT entries. Previously, magiskboot padded repacked zImage files to preserve the exact original file size. For non-gzip compressed zImages (such as XZ), repacking with a different payload size broke stub symbol resolution and resulted in ununpackable or unbootable images. Key changes: - Encapsulate zImage parsing and patching in a dedicated struct ZImage in Rust with CXX FFI, new_head, and new_tail methods. - Locate piggy_end using table references or GOT table entries bounded by LC0. - Relocate LC0 table entries, LC1 table, and GOT table entries with 4-byte padding/alignment when payload size changes. - Relocate R_ARM_GOTPC PC-relative literal pools in decompressor .text. - Append uncompressed size for non-gzip algorithms per size_append. - Remove zopfli compression support across magiskboot as dynamic zImage piggy sizing supersedes the need for exact size matching. Fix #9880 Assisted-by: Gemini 3.7 Flash
952 lines
33 KiB
C++
952 lines
33 KiB
C++
#include <bit>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <span>
|
|
|
|
#include <base.hpp>
|
|
|
|
#include "boot-rs.hpp"
|
|
#include "bootimg.hpp"
|
|
#include "magiskboot.hpp"
|
|
|
|
using namespace std;
|
|
|
|
#define PADDING 15
|
|
#define SHA256_DIGEST_SIZE 32
|
|
#define SHA_DIGEST_SIZE 20
|
|
|
|
#define RETURN_OK 0
|
|
#define RETURN_ERROR 1
|
|
#define RETURN_CHROMEOS 2
|
|
#define RETURN_VENDOR 3
|
|
|
|
static FileFormat check_fmt(const void *buf, size_t len) {
|
|
return check_fmt({static_cast<const uint8_t *>(buf), len});
|
|
}
|
|
|
|
static int find_dtb_offset(const void *buf, size_t len) {
|
|
return find_dtb_offset({static_cast<const uint8_t *>(buf), len});
|
|
}
|
|
|
|
static void decompress(FileFormat type, int fd, const void *in, size_t size) {
|
|
decompress_bytes(type, byte_view { in, size }, fd);
|
|
}
|
|
|
|
static off_t compress_len(FileFormat type, byte_view in, int fd) {
|
|
auto prev = lseek(fd, 0, SEEK_CUR);
|
|
compress_bytes(type, in, fd);
|
|
auto now = lseek(fd, 0, SEEK_CUR);
|
|
return now - prev;
|
|
}
|
|
|
|
static void dump(const void *buf, size_t size, const char *filename) {
|
|
if (size == 0)
|
|
return;
|
|
int fd = creat(filename, 0644);
|
|
xwrite(fd, buf, size);
|
|
close(fd);
|
|
}
|
|
|
|
static size_t restore(int fd, const char *filename) {
|
|
int ifd = xopen(filename, O_RDONLY);
|
|
size_t size = lseek(ifd, 0, SEEK_END);
|
|
lseek(ifd, 0, SEEK_SET);
|
|
xsendfile(fd, ifd, nullptr, size);
|
|
close(ifd);
|
|
return size;
|
|
}
|
|
|
|
static bool check_env(const char *name) {
|
|
const char *val = getenv(name);
|
|
return val != nullptr && val == "true"sv;
|
|
}
|
|
|
|
void dyn_img_hdr::print() const {
|
|
uint32_t ver = header_version();
|
|
fprintf(stderr, "%-*s [%u]\n", PADDING, "HEADER_VER", ver);
|
|
if (!is_vendor())
|
|
fprintf(stderr, "%-*s [%u]\n", PADDING, "KERNEL_SZ", kernel_size());
|
|
fprintf(stderr, "%-*s [%u]\n", PADDING, "RAMDISK_SZ", ramdisk_size());
|
|
if (ver < 3)
|
|
fprintf(stderr, "%-*s [%u]\n", PADDING, "SECOND_SZ", second_size());
|
|
if (ver == 0)
|
|
fprintf(stderr, "%-*s [%u]\n", PADDING, "EXTRA_SZ", extra_size());
|
|
if (ver == 1 || ver == 2)
|
|
fprintf(stderr, "%-*s [%u]\n", PADDING, "RECOV_DTBO_SZ", recovery_dtbo_size());
|
|
if (ver == 2 || is_vendor())
|
|
fprintf(stderr, "%-*s [%u]\n", PADDING, "DTB_SZ", dtb_size());
|
|
if (ver == 4 && is_vendor())
|
|
fprintf(stderr, "%-*s [%u]\n", PADDING, "BOOTCONFIG_SZ", bootconfig_size());
|
|
|
|
if (uint32_t os_ver = os_version()) {
|
|
int a,b,c,y,m = 0;
|
|
int version = os_ver >> 11;
|
|
int patch_level = os_ver & 0x7ff;
|
|
|
|
a = (version >> 14) & 0x7f;
|
|
b = (version >> 7) & 0x7f;
|
|
c = version & 0x7f;
|
|
fprintf(stderr, "%-*s [%d.%d.%d]\n", PADDING, "OS_VERSION", a, b, c);
|
|
|
|
y = (patch_level >> 4) + 2000;
|
|
m = patch_level & 0xf;
|
|
fprintf(stderr, "%-*s [%d-%02d]\n", PADDING, "OS_PATCH_LEVEL", y, m);
|
|
}
|
|
|
|
fprintf(stderr, "%-*s [%u]\n", PADDING, "PAGESIZE", page_size());
|
|
if (const char *n = name()) {
|
|
fprintf(stderr, "%-*s [%s]\n", PADDING, "NAME", n);
|
|
}
|
|
fprintf(stderr, "%-*s [%.*s%.*s]\n", PADDING, "CMDLINE",
|
|
BOOT_ARGS_SIZE, cmdline(), BOOT_EXTRA_ARGS_SIZE, extra_cmdline());
|
|
if (const char *checksum = id()) {
|
|
fprintf(stderr, "%-*s [", PADDING, "CHECKSUM");
|
|
for (int i = 0; i < SHA256_DIGEST_SIZE; ++i)
|
|
fprintf(stderr, "%02hhx", checksum[i]);
|
|
fprintf(stderr, "]\n");
|
|
}
|
|
}
|
|
|
|
void dyn_img_hdr::dump_hdr_file() const {
|
|
FILE *fp = xfopen(HEADER_FILE, "w");
|
|
if (name())
|
|
fprintf(fp, "name=%s\n", name());
|
|
fprintf(fp, "cmdline=%.*s%.*s\n", BOOT_ARGS_SIZE, cmdline(), BOOT_EXTRA_ARGS_SIZE, extra_cmdline());
|
|
uint32_t ver = os_version();
|
|
if (ver) {
|
|
int a, b, c, y, m;
|
|
int version, patch_level;
|
|
version = ver >> 11;
|
|
patch_level = ver & 0x7ff;
|
|
|
|
a = (version >> 14) & 0x7f;
|
|
b = (version >> 7) & 0x7f;
|
|
c = version & 0x7f;
|
|
fprintf(fp, "os_version=%d.%d.%d\n", a, b, c);
|
|
|
|
y = (patch_level >> 4) + 2000;
|
|
m = patch_level & 0xf;
|
|
fprintf(fp, "os_patch_level=%d-%02d\n", y, m);
|
|
}
|
|
fclose(fp);
|
|
}
|
|
|
|
void dyn_img_hdr::load_hdr_file() {
|
|
parse_prop_file(HEADER_FILE, [=, this](Utf8CStr key, Utf8CStr value) -> bool {
|
|
if (key == "name" && name()) {
|
|
memset(name(), 0, 16);
|
|
memcpy(name(), value.data(), value.length() > 15 ? 15 : value.length());
|
|
} else if (key == "cmdline") {
|
|
memset(cmdline(), 0, BOOT_ARGS_SIZE);
|
|
memset(extra_cmdline(), 0, BOOT_EXTRA_ARGS_SIZE);
|
|
if (value.length() > BOOT_ARGS_SIZE) {
|
|
memcpy(cmdline(), value.data(), BOOT_ARGS_SIZE);
|
|
auto len = std::min(value.length() - BOOT_ARGS_SIZE, (size_t) BOOT_EXTRA_ARGS_SIZE);
|
|
memcpy(extra_cmdline(), value.data() + BOOT_ARGS_SIZE, len);
|
|
} else {
|
|
memcpy(cmdline(), value.data(), value.length());
|
|
}
|
|
} else if (key == "os_version") {
|
|
int patch_level = os_version() & 0x7ff;
|
|
int a, b, c;
|
|
sscanf(value.data(), "%d.%d.%d", &a, &b, &c);
|
|
os_version() = (((a << 14) | (b << 7) | c) << 11) | patch_level;
|
|
} else if (key == "os_patch_level") {
|
|
int os_ver = os_version() >> 11;
|
|
int y, m;
|
|
sscanf(value.data(), "%d-%d", &y, &m);
|
|
y -= 2000;
|
|
os_version() = (os_ver << 11) | (y << 4) | m;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
boot_img::boot_img(const char *image) :
|
|
map(image), k_fmt(FileFormat::UNKNOWN), r_fmt(FileFormat::UNKNOWN), e_fmt(FileFormat::UNKNOWN) {
|
|
fprintf(stderr, "Parsing boot image: [%s]\n", image);
|
|
for (const uint8_t *addr = map.data(); addr < map.data() + map.size(); ++addr) {
|
|
FileFormat fmt = check_fmt(addr, map.size());
|
|
switch (fmt) {
|
|
case FileFormat::CHROMEOS:
|
|
// chromeos require external signing
|
|
flags[CHROMEOS_FLAG] = true;
|
|
addr += 65535;
|
|
break;
|
|
case FileFormat::DHTB:
|
|
flags[DHTB_FLAG] = true;
|
|
flags[SEANDROID_FLAG] = true;
|
|
fprintf(stderr, "DHTB_HDR\n");
|
|
addr += sizeof(dhtb_hdr) - 1;
|
|
break;
|
|
case FileFormat::BLOB:
|
|
flags[BLOB_FLAG] = true;
|
|
fprintf(stderr, "TEGRA_BLOB\n");
|
|
addr += sizeof(blob_hdr) - 1;
|
|
break;
|
|
case FileFormat::AOSP:
|
|
case FileFormat::AOSP_VENDOR:
|
|
if (parse_image(addr, fmt))
|
|
return;
|
|
// fallthrough
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
exit(RETURN_ERROR);
|
|
}
|
|
|
|
boot_img::~boot_img() {
|
|
delete hdr;
|
|
}
|
|
|
|
static FileFormat check_fmt_lg(const uint8_t *buf, unsigned sz) {
|
|
FileFormat fmt = check_fmt(buf, sz);
|
|
if (fmt == FileFormat::LZ4_LEGACY) {
|
|
// We need to check if it is LZ4_LG
|
|
uint32_t off = 4;
|
|
uint32_t block_sz;
|
|
while (off + sizeof(block_sz) <= sz) {
|
|
memcpy(&block_sz, buf + off, sizeof(block_sz));
|
|
off += sizeof(block_sz);
|
|
if (off + block_sz > sz)
|
|
return FileFormat::LZ4_LG;
|
|
off += block_sz;
|
|
}
|
|
}
|
|
return fmt;
|
|
}
|
|
|
|
#define CMD_MATCH(s) BUFFER_MATCH(h->cmdline, s)
|
|
|
|
const uint8_t *boot_img::parse_hdr(const uint8_t *addr, FileFormat type) {
|
|
if (type == FileFormat::AOSP_VENDOR) {
|
|
fprintf(stderr, "VENDOR_BOOT_HDR\n");
|
|
auto h = reinterpret_cast<const boot_img_hdr_vnd_v3*>(addr);
|
|
switch (h->header_version) {
|
|
case 4:
|
|
hdr = new dyn_img_vnd_v4(addr);
|
|
break;
|
|
default:
|
|
hdr = new dyn_img_vnd_v3(addr);
|
|
break;
|
|
}
|
|
return addr;
|
|
}
|
|
|
|
auto h = reinterpret_cast<const boot_img_hdr_v0*>(addr);
|
|
|
|
if (h->page_size >= 0x02000000) {
|
|
fprintf(stderr, "PXA_BOOT_HDR\n");
|
|
hdr = new dyn_img_pxa(addr);
|
|
return addr;
|
|
}
|
|
|
|
auto make_aosp_hdr = [](const uint8_t *ptr, ssize_t size = -1) -> dyn_img_hdr * {
|
|
auto h = reinterpret_cast<const boot_img_hdr_v0*>(ptr);
|
|
if (memcmp(h->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE) != 0)
|
|
return nullptr;
|
|
|
|
switch (h->header_version) {
|
|
case 1:
|
|
return new dyn_img_v1(ptr, size);
|
|
case 2:
|
|
return new dyn_img_v2(ptr, size);
|
|
case 3:
|
|
return new dyn_img_v3(ptr, size);
|
|
case 4:
|
|
return new dyn_img_v4(ptr, size);
|
|
default:
|
|
return new dyn_img_v0(ptr, size);
|
|
}
|
|
};
|
|
|
|
// For NOOKHD and ACCLAIM, the entire boot image is shifted by a fixed offset.
|
|
// For AMONET, the header itself is internally shifted by a fixed offset.
|
|
|
|
if (BUFFER_CONTAIN(addr, AMONET_MICROLOADER_SZ, AMONET_MICROLOADER_MAGIC) &&
|
|
BUFFER_MATCH(addr + AMONET_MICROLOADER_SZ, BOOT_MAGIC)) {
|
|
flags[AMONET_FLAG] = true;
|
|
fprintf(stderr, "AMONET_MICROLOADER\n");
|
|
|
|
// The real header is shifted
|
|
h = reinterpret_cast<const boot_img_hdr_v0*>(addr + AMONET_MICROLOADER_SZ);
|
|
auto real_hdr_sz = h->page_size - AMONET_MICROLOADER_SZ;
|
|
hdr = make_aosp_hdr(addr + AMONET_MICROLOADER_SZ, real_hdr_sz);
|
|
return addr;
|
|
}
|
|
|
|
if (CMD_MATCH(NOOKHD_RL_MAGIC) ||
|
|
CMD_MATCH(NOOKHD_GL_MAGIC) ||
|
|
CMD_MATCH(NOOKHD_GR_MAGIC) ||
|
|
CMD_MATCH(NOOKHD_EB_MAGIC) ||
|
|
CMD_MATCH(NOOKHD_ER_MAGIC)) {
|
|
flags[NOOKHD_FLAG] = true;
|
|
fprintf(stderr, "NOOKHD_LOADER\n");
|
|
addr += NOOKHD_PRE_HEADER_SZ;
|
|
} else if (BUFFER_MATCH(h->name, ACCLAIM_MAGIC)) {
|
|
flags[ACCLAIM_FLAG] = true;
|
|
fprintf(stderr, "ACCLAIM_LOADER\n");
|
|
addr += ACCLAIM_PRE_HEADER_SZ;
|
|
}
|
|
|
|
hdr = make_aosp_hdr(addr);
|
|
return addr;
|
|
}
|
|
|
|
static const char *vendor_ramdisk_type(int type) {
|
|
switch (type) {
|
|
case VENDOR_RAMDISK_TYPE_PLATFORM:
|
|
return "platform";
|
|
case VENDOR_RAMDISK_TYPE_RECOVERY:
|
|
return "recovery";
|
|
case VENDOR_RAMDISK_TYPE_DLKM:
|
|
return "dlkm";
|
|
case VENDOR_RAMDISK_TYPE_NONE:
|
|
default:
|
|
return "none";
|
|
}
|
|
}
|
|
|
|
std::span<const vendor_ramdisk_table_entry_v4> boot_img::vendor_ramdisk_tbl() const {
|
|
if (hdr->vendor_ramdisk_table_size() == 0) {
|
|
return {};
|
|
}
|
|
|
|
// v4 vendor boot contains multiple ramdisks
|
|
using table_entry = const vendor_ramdisk_table_entry_v4;
|
|
if (hdr->vendor_ramdisk_table_entry_size() != sizeof(table_entry)) {
|
|
fprintf(stderr,
|
|
"! Invalid vendor image: vendor_ramdisk_table_entry_size != %zu\n",
|
|
sizeof(table_entry));
|
|
exit(RETURN_ERROR);
|
|
}
|
|
return span(reinterpret_cast<table_entry *>(vendor_ramdisk_table), hdr->vendor_ramdisk_table_entry_num());
|
|
}
|
|
|
|
#define assert_off() \
|
|
if ((addr + off) > (map.data() + map_end)) { \
|
|
fprintf(stderr, "Corrupted boot image!\n"); \
|
|
return false; \
|
|
}
|
|
|
|
#define get_block(name) \
|
|
name = addr + off; \
|
|
off += hdr->name##_size(); \
|
|
off = align_to(off, hdr->page_size()); \
|
|
assert_off()
|
|
|
|
bool boot_img::parse_image(const uint8_t *addr, FileFormat type) {
|
|
addr = parse_hdr(addr, type);
|
|
if (hdr == nullptr) {
|
|
fprintf(stderr, "Invalid boot image header!\n");
|
|
return false;
|
|
}
|
|
|
|
if (const char *id = hdr->id()) {
|
|
for (int i = SHA_DIGEST_SIZE + 4; i < SHA256_DIGEST_SIZE; ++i) {
|
|
if (id[i]) {
|
|
flags[SHA256_FLAG] = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
hdr->print();
|
|
|
|
size_t map_end = align_to(map.size(), getpagesize());
|
|
size_t off = hdr->hdr_space();
|
|
get_block(kernel);
|
|
get_block(ramdisk);
|
|
get_block(second);
|
|
get_block(extra);
|
|
get_block(recovery_dtbo);
|
|
get_block(dtb);
|
|
get_block(signature);
|
|
get_block(vendor_ramdisk_table);
|
|
get_block(bootconfig);
|
|
|
|
payload = byte_view(addr, off);
|
|
auto tail_addr = addr + off;
|
|
tail = byte_view(tail_addr, map.data() + map_end - tail_addr);
|
|
|
|
if (auto size = hdr->kernel_size()) {
|
|
if (int dtb_off = find_dtb_offset(kernel, size); dtb_off > 0) {
|
|
kernel_dtb = byte_view(kernel + dtb_off, size - dtb_off);
|
|
hdr->kernel_size() = dtb_off;
|
|
fprintf(stderr, "%-*s [%zu]\n", PADDING, "KERNEL_DTB_SZ", kernel_dtb.size());
|
|
}
|
|
|
|
k_fmt = check_fmt_lg(kernel, hdr->kernel_size());
|
|
if (k_fmt == FileFormat::MTK) {
|
|
fprintf(stderr, "MTK_KERNEL_HDR\n");
|
|
flags[MTK_KERNEL] = true;
|
|
k_hdr = reinterpret_cast<const mtk_hdr *>(kernel);
|
|
fprintf(stderr, "%-*s [%u]\n", PADDING, "SIZE", k_hdr->size);
|
|
fprintf(stderr, "%-*s [%s]\n", PADDING, "NAME", k_hdr->name);
|
|
kernel += sizeof(mtk_hdr);
|
|
hdr->kernel_size() -= sizeof(mtk_hdr);
|
|
k_fmt = check_fmt_lg(kernel, hdr->kernel_size());
|
|
}
|
|
if (k_fmt == FileFormat::ZIMAGE) {
|
|
z_info = ZImage::parse(byte_view(kernel, hdr->kernel_size()));
|
|
if (z_info != nullptr) {
|
|
fprintf(stderr, "ZIMAGE_KERNEL\n");
|
|
flags[ZIMAGE_KERNEL] = true;
|
|
kernel = z_info->piggy.data();
|
|
if (z_info->fmt != FileFormat::GZIP) {
|
|
hdr->kernel_size() = z_info->piggy.size() - sizeof(uint32_t);
|
|
} else {
|
|
hdr->kernel_size() = z_info->piggy.size();
|
|
}
|
|
k_fmt = z_info->fmt;
|
|
}
|
|
}
|
|
fprintf(stderr, "%-*s [%s]\n", PADDING, "KERNEL_FMT", fmt2name(k_fmt));
|
|
}
|
|
if (auto size = hdr->ramdisk_size()) {
|
|
if (hdr->vendor_ramdisk_table_size()) {
|
|
for (auto &it : vendor_ramdisk_tbl()) {
|
|
FileFormat fmt = check_fmt_lg(ramdisk + it.ramdisk_offset, it.ramdisk_size);
|
|
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));
|
|
}
|
|
} else {
|
|
r_fmt = check_fmt_lg(ramdisk, size);
|
|
if (r_fmt == FileFormat::MTK) {
|
|
fprintf(stderr, "MTK_RAMDISK_HDR\n");
|
|
flags[MTK_RAMDISK] = true;
|
|
r_hdr = reinterpret_cast<const mtk_hdr *>(ramdisk);
|
|
fprintf(stderr, "%-*s [%u]\n", PADDING, "SIZE", r_hdr->size);
|
|
fprintf(stderr, "%-*s [%s]\n", PADDING, "NAME", r_hdr->name);
|
|
ramdisk += sizeof(mtk_hdr);
|
|
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));
|
|
}
|
|
}
|
|
if (auto size = hdr->extra_size()) {
|
|
e_fmt = check_fmt_lg(extra, size);
|
|
fprintf(stderr, "%-*s [%s]\n", PADDING, "EXTRA_FMT", fmt2name(e_fmt));
|
|
}
|
|
|
|
if (tail.size()) {
|
|
// Check special flags
|
|
if (tail.size() >= 16 && BUFFER_MATCH(tail.data(), SEANDROID_MAGIC)) {
|
|
fprintf(stderr, "SAMSUNG_SEANDROID\n");
|
|
flags[SEANDROID_FLAG] = true;
|
|
} else if (tail.size() >= 16 && BUFFER_MATCH(tail.data(), LG_BUMP_MAGIC)) {
|
|
fprintf(stderr, "LG_BUMP_IMAGE\n");
|
|
flags[LG_BUMP_FLAG] = true;
|
|
} else if (verify()) {
|
|
fprintf(stderr, "AVB1_SIGNED\n");
|
|
flags[AVB1_SIGNED_FLAG] = true;
|
|
}
|
|
|
|
// Find AVB footer
|
|
const void *footer = tail.data() + tail.size() - sizeof(AvbFooter);
|
|
if (BUFFER_MATCH(footer, AVB_FOOTER_MAGIC)) {
|
|
avb_footer = static_cast<const AvbFooter*>(footer);
|
|
// Double check if meta header exists
|
|
const void *meta = payload.data() + __builtin_bswap64(avb_footer->vbmeta_offset);
|
|
if (BUFFER_MATCH(meta, AVB_MAGIC)) {
|
|
fprintf(stderr, "VBMETA\n");
|
|
flags[AVB_FLAG] = true;
|
|
vbmeta = static_cast<const AvbVBMetaImageHeader*>(meta);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int split_image_dtb(Utf8CStr filename, bool skip_decomp) {
|
|
mmap_data img(filename.c_str());
|
|
|
|
if (int offset = find_dtb_offset(img.data(), img.size()); offset > 0) {
|
|
size_t off = (size_t) offset;
|
|
|
|
FileFormat fmt = check_fmt_lg(img.data(), img.size());
|
|
if (!skip_decomp && fmt_compressed(fmt)) {
|
|
int fd = creat(KERNEL_FILE, 0644);
|
|
decompress(fmt, fd, img.data(), off);
|
|
close(fd);
|
|
} else {
|
|
dump(img.data(), off, KERNEL_FILE);
|
|
}
|
|
dump(img.data() + off, img.size() - off, KER_DTB_FILE);
|
|
return 0;
|
|
} else {
|
|
fprintf(stderr, "Cannot find DTB in %s\n", filename.c_str());
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
int unpack(Utf8CStr image, bool skip_decomp, bool hdr) {
|
|
const boot_img boot(image.c_str());
|
|
|
|
if (hdr)
|
|
boot.hdr->dump_hdr_file();
|
|
|
|
// Dump kernel
|
|
if (!skip_decomp && fmt_compressed(boot.k_fmt)) {
|
|
if (boot.hdr->kernel_size() != 0) {
|
|
int fd = creat(KERNEL_FILE, 0644);
|
|
decompress(boot.k_fmt, fd, boot.kernel, boot.hdr->kernel_size());
|
|
close(fd);
|
|
}
|
|
} else {
|
|
dump(boot.kernel, boot.hdr->kernel_size(), KERNEL_FILE);
|
|
}
|
|
|
|
// Dump kernel_dtb
|
|
dump(boot.kernel_dtb.data(), boot.kernel_dtb.size(), KER_DTB_FILE);
|
|
|
|
// Dump ramdisk
|
|
if (boot.hdr->vendor_ramdisk_table_size()) {
|
|
xmkdir(VND_RAMDISK_DIR, 0755);
|
|
owned_fd dirfd = xopen(VND_RAMDISK_DIR, O_RDONLY | O_CLOEXEC);
|
|
for (auto &it : boot.vendor_ramdisk_tbl()) {
|
|
char file_name[40];
|
|
if (it.ramdisk_name[0] == '\0') {
|
|
strscpy(file_name, RAMDISK_FILE, sizeof(file_name));
|
|
} else {
|
|
ssprintf(file_name, sizeof(file_name), "%s.cpio", it.ramdisk_name);
|
|
}
|
|
owned_fd fd = xopenat(dirfd, file_name, O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC, 0644);
|
|
FileFormat fmt = check_fmt_lg(boot.ramdisk + it.ramdisk_offset, it.ramdisk_size);
|
|
if (!skip_decomp && fmt_compressed(fmt)) {
|
|
decompress(fmt, fd, boot.ramdisk + it.ramdisk_offset, it.ramdisk_size);
|
|
} else {
|
|
xwrite(fd, boot.ramdisk + it.ramdisk_offset, it.ramdisk_size);
|
|
}
|
|
}
|
|
} else if (!skip_decomp && fmt_compressed(boot.r_fmt)) {
|
|
if (boot.hdr->ramdisk_size() != 0) {
|
|
int fd = creat(RAMDISK_FILE, 0644);
|
|
decompress(boot.r_fmt, fd, boot.ramdisk, boot.hdr->ramdisk_size());
|
|
close(fd);
|
|
}
|
|
} else {
|
|
dump(boot.ramdisk, boot.hdr->ramdisk_size(), RAMDISK_FILE);
|
|
}
|
|
|
|
// Dump second
|
|
dump(boot.second, boot.hdr->second_size(), SECOND_FILE);
|
|
|
|
// Dump extra
|
|
if (!skip_decomp && fmt_compressed(boot.e_fmt)) {
|
|
if (boot.hdr->extra_size() != 0) {
|
|
int fd = creat(EXTRA_FILE, 0644);
|
|
decompress(boot.e_fmt, fd, boot.extra, boot.hdr->extra_size());
|
|
close(fd);
|
|
}
|
|
} else {
|
|
dump(boot.extra, boot.hdr->extra_size(), EXTRA_FILE);
|
|
}
|
|
|
|
// Dump recovery_dtbo
|
|
dump(boot.recovery_dtbo, boot.hdr->recovery_dtbo_size(), RECV_DTBO_FILE);
|
|
|
|
// Dump dtb
|
|
dump(boot.dtb, boot.hdr->dtb_size(), DTB_FILE);
|
|
|
|
// Dump bootconfig
|
|
dump(boot.bootconfig, boot.hdr->bootconfig_size(), BOOTCONFIG_FILE);
|
|
|
|
if (boot.flags[CHROMEOS_FLAG]) return RETURN_CHROMEOS;
|
|
if (boot.hdr->is_vendor()) return RETURN_VENDOR;
|
|
return RETURN_OK;
|
|
}
|
|
|
|
#define file_align_with(page_size) \
|
|
write_zero(fd, align_padding(lseek(fd, 0, SEEK_CUR) - off.header, page_size))
|
|
|
|
#define file_align() file_align_with(boot.hdr->page_size())
|
|
|
|
void repack(Utf8CStr src_img, Utf8CStr out_img, bool skip_comp) {
|
|
const boot_img boot(src_img.c_str());
|
|
fprintf(stderr, "Repack to boot image: [%s]\n", out_img.c_str());
|
|
|
|
struct {
|
|
uint32_t header;
|
|
uint32_t kernel;
|
|
uint32_t ramdisk;
|
|
uint32_t second;
|
|
uint32_t extra;
|
|
uint32_t dtb;
|
|
uint32_t tail;
|
|
uint32_t vbmeta;
|
|
} off{};
|
|
|
|
// Create a new boot header and reset sizes
|
|
auto hdr = boot.hdr->clone();
|
|
hdr->kernel_size() = 0;
|
|
hdr->ramdisk_size() = 0;
|
|
hdr->second_size() = 0;
|
|
hdr->dtb_size() = 0;
|
|
hdr->bootconfig_size() = 0;
|
|
|
|
if (access(HEADER_FILE, R_OK) == 0)
|
|
hdr->load_hdr_file();
|
|
|
|
/***************
|
|
* Write blocks
|
|
***************/
|
|
|
|
// Create new image
|
|
int fd = open(out_img.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0644);
|
|
|
|
// Copy non-standard headers
|
|
if (boot.flags[DHTB_FLAG]) {
|
|
xwrite(fd, boot.map.data(), sizeof(dhtb_hdr));
|
|
} else if (boot.flags[BLOB_FLAG]) {
|
|
xwrite(fd, boot.map.data(), sizeof(blob_hdr));
|
|
} else if (boot.flags[NOOKHD_FLAG]) {
|
|
xwrite(fd, boot.map.data(), NOOKHD_PRE_HEADER_SZ);
|
|
} else if (boot.flags[ACCLAIM_FLAG]) {
|
|
xwrite(fd, boot.map.data(), ACCLAIM_PRE_HEADER_SZ);
|
|
}
|
|
|
|
// Copy raw header
|
|
off.header = lseek(fd, 0, SEEK_CUR);
|
|
xwrite(fd, boot.payload.data(), hdr->hdr_space());
|
|
|
|
// kernel
|
|
off.kernel = lseek(fd, 0, SEEK_CUR);
|
|
if (boot.flags[MTK_KERNEL]) {
|
|
// Copy MTK headers
|
|
xwrite(fd, boot.k_hdr, sizeof(mtk_hdr));
|
|
}
|
|
if (boot.flags[ZIMAGE_KERNEL]) {
|
|
// Copy zImage headers stub
|
|
xwrite(fd, boot.z_info->head.data(), boot.z_info->head.size());
|
|
}
|
|
uint32_t z_payload_sz = 0;
|
|
if (access(KERNEL_FILE, R_OK) == 0) {
|
|
mmap_data m(KERNEL_FILE);
|
|
uint32_t payload_sz = 0;
|
|
if (!skip_comp && !fmt_compressed_any(check_fmt(m.data(), m.size())) && fmt_compressed(boot.k_fmt)) {
|
|
payload_sz = compress_len(boot.k_fmt, m, fd);
|
|
if (boot.flags[ZIMAGE_KERNEL] && boot.k_fmt != FileFormat::GZIP) {
|
|
// For non-gzip compression in zImage, size_append appends the 4-byte LE uncompressed size
|
|
uint32_t sz = m.size();
|
|
xwrite(fd, &sz, sizeof(sz));
|
|
payload_sz += sizeof(sz);
|
|
}
|
|
hdr->kernel_size() = payload_sz;
|
|
} else {
|
|
payload_sz = xwrite(fd, m.data(), m.size());
|
|
hdr->kernel_size() = payload_sz;
|
|
}
|
|
|
|
if (boot.flags[ZIMAGE_KERNEL]) {
|
|
z_payload_sz = payload_sz;
|
|
auto tail_buf = boot.z_info->new_tail(payload_sz);
|
|
xwrite(fd, tail_buf.data(), tail_buf.size());
|
|
hdr->kernel_size() = boot.z_info->head.size() + payload_sz + tail_buf.size();
|
|
}
|
|
} else if (boot.hdr->kernel_size() != 0) {
|
|
if (boot.flags[ZIMAGE_KERNEL]) {
|
|
xwrite(fd, boot.z_info->piggy.data(), boot.z_info->piggy.size());
|
|
xwrite(fd, boot.z_info->tail.data(), boot.z_info->tail.size());
|
|
hdr->kernel_size() = boot.z_info->head.size() + boot.z_info->piggy.size() + boot.z_info->tail.size();
|
|
} else {
|
|
xwrite(fd, boot.kernel, boot.hdr->kernel_size());
|
|
hdr->kernel_size() = boot.hdr->kernel_size();
|
|
}
|
|
}
|
|
|
|
// kernel dtb
|
|
if (access(KER_DTB_FILE, R_OK) == 0)
|
|
hdr->kernel_size() += restore(fd, KER_DTB_FILE);
|
|
file_align();
|
|
|
|
// ramdisk
|
|
off.ramdisk = lseek(fd, 0, SEEK_CUR);
|
|
if (boot.flags[MTK_RAMDISK]) {
|
|
// Copy MTK headers
|
|
xwrite(fd, boot.r_hdr, sizeof(mtk_hdr));
|
|
}
|
|
|
|
vector<vendor_ramdisk_table_entry_v4> ramdisk_table;
|
|
|
|
if (boot.hdr->vendor_ramdisk_table_size()) {
|
|
// Create a copy so we can modify it
|
|
ramdisk_table.assign_range(boot.vendor_ramdisk_tbl());
|
|
|
|
owned_fd dirfd = xopen(VND_RAMDISK_DIR, O_RDONLY | O_CLOEXEC);
|
|
uint32_t ramdisk_offset = 0;
|
|
for (auto &it : ramdisk_table) {
|
|
char file_name[64];
|
|
if (it.ramdisk_name[0] == '\0') {
|
|
strscpy(file_name, RAMDISK_FILE, sizeof(file_name));
|
|
} else {
|
|
ssprintf(file_name, sizeof(file_name), "%s.cpio", it.ramdisk_name);
|
|
}
|
|
mmap_data m(dirfd, file_name);
|
|
FileFormat fmt = check_fmt_lg(boot.ramdisk + it.ramdisk_offset, it.ramdisk_size);
|
|
it.ramdisk_offset = ramdisk_offset;
|
|
if (!skip_comp && !fmt_compressed_any(check_fmt(m.data(), m.size())) && fmt_compressed(fmt)) {
|
|
it.ramdisk_size = compress_len(fmt, m, fd);
|
|
} else {
|
|
it.ramdisk_size = xwrite(fd, m.data(), m.size());
|
|
}
|
|
ramdisk_offset += it.ramdisk_size;
|
|
}
|
|
|
|
hdr->ramdisk_size() = ramdisk_offset;
|
|
file_align();
|
|
} else if (access(RAMDISK_FILE, R_OK) == 0) {
|
|
mmap_data m(RAMDISK_FILE);
|
|
auto r_fmt = boot.r_fmt;
|
|
if (!skip_comp && !hdr->is_vendor() && hdr->header_version() == 4 && r_fmt != FileFormat::LZ4_LEGACY) {
|
|
// 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));
|
|
r_fmt = FileFormat::LZ4_LEGACY;
|
|
}
|
|
if (!skip_comp && !fmt_compressed_any(check_fmt(m.data(), m.size())) && fmt_compressed(r_fmt)) {
|
|
hdr->ramdisk_size() = compress_len(r_fmt, m, fd);
|
|
} else {
|
|
hdr->ramdisk_size() = xwrite(fd, m.data(), m.size());
|
|
}
|
|
file_align();
|
|
}
|
|
|
|
// second
|
|
off.second = lseek(fd, 0, SEEK_CUR);
|
|
if (access(SECOND_FILE, R_OK) == 0) {
|
|
hdr->second_size() = restore(fd, SECOND_FILE);
|
|
file_align();
|
|
}
|
|
|
|
// extra
|
|
off.extra = lseek(fd, 0, SEEK_CUR);
|
|
if (access(EXTRA_FILE, R_OK) == 0) {
|
|
mmap_data m(EXTRA_FILE);
|
|
if (!skip_comp && !fmt_compressed_any(check_fmt(m.data(), m.size())) && fmt_compressed(boot.e_fmt)) {
|
|
hdr->extra_size() = compress_len(boot.e_fmt, m, fd);
|
|
} else {
|
|
hdr->extra_size() = xwrite(fd, m.data(), m.size());
|
|
}
|
|
file_align();
|
|
}
|
|
|
|
// recovery_dtbo
|
|
if (access(RECV_DTBO_FILE, R_OK) == 0) {
|
|
hdr->recovery_dtbo_offset() = lseek(fd, 0, SEEK_CUR);
|
|
hdr->recovery_dtbo_size() = restore(fd, RECV_DTBO_FILE);
|
|
file_align();
|
|
}
|
|
|
|
// dtb
|
|
off.dtb = lseek(fd, 0, SEEK_CUR);
|
|
if (access(DTB_FILE, R_OK) == 0) {
|
|
hdr->dtb_size() = restore(fd, DTB_FILE);
|
|
file_align();
|
|
}
|
|
|
|
// Copy boot signature
|
|
if (boot.hdr->signature_size()) {
|
|
xwrite(fd, boot.signature, boot.hdr->signature_size());
|
|
file_align();
|
|
}
|
|
|
|
// vendor ramdisk table
|
|
if (!ramdisk_table.empty()) {
|
|
xwrite(fd, ramdisk_table.data(), sizeof(*ramdisk_table.data()) * ramdisk_table.size());
|
|
file_align();
|
|
}
|
|
|
|
// bootconfig
|
|
if (access(BOOTCONFIG_FILE, R_OK) == 0) {
|
|
hdr->bootconfig_size() = restore(fd, BOOTCONFIG_FILE);
|
|
file_align();
|
|
}
|
|
|
|
// Proprietary stuffs
|
|
if (boot.flags[SEANDROID_FLAG]) {
|
|
xwrite(fd, SEANDROID_MAGIC, 16);
|
|
if (boot.flags[DHTB_FLAG]) {
|
|
xwrite(fd, "\xFF\xFF\xFF\xFF", 4);
|
|
}
|
|
} else if (boot.flags[LG_BUMP_FLAG]) {
|
|
xwrite(fd, LG_BUMP_MAGIC, 16);
|
|
}
|
|
|
|
off.tail = lseek(fd, 0, SEEK_CUR);
|
|
file_align();
|
|
|
|
// vbmeta
|
|
if (boot.flags[AVB_FLAG]) {
|
|
// According to avbtool.py, if the input is not an Android sparse image
|
|
// (which boot images are not), the default block size is 4096
|
|
file_align_with(4096);
|
|
off.vbmeta = lseek(fd, 0, SEEK_CUR);
|
|
uint64_t vbmeta_size = __builtin_bswap64(boot.avb_footer->vbmeta_size);
|
|
xwrite(fd, boot.vbmeta, vbmeta_size);
|
|
}
|
|
|
|
// Pad image to original size if not chromeos (as it requires post processing)
|
|
if (!boot.flags[CHROMEOS_FLAG]) {
|
|
off_t current = lseek(fd, 0, SEEK_CUR);
|
|
if (current < boot.map.size()) {
|
|
write_zero(fd, boot.map.size() - current);
|
|
}
|
|
}
|
|
|
|
/******************
|
|
* Patch the image
|
|
******************/
|
|
|
|
uint32_t aosp_img_size = off.tail - off.header;
|
|
|
|
// Map output image as rw
|
|
mmap_data out(fd, lseek(fd, 0, SEEK_END), true);
|
|
|
|
// MTK headers
|
|
if (boot.flags[MTK_KERNEL]) {
|
|
auto m_hdr = reinterpret_cast<mtk_hdr *>(out.data() + off.kernel);
|
|
m_hdr->size = hdr->kernel_size();
|
|
hdr->kernel_size() += sizeof(mtk_hdr);
|
|
}
|
|
if (boot.flags[MTK_RAMDISK]) {
|
|
auto m_hdr = reinterpret_cast<mtk_hdr *>(out.data() + off.ramdisk);
|
|
m_hdr->size = hdr->ramdisk_size();
|
|
hdr->ramdisk_size() += sizeof(mtk_hdr);
|
|
}
|
|
|
|
// zImage header stub
|
|
if (boot.flags[ZIMAGE_KERNEL] && z_payload_sz) {
|
|
auto head_stub = boot.z_info->new_head(z_payload_sz);
|
|
uint8_t *head_ptr = out.data() + off.kernel + (boot.flags[MTK_KERNEL] ? sizeof(mtk_hdr) : 0);
|
|
memcpy(head_ptr, head_stub.data(), head_stub.size());
|
|
}
|
|
|
|
// Make sure header size matches
|
|
hdr->header_size() = hdr->hdr_size();
|
|
|
|
// Update checksum
|
|
if (char *id = hdr->id()) {
|
|
auto ctx = get_sha(!boot.flags[SHA256_FLAG]);
|
|
uint32_t size = hdr->kernel_size();
|
|
ctx->update(byte_view(out.data() + off.kernel, size));
|
|
ctx->update(byte_view(&size, sizeof(size)));
|
|
size = hdr->ramdisk_size();
|
|
ctx->update(byte_view(out.data() + off.ramdisk, size));
|
|
ctx->update(byte_view(&size, sizeof(size)));
|
|
size = hdr->second_size();
|
|
ctx->update(byte_view(out.data() + off.second, size));
|
|
ctx->update(byte_view(&size, sizeof(size)));
|
|
size = hdr->extra_size();
|
|
if (size) {
|
|
ctx->update(byte_view(out.data() + off.extra, size));
|
|
ctx->update(byte_view(&size, sizeof(size)));
|
|
}
|
|
uint32_t ver = hdr->header_version();
|
|
if (ver == 1 || ver == 2) {
|
|
size = hdr->recovery_dtbo_size();
|
|
ctx->update(byte_view(out.data() + hdr->recovery_dtbo_offset(), size));
|
|
ctx->update(byte_view(&size, sizeof(size)));
|
|
}
|
|
if (ver == 2) {
|
|
size = hdr->dtb_size();
|
|
ctx->update(byte_view(out.data() + off.dtb, size));
|
|
ctx->update(byte_view(&size, sizeof(size)));
|
|
}
|
|
memset(id, 0, BOOT_ID_SIZE);
|
|
ctx->finalize_into(byte_data(id, ctx->output_size()));
|
|
}
|
|
|
|
// Print new header info
|
|
hdr->print();
|
|
|
|
// Copy main header
|
|
if (boot.flags[AMONET_FLAG]) {
|
|
auto real_hdr_sz = std::min(hdr->hdr_space() - AMONET_MICROLOADER_SZ, hdr->hdr_size());
|
|
memcpy(out.data() + off.header + AMONET_MICROLOADER_SZ, hdr->raw_hdr(), real_hdr_sz);
|
|
} else {
|
|
memcpy(out.data() + off.header, hdr->raw_hdr(), hdr->hdr_size());
|
|
}
|
|
|
|
if (boot.flags[AVB_FLAG]) {
|
|
// Copy and patch AVB structures
|
|
auto footer = reinterpret_cast<AvbFooter*>(out.data() + out.size() - sizeof(AvbFooter));
|
|
memcpy(footer, boot.avb_footer, sizeof(AvbFooter));
|
|
footer->original_image_size = __builtin_bswap64(aosp_img_size);
|
|
footer->vbmeta_offset = __builtin_bswap64(off.vbmeta);
|
|
|
|
auto vbmeta = reinterpret_cast<AvbVBMetaImageHeader*>(out.data() + off.vbmeta);
|
|
|
|
if (check_env("PATCHVBMETAFLAG")) {
|
|
vbmeta->flags = __builtin_bswap32(3);
|
|
}
|
|
|
|
// Sync hash descriptor image_size with the new AOSP portion size.
|
|
// Without this, some bootloaders (e.g. Motorola) reject images.
|
|
for (auto &desc : vbmeta->descriptors()) {
|
|
if (__builtin_bswap64(desc.tag) != AVB_DESCRIPTOR_TAG_HASH)
|
|
continue;
|
|
|
|
// enforce size limits; protect against adversarial input.
|
|
size_t buf_remaining = out.data() + out.size() - reinterpret_cast<uint8_t *>(&desc);
|
|
if (buf_remaining < __builtin_bswap64(desc.num_bytes_following) || buf_remaining - __builtin_bswap64(desc.num_bytes_following) < sizeof(AvbDescriptor)) {
|
|
// beware: both conditions are necessary because underflow in the subtraction could wrap
|
|
fprintf(stderr, "AVB hash descriptor num_bytes_following overflows buffer\n");
|
|
break;
|
|
}
|
|
|
|
if (__builtin_bswap64(desc.num_bytes_following) < sizeof(AvbHashDescriptor) - sizeof(AvbDescriptor)) {
|
|
fprintf(stderr, "AvbDescriptor too small to hold AvbHashDescriptor\n");
|
|
break;
|
|
}
|
|
|
|
auto &hd = reinterpret_cast<AvbHashDescriptor &>(desc);
|
|
hd.image_size = __builtin_bswap64(aosp_img_size);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (boot.flags[DHTB_FLAG]) {
|
|
// DHTB header
|
|
auto d_hdr = reinterpret_cast<dhtb_hdr *>(out.data());
|
|
d_hdr->size = aosp_img_size + 16 /* SEANDROID_MAGIC */ + 4 /* DHTB trailer */;
|
|
sha256_hash(byte_view(out.data() + sizeof(dhtb_hdr), d_hdr->size),
|
|
byte_data(d_hdr->checksum, SHA256_DIGEST_SIZE));
|
|
} else if (boot.flags[BLOB_FLAG]) {
|
|
// Blob header
|
|
auto b_hdr = reinterpret_cast<blob_hdr *>(out.data());
|
|
b_hdr->size = aosp_img_size;
|
|
}
|
|
|
|
// Sign the image after we finish patching the boot image
|
|
if (boot.flags[AVB1_SIGNED_FLAG]) {
|
|
byte_view payload(out.data() + off.header, aosp_img_size);
|
|
auto sig = sign_payload(payload);
|
|
if (!sig.empty()) {
|
|
lseek(fd, off.tail, SEEK_SET);
|
|
xwrite(fd, sig.data(), sig.size());
|
|
}
|
|
}
|
|
|
|
close(fd);
|
|
}
|
|
|
|
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);
|
|
}
|