#!/usr/bin/env python3 """Verify native ABI completeness, JNI exports and 16 KiB ELF load alignment.""" import argparse import os from pathlib import Path import shutil import struct import subprocess import tempfile import zipfile # The native methods each of the app's Java classes declares. ART looks them up # by name only when they are first called, so a missing one fails at run time. NATIVE_METHODS = { "FlectarActivity": ( "nativeGoogleAuthorizationResult", "nativeMicrosoftRedirect", "nativeDocumentResult", "nativeDocumentsResult", "nativeDocumentRequestActive", "nativeSuspendPdfPreview", "nativeTrimMemory", "nativeResumed", "nativePaused", "nativeNotificationOpened", "nativeNotificationPermissionAnswered", ), "GoogleAuthorization": ("nativeResult",), "MailCheckJob": ("nativeCheck", "nativeStop"), "NotificationActionReceiver": ("nativeRespond",), } REQUIRED_EXPORTS = { "ANativeActivity_onCreate", *(f"Java_com_flectar_mail_{class_name}_{method}" for class_name, methods in NATIVE_METHODS.items() for method in methods), } def verify_jni_exports(data, name): # Inspect the dynamic symbol table: strings or a debug symbol table do not # prove that ART can find these functions after release stripping/LTO. readelf = os.environ.get("READELF") or shutil.which("llvm-readelf") or shutil.which("readelf") if not readelf: raise ValueError("JNI verification requires llvm-readelf or readelf (or set READELF)") with tempfile.TemporaryDirectory(prefix="flectar-jni-") as directory: library = Path(directory) / "library.so" library.write_bytes(data) result = subprocess.run( [readelf, "--dyn-syms", "--wide", str(library)], check=True, capture_output=True, text=True, env={**os.environ, "LC_ALL": "C"}, ) exported = set() for line in result.stdout.splitlines(): fields = line.split() if (len(fields) >= 8 and fields[3] == "FUNC" and fields[4] in {"GLOBAL", "WEAK"} and fields[5] in {"DEFAULT", "PROTECTED"} and fields[6] != "UND"): exported.add(fields[7]) missing = REQUIRED_EXPORTS - exported if missing: raise ValueError(f"{name}: missing native exports: {', '.join(sorted(missing))}") def verify_elf(data, name): if data[:6] != b"\x7fELF\x02\x01": raise ValueError(f"{name}: expected little-endian ELF64") offset = struct.unpack_from(" len(data): raise ValueError(f"{name}: invalid ELF program headers") loads = 0 for i in range(count): entry = offset + i * entry_size if struct.unpack_from("