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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
#!/usr/bin/env python3
# wall2_resolver4.py - HOST-SIDE wall#2 resolver v4 (final recipe).
#
# Association proven on r675: the RM keeps, in ONE page, the client's channel
# window records and their memdescs:
# push window record {VA=0x4480000, SIZE=0x20000}
# ring window record {VA=0x44a0000, SIZE=0x40000}
# push memdesc {0x20000, 0x03008000, FIRST_PA, ptr}
# ring memdesc {0x40000, 0x03008000, FIRST_PA, ptr}
# where FIRST_PA is the PA of the window's FIRST VA page. Verified: the ring
# memdesc's PA == the service's validated ring pa. So the push memdesc's PA ==
# the PA of VA 0x4480000 (the push page the service cannot translate).
#
# Recipe: wait for a NEW ring line -> find the page containing the ring window
# record {0x44a0000, 0x40000} -> require the sibling push window record
# {0x4480000, 0x20000} and the memdescs in the same page -> cross-check the
# ring memdesc PA == ring_pa -> inject 0x4480000 -> push memdesc PA (sudo).
import subprocess, struct, time, sys, re, os
RAM = "/dev/shm/vgpu-guest-ram"
PARAM = "/sys/module/vgsp/parameters/sema_map_add"
LOG = "/tmp/opencode/wall2_resolver4.log"
PUSH_VA = 0x4480000
RING_VA = 0x44a0000
WAIT_RING_S = int(os.environ.get("W2_WAIT_RING", "420"))
WATCH_S = int(os.environ.get("W2_WATCH", "180"))
logf = open(LOG, "a", buffering=1)
def log(*a):
s = " ".join(str(x) for x in a)
print(s, flush=True)
logf.write("[%s] %s\n" % (time.strftime("%H:%M:%S"), s))
img = open(RAM, "rb", buffering=0)
def gpa2off(pa):
if pa < 0x80000000: return pa
if pa < 0x180000000: return 0x80000000 + (pa - 0x100000000)
return None
def off2gpa(off):
return off if off < 0x80000000 else 0x100000000 + (off - 0x80000000)
def rd(pa, n):
off = gpa2off(pa)
if off is None: return None
img.seek(off); return img.read(n)
def dmesg():
return subprocess.run(["sudo", "dmesg"], capture_output=True, text=True).stdout
def inject(va, pa, ln=0x1000, ap=1):
cmd = "echo 0x%x,0x%x,0x%x,%d > %s" % (va, pa, ln, ap, PARAM)
r = subprocess.run(["sudo", "sh", "-c", cmd], capture_output=True, text=True)
if r.returncode == 0:
log("INJECT va=0x%x -> pa=0x%x len=0x%x OK" % (va, pa, ln))
return True
log("inject FAIL rc=%d %s" % (r.returncode, r.stderr.strip()))
return False
def find_qpair(a, b):
"""Find qword pairs {a,b}; returns [(page, off)]."""
img.seek(0, 2); fsz = img.tell()
pat = struct.pack("<QQ", a, b)
out = []
CH = 64 << 20
pos = 0
while pos < fsz:
img.seek(pos); d = img.read(min(CH, fsz - pos))
st = 0
while True:
i = d.find(pat, st)
if i < 0: break
st = i + 1
gpa = off2gpa(pos + i)
out.append((gpa & ~0xfff, gpa - (gpa & ~0xfff)))
pos += CH
return out
def scan_page_descs(page):
d = rd(page, 0x1000)
if not d: return []
out = []
for o in range(0, 0x1000 - 24, 8):
sz, fl = struct.unpack_from("<QQ", d, o)
if fl != 0x03008000: continue
if sz not in (0x20000, 0x40000): continue
pa = struct.unpack_from("<Q", d, o + 16)[0]
if 0x100000000 <= pa < 0x180000000:
out.append((sz, o, pa))
return out
def main():
log("=== resolver4 start ===")
def ring_lines():
return re.findall(r"ring rc=0 pa=0x([0-9a-f]+) ap=", dmesg())
base = ring_lines()
log("pre-existing ring lines: %d" % len(base))
ring_pa = None
deadline = time.time() + WAIT_RING_S
while time.time() < deadline:
cur = ring_lines()
if len(cur) > len(base):
ring_pa = int(cur[-1], 16)
break
time.sleep(1)
if not ring_pa:
log("no NEW ring line; exit"); return 1
log("NEW RING PA = 0x%x" % ring_pa)
injected = set()
LEARNED = [0x101e39000, 0x102a62000] # RM window-record pages (stable across runs)
def try_page(page):
descs = scan_page_descs(page)
rdesc = [x for x in descs if x[0] == 0x40000]
pdesc = [x for x in descs if x[0] == 0x20000]
if not rdesc or not pdesc:
return False
if rdesc[0][2] != ring_pa:
return False
ok = False
for (_sz, o, pa) in pdesc:
key = (page, o)
if key in injected: continue
pd = rd(pa, 0x40)
log("page=0x%x ring_desc@0x%x(pa=0x%x OK) push_desc@0x%x pa=0x%x head=%s"
% (page, rdesc[0][1], rdesc[0][2], o, pa,
pd.hex() if pd else "?"))
if inject(PUSH_VA, pa, 0x1000):
injected.add(key); ok = True
return ok
log("learned pages: %s" % [hex(p) for p in LEARNED])
watch = time.time() + WATCH_S
last_full = 0
while time.time() < watch:
hit = False
for page in LEARNED:
if try_page(page):
hit = True
if not hit and time.time() - last_full > 5:
last_full = time.time()
for (page, _off) in find_qpair(RING_VA, 0x40000):
if try_page(page):
if page not in LEARNED:
LEARNED.append(page)
hit = True
time.sleep(0.25)
log("=== resolver4 done: %d injection(s) ===" % len(injected))
return 0
if __name__ == "__main__":
sys.exit(main())
|