From 183cd0d49f3a2905db707e160734ffcf7852b9ab Mon Sep 17 00:00:00 2001 From: 0cwa Date: Fri, 25 Sep 2026 10:10:10 +0000 Subject: [PATCH 1/9] fix: bypass Android boot animation APEX precedence --- src/boot_animation.py | 87 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 7 deletions(-) diff --git a/src/boot_animation.py b/src/boot_animation.py index c41fed36..da63a5b9 100644 --- a/src/boot_animation.py +++ b/src/boot_animation.py @@ -32,6 +32,12 @@ ("product", "/product/media/bootanimation.zip"), ("product", "/product/media/bootanimation-dark.zip"), ) +BOOT_ANIMATION_BINARY = "/system/bin/bootanimation" +APEX_BOOT_ANIMATION_PATH = b"/apex/com.android.bootanimation/etc/bootanimation.zip" +DISABLED_APEX_BOOT_ANIMATION_PATH = b"/apex/com.android.bootanimation/etc/bootanimation.off" + +if len(APEX_BOOT_ANIMATION_PATH) != len(DISABLED_APEX_BOOT_ANIMATION_PATH): + raise RuntimeError("boot animation precedence patch must preserve binary length") class BootAnimationError(ValueError): @@ -247,6 +253,64 @@ def install_runtime_payload(ext_fs: dict[str, Any], payload: bytes) -> None: stream.write(payload) + +def disable_apex_boot_animation_precedence(ext_fs: dict[str, Any]) -> None: + """Make Android fall through from its APEX candidate to the product payload.""" + + fs = ext_fs.get("system") + if fs is None: + raise RuntimeError("boot animation system partition is missing") + + try: + with fs.open(BOOT_ANIMATION_BINARY, "r+b") as stream: + binary = stream.read() + active_count = binary.count(APEX_BOOT_ANIMATION_PATH) + disabled_count = binary.count(DISABLED_APEX_BOOT_ANIMATION_PATH) + if active_count != 1 or disabled_count != 0: + raise RuntimeError( + "unsupported bootanimation binary: expected exactly one " + "active APEX animation path" + ) + patched = binary.replace( + APEX_BOOT_ANIMATION_PATH, + DISABLED_APEX_BOOT_ANIMATION_PATH, + 1, + ) + if len(patched) != len(binary): + raise RuntimeError("bootanimation binary patch changed file size") + stream.seek(0) + stream.write(patched) + stream.truncate() + stream.flush() + os.fsync(stream.fileno()) + except FileNotFoundError as exc: + raise RuntimeError( + f"bootanimation binary is missing: {BOOT_ANIMATION_BINARY}" + ) from exc + + +def verify_runtime_installation( + source_path: str | os.PathLike[str], + binary_path: str | os.PathLike[str], + light_path: str | os.PathLike[str], + dark_path: str | os.PathLike[str], +) -> None: + """Verify the effective custom-animation state extracted from a finished OTA.""" + + expected = build_runtime_payload(source_path) + binary = Path(binary_path).read_bytes() + if binary.count(APEX_BOOT_ANIMATION_PATH) != 0: + raise RuntimeError("finished OTA still prefers the APEX boot animation") + if binary.count(DISABLED_APEX_BOOT_ANIMATION_PATH) != 1: + raise RuntimeError("finished OTA lacks the APEX-precedence patch") + + for runtime_path in (Path(light_path), Path(dark_path)): + if runtime_path.read_bytes() != expected: + raise RuntimeError( + f"finished OTA boot animation does not match payload: {runtime_path}" + ) + + def _module_class() -> type[Any]: # Import only when the pinned helper imports this file. This keeps the # standalone validator usable before the helper is fetched. @@ -287,7 +351,7 @@ def from_args(cls, args: argparse.Namespace) -> "BootAnimationMod": def requirements(self) -> ModuleRequirements: return ModuleRequirements( boot_images=set(), - ext_images={"product"}, + ext_images={"system", "product"}, selinux_patching=False, ) @@ -304,6 +368,7 @@ def inject( raise RuntimeError(f"{PAYLOAD_ENVIRONMENT} is not set") payload = build_runtime_payload(payload_path) install_runtime_payload(ext_fs, payload) + disable_apex_boot_animation_precedence(ext_fs) return BootAnimationMod @@ -319,15 +384,23 @@ def inject( def main(argv: list[str]) -> int: - if len(argv) != 3 or argv[1] not in {"validate", "digest"}: - print(f"usage: {argv[0]} validate ", file=sys.stderr) - return 2 try: - print(validate_payload(argv[2])) - except BootAnimationError as exc: + if len(argv) == 3 and argv[1] in {"validate", "digest"}: + print(validate_payload(argv[2])) + return 0 + if len(argv) == 6 and argv[1] == "verify-runtime": + verify_runtime_installation(argv[2], argv[3], argv[4], argv[5]) + return 0 + except (BootAnimationError, OSError, RuntimeError) as exc: print(f"Error: {exc}", file=sys.stderr) return 1 - return 0 + + print( + f"usage: {argv[0]} validate | " + "verify-runtime ", + file=sys.stderr, + ) + return 2 if __name__ == "__main__": From 7cc55362bc2a0f7b1f54c825adb0176f8afb4eef Mon Sep 17 00:00:00 2001 From: 0cwa Date: Fri, 25 Sep 2026 10:10:24 +0000 Subject: [PATCH 2/9] test: cover APEX boot animation precedence --- tests/boot_animation_test.py | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/boot_animation_test.py b/tests/boot_animation_test.py index b09c7c5e..8cea034a 100644 --- a/tests/boot_animation_test.py +++ b/tests/boot_animation_test.py @@ -14,11 +14,15 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) from boot_animation import ( # noqa: E402 + APEX_BOOT_ANIMATION_PATH, + DISABLED_APEX_BOOT_ANIMATION_PATH, MAX_MEMBER_COUNT, BootAnimationError, build_runtime_payload, + disable_apex_boot_animation_precedence, install_runtime_payload, validate_payload, + verify_runtime_installation, ) @@ -92,6 +96,48 @@ def test_runtime_payload_installation() -> None: for target in expected_targets: assert (product.root / target.lstrip("/")).read_bytes() == runtime + system = FakeExtFs(root / "system-fs") + bootanimation = system.root / "system/bin/bootanimation" + bootanimation.parent.mkdir(parents=True, exist_ok=True) + bootanimation.write_bytes( + b"ELF-prefix" + APEX_BOOT_ANIMATION_PATH + b"ELF-suffix" + ) + original_size = bootanimation.stat().st_size + disable_apex_boot_animation_precedence({"system": system}) + patched_binary = bootanimation.read_bytes() + assert bootanimation.stat().st_size == original_size + assert APEX_BOOT_ANIMATION_PATH not in patched_binary + assert patched_binary.count(DISABLED_APEX_BOOT_ANIMATION_PATH) == 1 + + verify_runtime_installation( + source, + bootanimation, + product.root / "product/media/bootanimation.zip", + product.root / "product/media/bootanimation-dark.zip", + ) + + unsupported = FakeExtFs(root / "unsupported-system") + unsupported_binary = unsupported.root / "system/bin/bootanimation" + unsupported_binary.parent.mkdir(parents=True, exist_ok=True) + unsupported_binary.write_bytes(b"ELF-without-apex-path") + try: + disable_apex_boot_animation_precedence({"system": unsupported}) + except RuntimeError: + pass + else: + raise AssertionError("unsupported bootanimation binary was accepted") + + duplicate = FakeExtFs(root / "duplicate-system") + duplicate_binary = duplicate.root / "system/bin/bootanimation" + duplicate_binary.parent.mkdir(parents=True, exist_ok=True) + duplicate_binary.write_bytes(APEX_BOOT_ANIMATION_PATH * 2) + try: + disable_apex_boot_animation_precedence({"system": duplicate}) + except RuntimeError: + pass + else: + raise AssertionError("ambiguous bootanimation binary was accepted") + try: install_runtime_payload({}, runtime) except RuntimeError: From 3b45d3146a4e3dc17672dd8c406d3c6e3eb3c272 Mon Sep 17 00:00:00 2001 From: 0cwa Date: Fri, 25 Sep 2026 10:10:59 +0000 Subject: [PATCH 3/9] build: verify boot animation in finished OTA --- src/util_functions.sh | 111 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/src/util_functions.sh b/src/util_functions.sh index 830a455d..5fd0626e 100755 --- a/src/util_functions.sh +++ b/src/util_functions.sh @@ -613,6 +613,7 @@ function patch_ota() { # output separation, but /data/adb/magisk is provisioned on-device by # Magisk's additional-setup/environment-fix flow. verify_requested_root_outputs || return 1 + verify_requested_boot_animation_outputs || return 1 if [[ "${RESOLVED_ROOT_MODE}" == 'both' ]]; then generate_custota_variant_sidecars "${OUTPUTS[PATCHED_OTA_ROOTLESS]}" "${OUTPUTS[OTA_METADATA_ROOTLESS]}" || return 1 @@ -662,6 +663,116 @@ function extract_ota_boot_target() { printf '%s\n' "${target}" } +function verify_boot_animation_ota() { + local ota_path="${1}" + local temp_dir payload_path avbroot_bin afsr_bin ota_abs + local partition extract_dir unpack_dir image_path raw_image + + [[ -f "${ota_path}" ]] || { + echo "Error: missing OTA for boot-animation inspection: ${ota_path}" >&2 + return 1 + } + + payload_path="$(_boot_animation_payload_path)" || return 1 + temp_dir="$(mktemp -d "${WORKDIR}/boot-animation-verify.XXXXXX")" || return 1 + temp_dir="$(realpath -- "${temp_dir}")" || return 1 + ota_abs="$(realpath -- "${ota_path}")" || { + rm -rf -- "${temp_dir}" + return 1 + } + avbroot_bin="$(resolve_executable_tool avbroot)" || { + rm -rf -- "${temp_dir}" + return 1 + } + afsr_bin="$(resolve_executable_tool afsr)" || { + rm -rf -- "${temp_dir}" + return 1 + } + avbroot_bin="$(realpath -- "${avbroot_bin}")" || { + rm -rf -- "${temp_dir}" + return 1 + } + afsr_bin="$(realpath -- "${afsr_bin}")" || { + rm -rf -- "${temp_dir}" + return 1 + } + + for partition in system product; do + extract_dir="${temp_dir}/extract-${partition}" + unpack_dir="${temp_dir}/unpack-${partition}" + mkdir -p -- "${extract_dir}" "${unpack_dir}" || { + rm -rf -- "${temp_dir}" + return 1 + } + + if ! "${avbroot_bin}" ota extract --input "${ota_abs}" --directory "${extract_dir}" --partition "${partition}" >/dev/null; then + rm -rf -- "${temp_dir}" + return 1 + fi + + image_path="${extract_dir}/${partition}.img" + [[ -s "${image_path}" ]] || { + echo "Error: boot-animation verification did not extract ${partition}.img." >&2 + rm -rf -- "${temp_dir}" + return 1 + } + image_path="$(realpath -- "${image_path}")" || { + rm -rf -- "${temp_dir}" + return 1 + } + + if ! ( + cd -- "${unpack_dir}" && + "${avbroot_bin}" avb unpack --quiet --input "${image_path}" && + raw_image="$(realpath -- raw.img)" && + "${afsr_bin}" unpack --input "${raw_image}" + ); then + rm -rf -- "${temp_dir}" + return 1 + fi + done + + if ! python3 src/boot_animation.py verify-runtime "${payload_path}" "${temp_dir}/unpack-system/fs_tree/system/bin/bootanimation" "${temp_dir}/unpack-product/fs_tree/product/media/bootanimation.zip" "${temp_dir}/unpack-product/fs_tree/product/media/bootanimation-dark.zip"; then + rm -rf -- "${temp_dir}" + return 1 + fi + + rm -rf -- "${temp_dir}" + echo "Verified effective custom boot animation in finished OTA: ${ota_path}" +} + +function verify_requested_boot_animation_outputs() { + local -a ota_paths=() + local ota_path + + [[ "${ADDITIONALS[BOOT_ANIMATION]}" == 'true' ]] || return 0 + + case "${RESOLVED_ROOT_MODE}" in + rootless|magisk) + ota_paths=("${OUTPUTS[PATCHED_OTA]}") + ;; + both) + ota_paths=( + "${OUTPUTS[PATCHED_OTA_ROOTLESS]}" + "${OUTPUTS[PATCHED_OTA_MAGISK]}" + ) + ;; + *) + echo "Error: cannot verify boot animation for root mode: ${RESOLVED_ROOT_MODE}" >&2 + return 1 + ;; + esac + + for ota_path in "${ota_paths[@]}"; do + if ! verify_boot_animation_ota "${ota_path}"; then + rm -f -- "${ota_path}" "${ota_path}.csig" + echo "Error: refusing to keep or publish an OTA without verified custom boot animation." >&2 + return 1 + fi + done +} + + function verify_magisk_ota() { local ota_path="${1}" local expected_preinit="${2}" From db709181c9cf95856c7395c0dd9f47478e21deda Mon Sep 17 00:00:00 2001 From: 0cwa Date: Fri, 25 Sep 2026 10:11:09 +0000 Subject: [PATCH 4/9] build: version boot animation selection contract --- src/ci/selection_variant.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ci/selection_variant.sh b/src/ci/selection_variant.sh index 5debe9f2..4fc3209d 100755 --- a/src/ci/selection_variant.sh +++ b/src/ci/selection_variant.sh @@ -7,6 +7,8 @@ # this helper owns the byte-for-byte representation that is hashed by both the # build and release preflight paths. +BOOT_ANIMATION_SELECTION_CONTRACT="product-stored-apex-bypass-v3" + function selection_variant_manifest() { local field value local -a required_fields=( @@ -116,6 +118,7 @@ function selection_variant_manifest() { if [[ "${SELECTION_BOOT_ANIMATION}" == 'true' ]]; then printf '%s\n' \ 'boot_animation=true' \ + "boot_animation_contract=${BOOT_ANIMATION_SELECTION_CONTRACT}" \ "boot_animation_sha256=${SELECTION_BOOT_ANIMATION_SHA256}" fi } From ee06ccf453cf4f480212c4e5405ffd991f0bfaf1 Mon Sep 17 00:00:00 2001 From: 0cwa Date: Fri, 25 Sep 2026 10:11:12 +0000 Subject: [PATCH 5/9] test: stub finished boot animation verifier --- tests/module_selection_test.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/module_selection_test.sh b/tests/module_selection_test.sh index 156fd80e..4e398c42 100644 --- a/tests/module_selection_test.sh +++ b/tests/module_selection_test.sh @@ -56,6 +56,10 @@ verify_requested_root_outputs() { : } +verify_requested_boot_animation_outputs() { + : +} + fail() { echo "$*" >&2 exit 1 From 9ebe4f275434b391cd48aef53e7b6bc29b0c3619 Mon Sep 17 00:00:00 2001 From: 0cwa Date: Fri, 25 Sep 2026 10:25:28 +0000 Subject: [PATCH 6/9] fix: target the product image mount root --- src/boot_animation.py | 62 +++++-------------------------------------- 1 file changed, 7 insertions(+), 55 deletions(-) diff --git a/src/boot_animation.py b/src/boot_animation.py index da63a5b9..7efd9f78 100644 --- a/src/boot_animation.py +++ b/src/boot_animation.py @@ -29,15 +29,9 @@ MAX_DESCRIPTION_BYTES = 4096 PAYLOAD_ENVIRONMENT = "PIXENEOS_BOOT_ANIMATION_PATH" BOOT_ANIMATION_TARGETS = ( - ("product", "/product/media/bootanimation.zip"), - ("product", "/product/media/bootanimation-dark.zip"), + ("product", "/media/bootanimation.zip"), + ("product", "/media/bootanimation-dark.zip"), ) -BOOT_ANIMATION_BINARY = "/system/bin/bootanimation" -APEX_BOOT_ANIMATION_PATH = b"/apex/com.android.bootanimation/etc/bootanimation.zip" -DISABLED_APEX_BOOT_ANIMATION_PATH = b"/apex/com.android.bootanimation/etc/bootanimation.off" - -if len(APEX_BOOT_ANIMATION_PATH) != len(DISABLED_APEX_BOOT_ANIMATION_PATH): - raise RuntimeError("boot animation precedence patch must preserve binary length") class BootAnimationError(ValueError): @@ -254,56 +248,15 @@ def install_runtime_payload(ext_fs: dict[str, Any], payload: bytes) -> None: -def disable_apex_boot_animation_precedence(ext_fs: dict[str, Any]) -> None: - """Make Android fall through from its APEX candidate to the product payload.""" - - fs = ext_fs.get("system") - if fs is None: - raise RuntimeError("boot animation system partition is missing") - - try: - with fs.open(BOOT_ANIMATION_BINARY, "r+b") as stream: - binary = stream.read() - active_count = binary.count(APEX_BOOT_ANIMATION_PATH) - disabled_count = binary.count(DISABLED_APEX_BOOT_ANIMATION_PATH) - if active_count != 1 or disabled_count != 0: - raise RuntimeError( - "unsupported bootanimation binary: expected exactly one " - "active APEX animation path" - ) - patched = binary.replace( - APEX_BOOT_ANIMATION_PATH, - DISABLED_APEX_BOOT_ANIMATION_PATH, - 1, - ) - if len(patched) != len(binary): - raise RuntimeError("bootanimation binary patch changed file size") - stream.seek(0) - stream.write(patched) - stream.truncate() - stream.flush() - os.fsync(stream.fileno()) - except FileNotFoundError as exc: - raise RuntimeError( - f"bootanimation binary is missing: {BOOT_ANIMATION_BINARY}" - ) from exc - def verify_runtime_installation( source_path: str | os.PathLike[str], - binary_path: str | os.PathLike[str], light_path: str | os.PathLike[str], dark_path: str | os.PathLike[str], ) -> None: - """Verify the effective custom-animation state extracted from a finished OTA.""" + """Verify the custom animation extracted from a finished product image.""" expected = build_runtime_payload(source_path) - binary = Path(binary_path).read_bytes() - if binary.count(APEX_BOOT_ANIMATION_PATH) != 0: - raise RuntimeError("finished OTA still prefers the APEX boot animation") - if binary.count(DISABLED_APEX_BOOT_ANIMATION_PATH) != 1: - raise RuntimeError("finished OTA lacks the APEX-precedence patch") - for runtime_path in (Path(light_path), Path(dark_path)): if runtime_path.read_bytes() != expected: raise RuntimeError( @@ -351,7 +304,7 @@ def from_args(cls, args: argparse.Namespace) -> "BootAnimationMod": def requirements(self) -> ModuleRequirements: return ModuleRequirements( boot_images=set(), - ext_images={"system", "product"}, + ext_images={"product"}, selinux_patching=False, ) @@ -368,7 +321,6 @@ def inject( raise RuntimeError(f"{PAYLOAD_ENVIRONMENT} is not set") payload = build_runtime_payload(payload_path) install_runtime_payload(ext_fs, payload) - disable_apex_boot_animation_precedence(ext_fs) return BootAnimationMod @@ -388,8 +340,8 @@ def main(argv: list[str]) -> int: if len(argv) == 3 and argv[1] in {"validate", "digest"}: print(validate_payload(argv[2])) return 0 - if len(argv) == 6 and argv[1] == "verify-runtime": - verify_runtime_installation(argv[2], argv[3], argv[4], argv[5]) + if len(argv) == 5 and argv[1] == "verify-runtime": + verify_runtime_installation(argv[2], argv[3], argv[4]) return 0 except (BootAnimationError, OSError, RuntimeError) as exc: print(f"Error: {exc}", file=sys.stderr) @@ -397,7 +349,7 @@ def main(argv: list[str]) -> int: print( f"usage: {argv[0]} validate | " - "verify-runtime ", + "verify-runtime ", file=sys.stderr, ) return 2 From accd8e6bc749e7d01e5df4d6e126c5103d6b227b Mon Sep 17 00:00:00 2001 From: 0cwa Date: Fri, 25 Sep 2026 10:25:52 +0000 Subject: [PATCH 7/9] test: enforce product image root paths --- tests/boot_animation_test.py | 51 ++++++------------------------------ 1 file changed, 8 insertions(+), 43 deletions(-) diff --git a/tests/boot_animation_test.py b/tests/boot_animation_test.py index 8cea034a..c8bfbad7 100644 --- a/tests/boot_animation_test.py +++ b/tests/boot_animation_test.py @@ -14,12 +14,9 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) from boot_animation import ( # noqa: E402 - APEX_BOOT_ANIMATION_PATH, - DISABLED_APEX_BOOT_ANIMATION_PATH, MAX_MEMBER_COUNT, BootAnimationError, build_runtime_payload, - disable_apex_boot_animation_precedence, install_runtime_payload, validate_payload, verify_runtime_installation, @@ -87,57 +84,25 @@ def test_runtime_payload_installation() -> None: install_runtime_payload({"product": product}, runtime) expected_targets = [ - "/product/media/bootanimation.zip", - "/product/media/bootanimation-dark.zip", + "/media/bootanimation.zip", + "/media/bootanimation-dark.zip", ] assert [call[0] for call in product.open_calls] == expected_targets assert all(call[1:] == ("wb", 0o644) for call in product.open_calls) - assert all(call[0] == "/product/media" for call in product.mkdir_calls) + assert all(call[0] == "/media" for call in product.mkdir_calls) for target in expected_targets: assert (product.root / target.lstrip("/")).read_bytes() == runtime - system = FakeExtFs(root / "system-fs") - bootanimation = system.root / "system/bin/bootanimation" - bootanimation.parent.mkdir(parents=True, exist_ok=True) - bootanimation.write_bytes( - b"ELF-prefix" + APEX_BOOT_ANIMATION_PATH + b"ELF-suffix" - ) - original_size = bootanimation.stat().st_size - disable_apex_boot_animation_precedence({"system": system}) - patched_binary = bootanimation.read_bytes() - assert bootanimation.stat().st_size == original_size - assert APEX_BOOT_ANIMATION_PATH not in patched_binary - assert patched_binary.count(DISABLED_APEX_BOOT_ANIMATION_PATH) == 1 + # A standalone product.img is mounted at /product. Its filesystem root + # therefore contains /media, not another nested /product directory. + assert not (product.root / "product").exists() verify_runtime_installation( source, - bootanimation, - product.root / "product/media/bootanimation.zip", - product.root / "product/media/bootanimation-dark.zip", + product.root / "media/bootanimation.zip", + product.root / "media/bootanimation-dark.zip", ) - unsupported = FakeExtFs(root / "unsupported-system") - unsupported_binary = unsupported.root / "system/bin/bootanimation" - unsupported_binary.parent.mkdir(parents=True, exist_ok=True) - unsupported_binary.write_bytes(b"ELF-without-apex-path") - try: - disable_apex_boot_animation_precedence({"system": unsupported}) - except RuntimeError: - pass - else: - raise AssertionError("unsupported bootanimation binary was accepted") - - duplicate = FakeExtFs(root / "duplicate-system") - duplicate_binary = duplicate.root / "system/bin/bootanimation" - duplicate_binary.parent.mkdir(parents=True, exist_ok=True) - duplicate_binary.write_bytes(APEX_BOOT_ANIMATION_PATH * 2) - try: - disable_apex_boot_animation_precedence({"system": duplicate}) - except RuntimeError: - pass - else: - raise AssertionError("ambiguous bootanimation binary was accepted") - try: install_runtime_payload({}, runtime) except RuntimeError: From abae8e3c087cb9278c3c57768080fcdb44c88d80 Mon Sep 17 00:00:00 2001 From: 0cwa Date: Fri, 25 Sep 2026 10:25:56 +0000 Subject: [PATCH 8/9] build: verify product-root boot animation paths --- src/util_functions.sh | 66 +++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/src/util_functions.sh b/src/util_functions.sh index 5fd0626e..646e3d40 100755 --- a/src/util_functions.sh +++ b/src/util_functions.sh @@ -666,7 +666,7 @@ function extract_ota_boot_target() { function verify_boot_animation_ota() { local ota_path="${1}" local temp_dir payload_path avbroot_bin afsr_bin ota_abs - local partition extract_dir unpack_dir image_path raw_image + local extract_dir unpack_dir image_path raw_image [[ -f "${ota_path}" ]] || { echo "Error: missing OTA for boot-animation inspection: ${ota_path}" >&2 @@ -697,48 +697,46 @@ function verify_boot_animation_ota() { return 1 } - for partition in system product; do - extract_dir="${temp_dir}/extract-${partition}" - unpack_dir="${temp_dir}/unpack-${partition}" - mkdir -p -- "${extract_dir}" "${unpack_dir}" || { - rm -rf -- "${temp_dir}" - return 1 - } + extract_dir="${temp_dir}/extract-product" + unpack_dir="${temp_dir}/unpack-product" + mkdir -p -- "${extract_dir}" "${unpack_dir}" || { + rm -rf -- "${temp_dir}" + return 1 + } - if ! "${avbroot_bin}" ota extract --input "${ota_abs}" --directory "${extract_dir}" --partition "${partition}" >/dev/null; then - rm -rf -- "${temp_dir}" - return 1 - fi + if ! "${avbroot_bin}" ota extract --input "${ota_abs}" --directory "${extract_dir}" --partition product >/dev/null; then + rm -rf -- "${temp_dir}" + return 1 + fi - image_path="${extract_dir}/${partition}.img" - [[ -s "${image_path}" ]] || { - echo "Error: boot-animation verification did not extract ${partition}.img." >&2 - rm -rf -- "${temp_dir}" - return 1 - } - image_path="$(realpath -- "${image_path}")" || { - rm -rf -- "${temp_dir}" - return 1 - } + image_path="${extract_dir}/product.img" + [[ -s "${image_path}" ]] || { + echo "Error: boot-animation verification did not extract product.img." >&2 + rm -rf -- "${temp_dir}" + return 1 + } + image_path="$(realpath -- "${image_path}")" || { + rm -rf -- "${temp_dir}" + return 1 + } - if ! ( - cd -- "${unpack_dir}" && - "${avbroot_bin}" avb unpack --quiet --input "${image_path}" && - raw_image="$(realpath -- raw.img)" && - "${afsr_bin}" unpack --input "${raw_image}" - ); then - rm -rf -- "${temp_dir}" - return 1 - fi - done + if ! ( + cd -- "${unpack_dir}" && + "${avbroot_bin}" avb unpack --quiet --input "${image_path}" && + raw_image="$(realpath -- raw.img)" && + "${afsr_bin}" unpack --input "${raw_image}" + ); then + rm -rf -- "${temp_dir}" + return 1 + fi - if ! python3 src/boot_animation.py verify-runtime "${payload_path}" "${temp_dir}/unpack-system/fs_tree/system/bin/bootanimation" "${temp_dir}/unpack-product/fs_tree/product/media/bootanimation.zip" "${temp_dir}/unpack-product/fs_tree/product/media/bootanimation-dark.zip"; then + if ! python3 src/boot_animation.py verify-runtime "${payload_path}" "${unpack_dir}/fs_tree/media/bootanimation.zip" "${unpack_dir}/fs_tree/media/bootanimation-dark.zip"; then rm -rf -- "${temp_dir}" return 1 fi rm -rf -- "${temp_dir}" - echo "Verified effective custom boot animation in finished OTA: ${ota_path}" + echo "Verified custom boot animation in finished OTA product image: ${ota_path}" } function verify_requested_boot_animation_outputs() { From f350bbae0f09225f9167e8ef78a6c3918934f4f2 Mon Sep 17 00:00:00 2001 From: 0cwa Date: Fri, 25 Sep 2026 10:26:01 +0000 Subject: [PATCH 9/9] build: identify product-root animation contract --- src/ci/selection_variant.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ci/selection_variant.sh b/src/ci/selection_variant.sh index 4fc3209d..c53b0390 100755 --- a/src/ci/selection_variant.sh +++ b/src/ci/selection_variant.sh @@ -7,7 +7,7 @@ # this helper owns the byte-for-byte representation that is hashed by both the # build and release preflight paths. -BOOT_ANIMATION_SELECTION_CONTRACT="product-stored-apex-bypass-v3" +BOOT_ANIMATION_SELECTION_CONTRACT="product-image-root-stored-v4" function selection_variant_manifest() { local field value