aboutsummaryrefslogtreecommitdiffstats
path: root/hw/rollback_boot.py
blob: 1f24f39630d0ee02dcf3f718ffc7a09e27f37801 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#!/usr/bin/env python3
"""Does the PSP bootloader enforce anti-rollback on SYS_DRV?

Unblocked in pass 144: historical `psp_13_0_0_sos.bin` -- the exact file this part loads --
yields older signed SYS_DRV builds for this ASIC:

    running  49.0.54  (0x310036)  161,680 B
    older    49.0.47  (0x31002f)  157,584 B
    older    49.0.37  (0x310025)  157,584 B

The bootloader accepts component loads during driver init (that is how the running firmware
got there), so this substitutes an older signed build in that window and boots.

  * boots -> the bootloader accepts a signed but OLDER component: no anti-rollback, and any
    vulnerability fixed between that build and the current one is reachable again
  * refused -> anti-rollback (or a version/fuse gate) is enforced; a boundary that holds

Before substituting, the extracted blob is checked for the same `$PS1` container magic at
+16 as the running component, so a parse error cannot masquerade as a rejection.

argv[1]: path to sos bin | 'identity' (re-inject the running bytes, positive control)
"""
import ctypes, hashlib, sys
sys.path.insert(0, '/root/psp/hw')
import tinygrad.runtime.support.am.amdev as amdev
import tinygrad.runtime.autogen.am.am as am

def extract_sysdrv(path):
    blob = open(path, 'rb').read()
    hdr = am.struct_psp_firmware_header_v2_0.from_buffer_copy(blob)
    ch = hdr.header
    d = am.struct_psp_fw_bin_desc.from_address(ctypes.addressof(hdr.psp_fw_bin))
    if d.fw_type != am.PSP_FW_TYPE_PSP_SYS_DRV:
        raise RuntimeError('first descriptor is not SYS_DRV (%#x)' % d.fw_type)
    start = d.offset_bytes + ch.ucode_array_offset_bytes
    return blob[start:start + d.size_bytes], d.fw_version

target = sys.argv[1]
sub = None; subver = None
if target != 'identity':
    sub, subver = extract_sysdrv(target)
    if sub[16:20] != b'$PS1':
        print('EXTRACT BAD: container magic at +16 is %r, not $PS1 -> refusing to boot with it'
              % sub[16:20]); sys.exit(2)
    print('substitute: %s ver=%#x len=%d sha=%s magic_ok=True'
          % (target.split('/')[-1], subver, len(sub), hashlib.sha256(sub).hexdigest()[:16]),
          flush=True)
else:
    print('identity run: re-inject the running bytes (positive control)', flush=True)

orig = amdev.AMFirmware.__init__
def patched(self, adev):
    orig(self, adev)
    running = bytes(self.sos_fw[am.PSP_FW_TYPE_PSP_SYS_DRV])
    print('  running SYS_DRV len=%d sha=%s'
          % (len(running), hashlib.sha256(running).hexdigest()[:16]), flush=True)
    newb = running if sub is None else sub
    self.sos_fw[am.PSP_FW_TYPE_PSP_SYS_DRV] = memoryview(bytearray(newb))
    print('  INJECTED len=%d sha=%s (%s)'
          % (len(newb), hashlib.sha256(newb).hexdigest()[:16],
             'identity' if sub is None else 'older build %#x' % subver), flush=True)
amdev.AMFirmware.__init__ = patched

from tinygrad import Device
try:
    adev = Device["AMD"].iface.dev_impl
    alive = bool(adev.psp.is_sos_alive())
    print('BOOTRESULT ok=True sos_alive=%s' % alive, flush=True)
    print('VERDICT %s' % ('ACCEPTED - booted with this SYS_DRV' if alive
                          else 'booted but sOS not alive'), flush=True)
except Exception as e:
    print('BOOTRESULT ok=False err=%s %s' % (type(e).__name__, str(e)[:160]), flush=True)
    print('VERDICT REFUSED - the bootloader would not come up with this SYS_DRV', flush=True)