#!/usr/bin/env python3 # SPDX-License-Identifier: MPL-2.0 """Build a diagnostic using verified /boot copies. Never writes to a device.""" import argparse import json from pathlib import Path import shlex import stat import struct import subprocess import sys import tempfile from tundra import ROOT, checked_source, cpio, digest, linker, now, save from boot_backup import verify as verify_backup def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--boot-copies', required=True, type=Path) parser.add_argument('--raw-backup', type=Path, help='Use the verified raw slot A header, retaining the inventoried kernel') args = parser.parse_args() copies = args.boot_copies.resolve() manifest = json.loads((copies/'manifest.json').read_text()) if manifest.get('device') != 'sargo': raise ValueError('Expected verified sargo boot copies') for item in manifest['files']: path = copies/item['file'] if path.parent != copies or digest(path) != item['sha256'] or path.stat().st_size != item['bytes']: raise ValueError('Boot copy changed: '+item['file']) original = copies/('boot.img-'+manifest['runningKernel']) folder = ROOT/'out/installed-sargo-diagnostic' raw_manifest_hash = None if args.raw_backup: raw = args.raw_backup.resolve() verify_backup(raw) raw_manifest_hash = digest(raw/'manifest.json') original = raw/'boot_a.img' folder = ROOT/'out/sargo-raw-diagnostic' folder.mkdir(parents=True, exist_ok=True) source = ROOT/'boot/usb-init.c' binary = folder/'init' command = ['clang','--target=aarch64-linux-gnu',f'--ld-path={linker()}', '-Os','-ffreestanding','-fno-builtin','-fno-stack-protector','-fno-pie', '-fno-unwind-tables','-fno-asynchronous-unwind-tables', '-Wall','-Wextra','-Werror','-nostdlib','-static','-Wl,--build-id=none', '-Wl,-e,_start',str(source),'-o',str(binary)] subprocess.run(command, check=True) data = binary.read_bytes() if data[:4] != b'\x7fELF' or struct.unpack_from(' 64*1024*1024: raise ValueError('Image exceeds observed boot partition size') verify = Path(temp)/'verify' result = subprocess.run([sys.executable,str(packager/'unpack_bootimg.py'), '--boot_img',str(image),'--out',str(verify),'--format=mkbootimg'], check=True,capture_output=True,text=True) rebuilt = shlex.split(result.stdout) rebuilt = dict(zip(rebuilt[::2],rebuilt[1::2])) for key,value in fields.items(): if key not in ('--kernel','--ramdisk') and rebuilt.get(key) != value: raise ValueError('Header roundtrip mismatch: '+key) if digest(verify/'kernel') != digest(kernel) or digest(verify/'ramdisk') != digest(ramdisk): raise ValueError('Kernel/ramdisk roundtrip mismatch') metadata = {k:v for k,v in fields.items() if k not in ('--kernel','--ramdisk')} kernel_hash = digest(kernel) save(folder/'build.json',{ 'recordedAt':now(),'device':'sargo','kernel':manifest['runningKernel'], 'originalBootSha256':digest(original),'bootCopiesManifestSha256':digest(copies/'manifest.json'), 'rawBackupManifestSha256':raw_manifest_hash, 'rawBootSlot':'a' if args.raw_backup else None, 'kernelSha256':kernel_hash,'ramdiskSha256':digest(ramdisk), 'initSha256':digest(binary),'sourceSha256':digest(source), 'sharedInitSha256':digest(ROOT/'boot/init.c'),'compileCommand':command, 'header':metadata,'file':image.name,'imageSha256':digest(image), 'bytes':image.stat().st_size,'roundtripPassed':True,'hardwareTested':False, 'flashReady':False,'gaiaIncluded':False,'partitionsWritten':[], 'blockers':['Restoration not validated', *([] if args.raw_backup else ['Raw partition backup not verified by this build']), 'Qualcomm UDC startup not tested with this ramdisk']}) print(image) if __name__ == '__main__': main()