DF-5730 / drive.py
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 | #!/usr/bin/env python3 """DF-5730/DF-5731 โ drive DDB over the socket serial console. DF-5730 (sys/ddb/db_variables.c, beyond DF-5724's $tabstops): db_write_variable() (db_variables.c:120-129) stores any 64-bit value through the FCN_NULL fast path with zero validation for $radix, $maxoff and $maxwidth (table at db_variables.c:48-53). Consequences, all SEMANTIC (no crash โ every consumer traced): * $radix=N (N not 8/10/16): db_lex() parses every subsequent number literal in base N and silently accepts digits invalid for base N (db_lex.c:170-216 โ the per-base digit bound at :192 is only special-cased for base 8). Even the value of a later `set $maxwidth=-55` is misparsed. Restoring requires the forced prefixes 0x/0t because decimal literals are themselves corrupted. * $maxoff=-1 -> (unsigned long)db_maxoff = ULONG_MAX suppresses all symbol+offset resolution (db_sym.c:289, db_print.c:56). * $maxwidth<0 -> db_end_line() always breaks (db_output.c:221), cosmetic. DF-5731 (sys/ddb/db_access.c): db_get_value/db_put_value clamp only size>sizeof(long) (db_access.c:79, :107); a size<=0 would become a huge size_t in db_read_bytes/ db_write_bytes' `while (size-- > 0)` loops (db_interface.c:238-249, :255-326) -> unbounded OOB copy. Demonstrated here to be UNREACHABLE from the console: the write command's size modifier is a fixed whitelist (db_write_cmd.c:60-70) โ `w/q` is rejected. usage: drive.py """ import os, socket, sys, threading, time SOCK = os.environ.get("SOCK", "/tmp/opencode/df-5730-ser.sock") DRAIN = os.environ.get("DRAIN", "/tmp/opencode/df-5730-drain.log") def connect(): while True: try: s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.connect(SOCK) return s except (FileNotFoundError, ConnectionRefusedError) as e: print(f"connect failed: {e}; retrying...", flush=True) time.sleep(2) def send_raw(s, data, chunk=8, pace=0.15): for i in range(0, len(data), chunk): s.sendall(data[i:i+chunk]) time.sleep(pace) def send_line(s, line, wait=4): print(f"\n>>> {line!r}", flush=True) send_raw(s, line.encode() + b"\n") time.sleep(wait) def main(): s = connect() def drainer(sock): with open(DRAIN, "ab", buffering=0) as d: while True: try: b = sock.recv(65536) except OSError: return if not b: return d.write(b) sys.stdout.write(b.decode("latin1")); sys.stdout.flush() threading.Thread(target=drainer, args=(s,), daemon=True).start() time.sleep(2) # ---- controls (defaults: radix=16, maxoff=0x100000, maxwidth=79) ---- send_line(s, "x/x db_active") # output + symbol table alive send_line(s, "print/d 10") # radix 16: "10" parses 0x10 = 16 send_line(s, "print/d 0t10") # forced decimal: 10 # ---- DF-5730: $radix=3 silently corrupts the expression language ---- send_line(s, "set $radix=3") # accepted, no validation send_line(s, "print/d 10") # "10" base 3 -> 3 send_line(s, "print/d 19") # '9' ACCEPTED in base 3 -> 1*3+9 = 12 send_line(s, "set $maxwidth=-55") # parses "55" base 3 = 20 -> stores -20! send_line(s, "print/d $maxwidth") # shows -20 (typed -55, radix corrupted it) # ---- DF-5730: $maxoff=-1 suppresses symbol resolution ---- send_line(s, "print/a db_active") # control: symbol form send_line(s, "set $maxoff=-1") # accepted send_line(s, "print/a db_active") # raw hex now (name suppressed) # ---- restore (0x/0t prefixes bypass the corrupted radix) ---- send_line(s, "set $maxoff=0x100000") send_line(s, "set $radix=0x10") # restore radix 16 via forced hex send_line(s, "set $maxwidth=0t79") send_line(s, "print/d 10") # back to 16 โ proves restoration send_line(s, "print/a db_active") # symbol form back # ---- DF-5731: size modifier is a fixed whitelist โ no way to a # zero/negative size from the console ---- send_line(s, "w/q 0xc0100000 0x90") # -> "Unknown size" (db_write_cmd.c:70) send_line(s, "x/x db_active") # session still healthy, zero traps send_line(s, "c") # continue -> kernel resumes print("\n>>> session script complete", flush=True) time.sleep(3) if __name__ == "__main__": main() |