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
|
#!/usr/bin/env python3
"""Can an unprivileged SDMA queue read an arbitrary register into memory?
`SDMA_OP_POLL_REGMEM / POLL_REG_WRITE_MEM` (key 0x0810, entry 0x0653) is the read
counterpart of SRBM_WRITE. The call-clobber-aware taint pass
(`sdma_audit.py taint`) flags it as the *only* place in TH0 where a host dword reaches
a load address:
0654 mov r5, r1 ; dword 1 = SRC_ADDR (ABI: addr_31_2)
0655 mov r8, r1 ; dword 2 = DST_ADDR_LO
0656 mov r9, r1 ; dword 3 = DST_ADDR_HI
0657 ldw r10, unk[r5, #0x0] ; read the register the packet names
0663 stw r10, mem[r0, #0x48] ; value ...
0666 stw #0x4, [r0, #0x81] ; ... DMA'd out to DST_ADDR
SRBM_WRITE is gated on `RB_CNTL.RB_PRIV` ([HW], sdma_srbm_probe.py). This handler shows
no such gate, so the question is whether the *read* side is ungated -- which would let
an unprivileged queue pull privileged register state into its own memory.
Address convention is known, not guessed: amdgpu's `sdma_v6_0_ring_emit_reg_wait`
writes `reg << 2` for POLL_REGMEM packets, i.e. the host register index shifted left by
2. So the test can predict the exact expected value: the host writes a magic into
GRBM_SCRATCH_REG0 first, and the DMA'd result must equal it.
Method as in the other probes: bring the queue up privileged, then clear only RB_PRIV
on the live queue (a queue configured unprivileged from scratch does not fetch at all).
usage: PYTHONPATH=/root/psp/tinygrad sdma_regread_probe.py --apply
"""
import argparse
import os
import struct
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from sdma_srbm_probe import Probe, SDMA_OP_NOP # noqa: E402
SDMA_OP_POLL_REGMEM = 8
SUBOP_POLL_REG_WRITE_MEM = 1
MAGIC = 0xFEEDFACE
def pkt(reg_index, dst_mc):
hdr = SDMA_OP_POLL_REGMEM | (SUBOP_POLL_REG_WRITE_MEM << 8)
return [hdr, (reg_index << 2) & 0xffffffff,
dst_mc & 0xffffffff, (dst_mc >> 32) & 0xffffffff]
def main():
ap = argparse.ArgumentParser()
ap.add_argument('--apply', action='store_true')
ap.add_argument('--target', default='regGRBM_SCRATCH_REG0')
args = ap.parse_args()
if not args.apply:
print('dry run')
return 0
from tinygrad import Device
adev = Device["AMD"].iface.dev_impl
treg = adev.reg(args.target)
tidx = treg.addr[0]
p = Probe(adev)
dst = adev.mm.palloc(0x1000, zero=True, boot=False)
dst_mc = adev.paddr2mc(dst)
print('read %s (host idx 0x%x -> packet SRC_ADDR 0x%x) into mc 0x%x' % (
args.target, tidx, tidx << 2, dst_mc))
results = {}
for priv in (1, 0):
label = 'RB_PRIV=%d' % priv
if priv == 1:
p.setup(rb_priv=1)
else:
p.reg('_RB_CNTL').update(rb_priv=0)
magic = MAGIC if priv else (MAGIC ^ 0xffff)
treg.write(magic)
adev.vram.view(dst, 0x10)[:] = bytes(0x10)
adev.gmc.flush_hdp()
ok, _ = p.submit([SDMA_OP_NOP])
if not ok:
print(' %-12s queue not running -- inconclusive' % label)
results[priv] = None
continue
ok2, dt = p.submit(pkt(tidx, dst_mc))
got = struct.unpack('<I', bytes(adev.vram.view(dst, 4)[:]))[0]
hit = (got == magic)
print(' %-12s consumed=%-5s (%.1f ms) host wrote 0x%08x -> memory got '
'0x%08x %s' % (label, ok2, 1000 * dt, magic, got,
'LEAKED' if hit else ('changed' if got else 'nothing')))
results[priv] = hit
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(' UNGATED REGISTER READ: an unprivileged queue (RB_PRIV=0) DMA\'d a')
print(' register value into its own memory, on the same queue where')
print(' SRBM_WRITE is refused. Reads are not gated; writes are.')
elif results[1] and not results[0]:
print(' gated: the read works only with RB_PRIV=1, same trust model as')
print(' SRBM_WRITE. Not a primitive.')
else:
print(' the read did not land even with RB_PRIV=1 -- addressing or packet')
print(' layout is wrong; no conclusion.')
print('\nqueue alive at end: %s' % p.submit([SDMA_OP_NOP])[0])
return 0
if __name__ == '__main__':
raise SystemExit(main())
|