summaryrefslogtreecommitdiffstats
path: root/tools/guest-diag/wall2/wall2_resolver4.py
blob: 140aaa74f0c709d2f91f7d112ee48b29f82c8141 (plain)
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())