64 lines
3.8 KiB
Python
64 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Real Pi 5 firmware configuration roundtrips without any hardware access."""
|
|
import importlib.machinery
|
|
import importlib.util
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
project=Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0,str(project/'tools'))
|
|
from eeprom_inputs import prepare,sha256
|
|
cache,pin=prepare()
|
|
work=Path(tempfile.mkdtemp(prefix='m12-eeprom.',dir=project/'out'))
|
|
base=cache/'pieeprom.bin';before=sha256(base)
|
|
# Use the pinned upstream parser to compare immutable firmware payloads as well
|
|
# as configuration text, including both AB slots where present.
|
|
loader=importlib.machinery.SourceFileLoader('upstream_eeprom',str(cache/'rpi-eeprom-config'))
|
|
spec=importlib.util.spec_from_loader(loader.name,loader);upstream=importlib.util.module_from_spec(spec);loader.exec_module(upstream)
|
|
original=upstream.BootloaderImage(str(base))
|
|
protected={s.filename:original.get_file(s.filename) for s in original._sections if s.filename and s.filename not in ['bootconf.txt','bootconf.sig']}
|
|
|
|
def run(arguments,ok=True):
|
|
result=subprocess.run([str(project/'tools/configure-pi-eeprom'),*map(str,arguments)],capture_output=True,text=True,env={**os.environ,'FDS_OFFLINE':'1'})
|
|
assert (result.returncode==0)==ok,(arguments,result.stdout,result.stderr)
|
|
return result
|
|
|
|
for profile in ['production','development']:
|
|
output=work/profile
|
|
run(['--profile',profile,'--output-directory',output])
|
|
manifest=json.loads((output/'manifest.json').read_text())
|
|
assert manifest['hardware_modified'] is False and manifest['custom_inputs_provided'] is False
|
|
for name,digest in manifest['files'].items():assert sha256(output/name)==digest
|
|
image=upstream.BootloaderImage(str(output/'configured.bin'))
|
|
for name,data in protected.items():assert image.get_file(name)==data,(profile,name)
|
|
assert 'NET_INSTALL_ENABLED=0' in image.get_file('bootconf.txt').decode()
|
|
second=work/(profile+'-repeat');run(['--profile',profile,'--output-directory',second])
|
|
assert sha256(output/'configured.bin')==sha256(second/'configured.bin')
|
|
assert sha256(output/'rollback.bin')==sha256(second/'rollback.bin')
|
|
run(['--profile',profile,'--output-directory',output],ok=False)
|
|
print(f'PASS: {profile}: actual Pi 5 firmware roundtrip, immutable payloads, deterministic files and overwrite refusal',flush=True)
|
|
|
|
saved=work/'board.conf'
|
|
saved.write_text('[all]\nBOOT_ORDER=0xf461\nCUSTOM_BOARD_SETTING=retained\n[gpio8=0]\nBOOT_ORDER=0xf7\nNET_INSTALL_ENABLED=1\nOTHER_SETTING=unchanged\n')
|
|
output=work/'custom'
|
|
run(['--current-config',saved,'--output-directory',output])
|
|
changed=(output/'configured.conf').read_text()
|
|
assert changed.count('BOOT_ORDER=')==1 and 'BOOT_ORDER=0xf6' in changed
|
|
assert 'CUSTOM_BOARD_SETTING=retained' in changed and '[gpio8=0]\nOTHER_SETTING=unchanged' in changed
|
|
rollback=upstream.BootloaderImage(str(output/'rollback.bin')).get_file('bootconf.txt').decode()
|
|
assert rollback==saved.read_text(),'Rollback lost the original conditional settings'
|
|
bad=work/'bad.bin';bad.write_bytes(b'not firmware')
|
|
run(['--base-image',bad,'--output-directory',work/'bad'],ok=False)
|
|
run(['--base-image','/dev/null','--output-directory',work/'device'],ok=False)
|
|
large=work/'large.conf';large.write_bytes(b'x'*5000)
|
|
run(['--current-config',large,'--output-directory',work/'large'],ok=False)
|
|
assert sha256(base)==before
|
|
(work/'evidence.json').write_text(json.dumps({'format':1,'upstream_commit':pin['commit'],'base_sha256':before,'hardware_modified':False},indent=2)+'\n')
|
|
link=project/'out/m12-eeprom-latest';link.unlink(missing_ok=True);link.symlink_to(work.name)
|
|
print(f'PASS: preserved custom settings, exact rollback, invalid-input rejection and unchanged firmware input: {work}')
|
|
print('SKIP: EEPROM application, physical boot order, PMIC and Pi timing')
|