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
74
75
76
|
"""Replicate the off=0x118 / delay=65535 hit with a PAIRED CONTROL.
The sweep flagged: LOAD_TA succeeded (verification passed) but the TA then failed
to answer. Offset 0x118 is image_header+0x18 -- the requested memory footprint,
parsed by bl_load_ta_image AFTER the integrity check -- so this is exactly where
"verify hashed A, loader consumed B" would show up.
BUT the sweep was running at ~92% exception rate, so a lone TIMEOUT may simply be
background noise. This script settles it by interleaving two arms under identical
conditions and comparing TIMEOUT rates:
arm FLIP : one-shot swap armed at (off, delay) <- byte can change
arm CONTROL : identical sequence, but A and B are BOTH the genuine byte
(one-shot still fires, same timing, same code path -- only the
written value is harmless)
If the effect is real, FLIP shows loaded-but-dead TAs and CONTROL does not.
If both arms time out at similar rates, it is background instability, not TOCTOU.
Paired/interleaved so drift in device health hits both arms equally.
"""
import sys, time, json
from psp_out.toctou_sweep import Sweep
OFF = int(sys.argv[1], 0) if len(sys.argv) > 1 else 0x118
DELAY = int(sys.argv[2], 0) if len(sys.argv) > 2 else 65535
N = int(sys.argv[3]) if len(sys.argv) > 3 else 30
def one(s, off, delay, corrupt):
"""One attempt. corrupt=False -> both A and B are genuine (control)."""
g = s.hdcp[off]
b = (g ^ 0xFF) if corrupt else g
s.unload_all(); s.stage()
s.set_ab(off, g, b)
s.oneshot(off, delay)
st, sid, shm, exc = s.load()
if st == 0 and sid:
r = s.invoke(sid, shm)
s.unload_all()
if r[0] == 'TIMEOUT': return 'LOADED_DEAD'
return 'LOADED_OK' if (r[0], r[1][:16] if r[1] else None) == s.baseline else 'LOADED_DIFF'
s.unload_all()
return 'REJECT' if not exc else 'ERR'
if __name__ == '__main__':
s = Sweep()
s.baseline = s.get_baseline(verbose=True)
if s.baseline is None:
s.recover(); s.baseline = s.get_baseline(verbose=True)
assert s.baseline, "no baseline"
print("replicating off=%#x delay=%d, %d paired trials\n" % (OFF, DELAY, N), flush=True)
tally = {'FLIP': {}, 'CTRL': {}}
for i in range(N):
for arm, corrupt in (('FLIP', True), ('CTRL', False)):
try:
r = one(s, OFF, DELAY, corrupt)
except Exception as e:
r = 'EXC:' + type(e).__name__
s.recover(); s.baseline = s.baseline or s.get_baseline()
tally[arm][r] = tally[arm].get(r, 0) + 1
if (i + 1) % 5 == 0:
print(" %2d/%d FLIP=%s CTRL=%s" % (i + 1, N, tally['FLIP'], tally['CTRL']), flush=True)
print("\nFINAL")
for arm in ('FLIP', 'CTRL'):
t = tally[arm]; tot = sum(t.values()) or 1
dead = t.get('LOADED_DEAD', 0) + t.get('LOADED_DIFF', 0)
print(" %-5s %s loaded-but-wrong: %d/%d (%.0f%%)" % (arm, t, dead, tot, 100.0 * dead / tot))
f = tally['FLIP'].get('LOADED_DEAD', 0) + tally['FLIP'].get('LOADED_DIFF', 0)
c = tally['CTRL'].get('LOADED_DEAD', 0) + tally['CTRL'].get('LOADED_DIFF', 0)
print("\nverdict:", "SIGNAL (flip >> control) -- investigate as real TOCTOU" if f > c + 2 else
"NO SIGNAL -- consistent with background instability, not a verify/load split")
json.dump(tally, open('/tmp/toctou_replicate.json', 'w'), indent=1)
|