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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
#!/usr/bin/env python3
"""Is SDMA's PTEPDE (page-table-entry write) privilege-gated like SRBM_WRITE?
`hw/sdma_srbm_probe.py` established [HW] that `SRBM_WRITE` runs only with
`RB_CNTL.RB_PRIV = 1`. PTEPDE is the other packet that matters for isolation: it writes
page-table entries, so a queue that can issue it can forge translations.
Static reading (`../AUDIT-SDMA-01.md`): the PTEPDE handler has **no privilege check**.
It does consult `reg[0x5a6c]` bits 0 and 8, and an earlier draft of that document called
this "PTEPDE's gate" -- that was wrong and is corrected there: the branch does not
reject anything, it selects between two *implementations* (the bits-set path applies an
extra address/value fixup via `_DMA_DATA_6`, then both paths fall into the same PTE
write at `_PKT_0xc00_0`).
So the prediction is: PTEPDE executes regardless of RB_PRIV. This tests it.
Packet (SDMA_PKT_PTEPDE_GEN, matching sdma_v6_0_vm_set_pte_pde):
header, pe_lo, pe_hi, mask_lo, mask_hi, value_lo, value_hi, incr, 0, count-1
which is exactly the order the handler pulls off r1 at 0x09b4..0x09c2.
Method mirrors the SRBM probe: bring the queue up privileged, then clear *only*
RB_PRIV on the live queue, because a queue configured unprivileged from scratch does
not fetch at all and would make "nothing happened" meaningless.
Writes land in a VRAM buffer this script allocates. Nothing else is touched.
usage: PYTHONPATH=/root/psp/tinygrad sdma_ptepde_probe.py --apply
"""
import argparse
import struct
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from sdma_srbm_probe import Probe, SDMA_OP_NOP # noqa: E402
SDMA_OP_PTEPDE = 12
COUNT = 8
def ptepde_gen(pe_mc, value, incr, count):
return [SDMA_OP_PTEPDE,
pe_mc & 0xffffffff, (pe_mc >> 32) & 0xffffffff,
0, 0, # mask/flags
value & 0xffffffff, (value >> 32) & 0xffffffff,
incr, 0, count - 1]
def main():
ap = argparse.ArgumentParser()
ap.add_argument('--apply', action='store_true')
args = ap.parse_args()
if not args.apply:
print('dry run: pass --apply')
return 0
from tinygrad import Device
adev = Device["AMD"].iface.dev_impl
p = Probe(adev)
buf = adev.mm.palloc(0x1000, zero=True, boot=False)
buf_mc = adev.paddr2mc(buf)
print('target buffer paddr 0x%x mc 0x%x, %d entries of 8 bytes' % (buf, buf_mc, COUNT))
results = {}
for priv in (1, 0):
print('\n=== RB_PRIV = %d' % priv)
if priv == 1:
p.setup(rb_priv=1)
else:
p.reg('_RB_CNTL').update(rb_priv=0) # live flip, ring stays configured
print(' RB_CNTL.rb_priv = %s' % p.reg('_RB_CNTL').read_bitfields().get('rb_priv'))
value = 0xAAAA0000BBBB0000 if priv else 0xCCCC0000DDDD0000
adev.vram.view(buf, 0x40)[:] = bytes(0x40)
adev.gmc.flush_hdp()
before = bytes(adev.vram.view(buf, 0x40)[:])
ok, dt = p.submit([SDMA_OP_NOP])
print(' control: NOP consumed=%s (%.1f ms)' % (ok, 1000 * dt))
if not ok:
print(' !! queue not running -- inconclusive for this setting')
results[priv] = None
continue
ok2, dt2 = p.submit(ptepde_gen(buf_mc, value, 0x1000, COUNT))
after = bytes(adev.vram.view(buf, 0x40)[:])
wrote = after != before
ents = struct.unpack('<8Q', after)
print(' PTEPDE consumed=%s (%.1f ms) -> buffer %s' % (
ok2, 1000 * dt2, 'WRITTEN' if wrote else 'unchanged'))
for i, e in enumerate(ents[:4]):
print(' entry[%d] = 0x%016x' % (i, e))
results[priv] = wrote
print('\n=== result')
if results.get(1) is None or results.get(0) is None:
print(' inconclusive: a control failed')
elif results[1] and results[0]:
print(' PTEPDE IS NOT PRIVILEGE-GATED: page-table entries were written with')
print(' RB_PRIV=0, on the same queue where SRBM_WRITE is refused.')
print(' => the two packets have different trust models in this firmware.')
elif results[1] and not results[0]:
print(' PTEPDE is gated after all -- the static reading (no privilege check in')
print(' the handler) is wrong or the gate lives outside the F32 code path.')
else:
print(' PTEPDE did not write even with RB_PRIV=1: addressing/setup is wrong,')
print(' no conclusion can be drawn.')
return 0
if __name__ == '__main__':
raise SystemExit(main())
|