From 4b9fe76becedab3fe373360936fd66e3d05cb03d Mon Sep 17 00:00:00 2001 From: blakinio <75369544+blakinio@users.noreply.github.com> Date: Sat, 15 Aug 2026 21:31:13 +0200 Subject: [PATCH 01/12] docs(track-a): claim P2 downstream consumer research --- ...5-track-a-p2-buffer-downstream-consumer.md | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md diff --git a/docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md b/docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md new file mode 100644 index 0000000000..1c1bbf915d --- /dev/null +++ b/docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md @@ -0,0 +1,117 @@ +--- +task_id: OTC-20260815-track-a-p2-buffer-downstream-consumer +status: active +agent: ChatGPT +session_id: chatgpt-p2-downstream-researcher-20260815-2140 +session_role: researcher +session_rotation_count: 1 +project_lane: otclient +lane: P2-NETWORK +track_id: official-client-re +task_kind: runtime-research +phase: p2-buffer-downstream-consumer +branch: research/OTC-20260815-track-a-p2-buffer-downstream-consumer +base_branch: main +base_main: 8fca1c3eee453d0d4ef8a47e0f15c9dbae491b45 +worktree: github-only://blakinio/otclient/refs/heads/research/OTC-20260815-track-a-p2-buffer-downstream-consumer +worktree_mode: isolated_branch_checkout_equivalent +risk: medium +related_pr: null +created: 2026-08-15T21:40:00+02:00 +updated: 2026-08-15T21:40:00+02:00 +lease_expires_at: 2026-08-15T22:25:00+02:00 +owned_paths: + - docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md + - docs/agents/evidence/OTC-20260815-track-a-p2-buffer-downstream-consumer/** + - .github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml + - .github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py +depends_on: + - main@8fca1c3eee453d0d4ef8a47e0f15c9dbae491b45 + - coordinator PR #300 ACCEPT_WITH_EDITS promotion of closed-unmerged PR #308 as pinned evidence only +blocks: [] +policy_version: 2 +prompting_standard_version: 2.1 +prompt_contract_version: 1.0.0 +execution_mode: github-only +execution_reason: exact-build static ELF/disassembly discriminator from promoted retained QBuffer-backed QDataStream boundary +run_scope: single_task +continuation_policy: continue_until_real_stop +task_completion_policy: draft_pr_only +user_communication: terminal_only +implementation_authorized: true +invocation_started_at: 2026-08-15T21:40:00+02:00 +last_progress_at: 2026-08-15T21:40:00+02:00 +ci_checks_for_current_head: 0 +ci_check_generation: initial-downstream-inventory +terminal_ci_wait_started_at: null +terminal_ci_checks_for_current_generation: 0 +unchanged_state_checks: 0 +identical_failure_retries: 0 +repair_cycles_for_current_gate: 0 +context_reconstruction_attempts: 1 +stall_warnings: 0 +active_operation: recover the first exact downstream consumer/transform after the promoted persistent QBuffer-backed byte container using type-anchored static evidence +next_action: inventory exact TProtocolWriter and retained-intermediate callable slots plus bounded construction/use references, then isolate the first function that reads/exposes/transforms retained byte-container state; produce a semantic result without using generic census or historical final-socket evidence +--- + +# Objective + +Start from the coordinator-promoted P2 boundary: + +```text +TProtocolClientMessageProcessor + -> retained intermediate AP 0x2f69e30 / RTTI 0x3080748 + -> retained TProtocolWriter AP 0x2f69dd0 / RTTI 0x3080728 + -> retained helper 0x1960340 / TIODeviceWriter AP 0x2f69d48 + -> retained QDataStream serialization + -> persistent QBuffer-backed QIODevice byte container +``` + +Recover the **first exact downstream consumer or transform** of that retained byte-container state toward framing/final binary egress. Distinguish concrete data flow from lifecycle adjacency. + +# Exact client fence + +```yaml +version_mapping: 15.32.df7b29 +size: 51965216 +sha256: e6c244bd39fe2e0632f6f000efd3147164696efa8e901718668e0442325ff7fe +platform: official_native_linux_only +runner: synology-otclient-01 +``` + +# Pinned promoted facts + +- helper `0x1960340` binds supplied QIODevice shared pair to QDataStream and retains device at `+0x8/+0x10`, stream at `+0x18/+0x20`; +- persistent QBuffer pair is passed to that helper and helper is retained at `TProtocolWriter+0x18/+0x20`; +- retained writer is retained by intermediate, which is retained by `TProtocolClientMessageProcessor`; +- serializer slots `0xc10960` and `0xc20290` use the retained QDataStream; +- local QBuffer slot `0xc20c70` proves QBuffer-backed serialization/exposure; +- only local object lifecycle order is proven. + +# Acceptance boundary + +A positive downstream claim requires exact type/member provenance plus a concrete read/call/data-flow edge from the retained QBuffer/helper/writer state into the candidate transform/consumer. A symbol-name hit, generic Qt/QBuffer census, vtable adjacency, unrelated local QBuffer use, or a historical final-socket observation is insufficient. + +Classify separately: + +```yaml +first_downstream_consumer: PROVEN | CANDIDATE | UNKNOWN +protocol_stage_order: PROVEN_PARTIAL | UNKNOWN +framing: PROVEN | CANDIDATE | UNKNOWN +sequence: PROVEN | CANDIDATE | UNKNOWN +compression: PROVEN | CANDIDATE | UNKNOWN +encryption: PROVEN | CANDIDATE | UNKNOWN +final_binary_egress: PROVEN | CANDIDATE | UNKNOWN +``` + +# Forbidden shortcuts + +Do not use: +- generic QIODevice/QBuffer/QByteArray callsite census as proof; +- vtable adjacency as temporal order; +- historical final-socket run `31825417040` as proof; +- stale/superseded sink models; +- unproven direct DualConnection writer ownership; +- live traffic, credentials, login, attach, gameplay or account state. + +Static exact-build analysis only. Track B is out of scope. Research stays Draft-only; coordinator PR #300 owns promotion. From 235df87ffe963948c0601aabcd339c8f579548dc Mon Sep 17 00:00:00 2001 From: blakinio <75369544+blakinio@users.noreply.github.com> Date: Sat, 15 Aug 2026 21:31:58 +0200 Subject: [PATCH 02/12] research(track-a): inventory typed post-buffer consumers --- ...client-re-p2-buffer-downstream-consumer.py | 273 ++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 .github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py diff --git a/.github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py b/.github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py new file mode 100644 index 0000000000..fca6b557c6 --- /dev/null +++ b/.github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import hashlib +import json +import re +import struct +import subprocess +import sys +from pathlib import Path + +EXPECTED_SHA256 = "e6c244bd39fe2e0632f6f000efd3147164696efa8e901718668e0442325ff7fe" +EXPECTED_SIZE = 51965216 +WRITER_AP = 0x2F69DD0 +WRITER_RTTI = 0x3080728 +IODEVICE_WRITER_AP = 0x2F69D48 +IODEVICE_WRITER_RTTI = 0x3080718 +INTERMEDIATE_AP = 0x2F69E30 +INTERMEDIATE_RTTI = 0x3080748 +BUFFER_WRITER_HELPER = 0x1960340 +KNOWN_INTERMEDIATE = (0x7DE7F0, 0x7DFD60, 0xC10960, 0xC20290, 0xC20C70) + + +def require(value: bool, marker: str) -> None: + if not value: + print(f"P2_DOWNSTREAM_FAIL={marker}", file=sys.stderr) + raise SystemExit(2) + print(f"P2_DOWNSTREAM_OK={marker}") + + +class Elf64: + def __init__(self, path: Path) -> None: + self.data = path.read_bytes() + require(self.data[:4] == b"\x7fELF", "elf_magic") + require(self.data[4] == 2 and self.data[5] == 1, "elf64_little_endian") + phoff = struct.unpack_from(" int: + for vaddr, memsz, offset, _flags in self.loads: + if vaddr <= va and va + size <= vaddr + memsz: + out = offset + (va - vaddr) + require(out + size <= len(self.data), f"file_backed_{va:x}") + return out + raise ValueError(f"unmapped VA 0x{va:x}") + + def read(self, va: int, size: int) -> bytes: + off = self.file_offset(va, size) + return self.data[off : off + size] + + def u64(self, va: int) -> int: + return struct.unpack(" bool: + for vaddr, memsz, _offset, flags in self.loads: + if vaddr <= va < vaddr + memsz: + return bool(flags & 1) + return False + + +def sha256(path: Path) -> str: + h = hashlib.sha256() + with path.open("rb") as f: + for chunk in iter(lambda: f.read(1024 * 1024), b""): + h.update(chunk) + return h.hexdigest() + + +def disassemble(objdump: Path, client: Path, lo: int, hi: int) -> str: + p = subprocess.run( + [str(objdump), "-d", "-Mintel", "--no-show-raw-insn", f"--start-address=0x{lo:x}", f"--stop-address=0x{hi:x}", str(client)], + check=True, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=60, + ) + return p.stdout + + +def normalize_lines(text: str) -> list[str]: + return [line.strip() for line in text.splitlines() if line.strip()] + + +def relevant_lines(text: str) -> list[str]: + needles = ( + "call", "QBuffer", "QDataStream", "QIODevice", "QByteArray", "[rdi+0x18]", "[rdi+0x20]", + "[rbx+0x18]", "[rbx+0x20]", "[rax+0x18]", "[rax+0x20]", "compress", "encrypt", "write", "send", + ) + out: list[str] = [] + for line in normalize_lines(text): + if any(n.lower() in line.lower() for n in needles): + out.append(line) + return out[:120] + + +def vtable_slots(elf: Elf64, ap: int, maximum: int) -> list[dict[str, object]]: + out: list[dict[str, object]] = [] + for index in range(maximum): + va = elf.u64(ap + index * 8) + out.append({"index": index, "va": f"0x{va:x}", "executable": elf.executable(va)}) + return out + + +def parse_args() -> argparse.Namespace: + p = argparse.ArgumentParser() + p.add_argument("--client", type=Path, required=True) + p.add_argument("--objdump", type=Path, required=True) + p.add_argument("--output-json", type=Path, required=True) + p.add_argument("--output-text", type=Path, required=True) + p.add_argument("--evidence", type=Path, required=True) + return p.parse_args() + + +def main() -> int: + args = parse_args() + require(args.client.is_file(), "client_present") + require(args.objdump.is_file(), "objdump_present") + require(args.client.stat().st_size == EXPECTED_SIZE, "exact_client_size") + digest = sha256(args.client) + require(digest == EXPECTED_SHA256, "exact_client_sha256") + elf = Elf64(args.client) + + for ap, rtti, name in ( + (WRITER_AP, WRITER_RTTI, "writer"), + (IODEVICE_WRITER_AP, IODEVICE_WRITER_RTTI, "iodevice_writer"), + (INTERMEDIATE_AP, INTERMEDIATE_RTTI, "intermediate"), + ): + require(elf.u64(ap - 16) == 0, f"{name}_offset_to_top_zero") + require(elf.u64(ap - 8) == rtti, f"{name}_rtti") + + for i, expected in enumerate(KNOWN_INTERMEDIATE): + require(elf.u64(INTERMEDIATE_AP + i * 8) == expected, f"intermediate_slot_{i}") + require(elf.executable(expected), f"intermediate_slot_{i}_exec") + + writer_slots = vtable_slots(elf, WRITER_AP, 12) + iodevice_slots = vtable_slots(elf, IODEVICE_WRITER_AP, 12) + intermediate_slots = vtable_slots(elf, INTERMEDIATE_AP, 8) + + windows: dict[str, dict[str, object]] = {} + for label, slots in (("writer", writer_slots), ("iodevice_writer", iodevice_slots), ("intermediate", intermediate_slots)): + for slot in slots: + if not slot["executable"]: + continue + va = int(str(slot["va"]), 16) + text = disassemble(args.objdump, args.client, va, va + 0x280) + key = f"{label}_slot_{slot['index']}" + windows[key] = { + "va": slot["va"], + "relevant": relevant_lines(text), + "reads_retained_plus18": bool(re.search(r"\[[^\]]+\+0x18\]", text, re.I)), + "reads_retained_plus20": bool(re.search(r"\[[^\]]+\+0x20\]", text, re.I)), + "qbuffer": "QBuffer" in text, + "qdatastream": "QDataStream" in text, + "qiodevice": "QIODevice" in text, + "qbytearray": "QByteArray" in text, + } + + # Construction/retention neighborhood is a bounded provenance control, not protocol-order proof. + setup = disassemble(args.objdump, args.client, 0x1970C60, 0x1971250) + require("1970d16:" in setup and "1960340" in setup, "persistent_helper_call_revalidated") + require("1970d71:" in setup and "+0x18" in setup, "writer_retention_revalidated") + require("1971068:" in setup and "+0x18" in setup, "processor_retention_revalidated") + + # Candidate means a TYPE-ANCHORED callable slot reads retained-looking +0x18/+0x20 and also + # contains a concrete Qt byte/device/container operation. It is not yet a temporal/framing proof. + candidates: list[dict[str, object]] = [] + for key, item in windows.items(): + if not key.startswith(("writer_", "iodevice_writer_")): + continue + member_read = bool(item["reads_retained_plus18"] or item["reads_retained_plus20"]) + byte_op = bool(item["qbuffer"] or item["qiodevice"] or item["qbytearray"]) + if member_read and byte_op: + candidates.append({"slot": key, "va": item["va"], "relevant": item["relevant"]}) + + result = { + "schema_version": 1, + "exact_client": { + "sha256": digest, + "size": EXPECTED_SIZE, + "version_mapping": "15.32.df7b29", + "platform": "official_native_linux_only", + }, + "pinned": { + "writer_ap": "0x2f69dd0", + "writer_rtti": "0x3080728", + "iodevice_writer_ap": "0x2f69d48", + "iodevice_writer_rtti": "0x3080718", + "intermediate_ap": "0x2f69e30", + "intermediate_rtti": "0x3080748", + "helper": "0x1960340", + }, + "slots": { + "writer": writer_slots, + "iodevice_writer": iodevice_slots, + "intermediate": intermediate_slots, + }, + "windows": windows, + "candidate_count": len(candidates), + "candidates": candidates, + "classification": { + "first_downstream_consumer": "CANDIDATE" if candidates else "UNKNOWN", + "protocol_stage_order": "UNKNOWN", + "framing": "UNKNOWN", + "sequence": "UNKNOWN", + "compression": "UNKNOWN", + "encryption": "UNKNOWN", + "final_binary_egress": "UNKNOWN", + }, + "negative_controls": { + "generic_qiodevice_census_used_as_proof": False, + "generic_qbuffer_census_used_as_proof": False, + "vtable_adjacency_used_as_temporal_proof": False, + "historical_final_socket_run_used_as_proof": False, + "direct_dualconnection_writer_ownership_assumed": False, + }, + "semantic_result": "TYPED_DOWNSTREAM_CANDIDATES_INVENTORIED" if candidates else "BOUNDED_TYPED_INVENTORY_NO_CONSUMER_PROVEN", + } + + args.output_json.parent.mkdir(parents=True, exist_ok=True) + args.output_json.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="utf-8") + args.output_text.write_text( + "\n".join( + [ + f"P2_DOWNSTREAM_RESULT={result['semantic_result']}", + f"CLIENT_SHA256={digest}", + f"CLIENT_SIZE={EXPECTED_SIZE}", + f"TYPED_CANDIDATE_COUNT={len(candidates)}", + f"FIRST_DOWNSTREAM_CONSUMER={result['classification']['first_downstream_consumer']}", + "PROTOCOL_STAGE_ORDER=UNKNOWN", + "PROTOCOL_FRAMING=UNKNOWN", + "SEQUENCE=UNKNOWN", + "COMPRESSION=UNKNOWN", + "ENCRYPTION=UNKNOWN", + "FINAL_BINARY_EGRESS=UNKNOWN", + ] + ) + "\n", + encoding="utf-8", + ) + + evidence_lines = [ + "# Exact typed vtable slot inventory", + json.dumps(result["slots"], indent=2, sort_keys=True), + "", + "# Candidate windows", + ] + for c in candidates: + evidence_lines.append(f"## {c['slot']} @ {c['va']}") + evidence_lines.extend(str(x) for x in c["relevant"]) + evidence_lines.extend(["", "# Persistent provenance control", *relevant_lines(setup)]) + args.evidence.write_text("\n".join(evidence_lines) + "\n", encoding="utf-8") + + print("P2_DOWNSTREAM_COMPLETE=true") + print(f"P2_DOWNSTREAM_RESULT={result['semantic_result']}") + print(f"P2_DOWNSTREAM_TYPED_CANDIDATE_COUNT={len(candidates)}") + print("P2_DOWNSTREAM_PROTOCOL_STAGE_ORDER=UNKNOWN") + print("P2_DOWNSTREAM_FINAL_BINARY_EGRESS=UNKNOWN") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 65b349e4b70f6a31e5c210610b638eefc98baee7 Mon Sep 17 00:00:00 2001 From: blakinio <75369544+blakinio@users.noreply.github.com> Date: Sat, 15 Aug 2026 21:32:16 +0200 Subject: [PATCH 03/12] research(track-a): run typed post-buffer consumer probe --- ...lient-re-p2-buffer-downstream-consumer.yml | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 .github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml diff --git a/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml b/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml new file mode 100644 index 0000000000..09e173358e --- /dev/null +++ b/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml @@ -0,0 +1,116 @@ +name: Track A P2 buffer downstream consumer + +on: + push: + branches: [research/OTC-20260815-track-a-p2-buffer-downstream-consumer] + paths: + - .github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml + - .github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py + - docs/agents/evidence/OTC-20260815-track-a-p2-buffer-downstream-consumer/** + - docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md + workflow_dispatch: + +permissions: + contents: read + +jobs: + typed-downstream-probe: + runs-on: [otclient, synology] + timeout-minutes: 8 + env: + EXPECTED_CLIENT_SHA256: e6c244bd39fe2e0632f6f000efd3147164696efa8e901718668e0442325ff7fe + EXPECTED_CLIENT_SIZE: '51965216' + EXPECTED_HEAD_BRANCH: research/OTC-20260815-track-a-p2-buffer-downstream-consumer + steps: + - name: Checkout exact Draft head + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 + with: + persist-credentials: false + + - name: Verify exact client and static tool context + shell: bash + run: | + set -Eeuo pipefail + [[ "$GITHUB_REPOSITORY" == blakinio/otclient ]] + [[ "$GITHUB_REF_NAME" == "$EXPECTED_HEAD_BRANCH" ]] + [[ "$RUNNER_NAME" == synology-otclient-01 ]] + [[ "$RUNNER_OS" == Linux ]] + python3 -m py_compile .github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py + + client='' + state='' + for candidate in /work/_otclient_tibia_re_state /home/runner/_work/_otclient_tibia_re_state; do + probe="$candidate/home/.local/share/CipSoft GmbH/Tibia/packages/Tibia/bin/client" + [[ -x "$probe" ]] || continue + size="$(stat -c '%s' "$probe")" + sha="$(sha256sum "$probe" | awk '{print $1}')" + if [[ "$size" == "$EXPECTED_CLIENT_SIZE" && "$sha" == "$EXPECTED_CLIENT_SHA256" ]]; then + client="$probe" + state="$candidate" + break + fi + done + [[ -n "$client" && -n "$state" ]] + + objdump="$(command -v objdump || true)" + if [[ -z "$objdump" ]]; then + pinned="$state/toolroot/usr/bin/objdump" + [[ -x "$pinned" ]] + wrapper="$RUNNER_TEMP/track-a-p2-downstream-objdump" + cat > "$wrapper" </dev/null + + { + echo "TRACK_A_P2_CLIENT=$client" + echo "TRACK_A_P2_OBJDUMP=$objdump" + echo "TRACK_A_P2_STATE=$state" + } >> "$GITHUB_ENV" + echo "TRACK_A_P2_DOWNSTREAM_EXACT_HEAD=$GITHUB_SHA" + echo "TRACK_A_P2_DOWNSTREAM_EXACT_CLIENT_VERIFIED=true" + + - name: Inventory typed downstream candidates + shell: bash + run: | + set -Eeuo pipefail + out=artifacts/p2-buffer-downstream-consumer + mkdir -p "$out" + python3 .github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py \ + --client "$TRACK_A_P2_CLIENT" \ + --objdump "$TRACK_A_P2_OBJDUMP" \ + --output-json "$out/result.json" \ + --output-text "$out/result.txt" \ + --evidence "$out/evidence.txt" \ + | tee "$out/validation.log" + grep -Fxq 'P2_DOWNSTREAM_COMPLETE=true' "$out/validation.log" + grep -Eq '^P2_DOWNSTREAM_RESULT=(TYPED_DOWNSTREAM_CANDIDATES_INVENTORIED|BOUNDED_TYPED_INVENTORY_NO_CONSUMER_PROVEN)$' "$out/validation.log" + grep -Fxq 'P2_DOWNSTREAM_PROTOCOL_STAGE_ORDER=UNKNOWN' "$out/validation.log" + grep -Fxq 'P2_DOWNSTREAM_FINAL_BINARY_EGRESS=UNKNOWN' "$out/validation.log" + python3 - "$out/result.json" <<'PY' + import json, sys + d=json.load(open(sys.argv[1],encoding='utf-8')) + assert d['exact_client']['sha256']=='e6c244bd39fe2e0632f6f000efd3147164696efa8e901718668e0442325ff7fe' + assert d['exact_client']['size']==51965216 + assert d['classification']['protocol_stage_order']=='UNKNOWN' + assert d['classification']['framing']=='UNKNOWN' + assert d['classification']['final_binary_egress']=='UNKNOWN' + assert not d['negative_controls']['generic_qiodevice_census_used_as_proof'] + assert not d['negative_controls']['generic_qbuffer_census_used_as_proof'] + assert not d['negative_controls']['vtable_adjacency_used_as_temporal_proof'] + assert not d['negative_controls']['historical_final_socket_run_used_as_proof'] + PY + echo TRACK_A_P2_DOWNSTREAM_VALIDATED=true + + - name: Upload sanitized static evidence only + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: track-a-p2-buffer-downstream-consumer-${{ github.run_id }} + path: artifacts/p2-buffer-downstream-consumer/** + if-no-files-found: error + retention-days: 7 From aa9210045be552ed9c02b3bbbbdf410528af4b0a Mon Sep 17 00:00:00 2001 From: blakinio <75369544+blakinio@users.noreply.github.com> Date: Sat, 15 Aug 2026 21:41:08 +0200 Subject: [PATCH 04/12] research(track-a): target processor downstream flow --- ...client-re-p2-buffer-downstream-consumer.py | 272 ++++++++++-------- ...lient-re-p2-buffer-downstream-consumer.yml | 19 +- 2 files changed, 169 insertions(+), 122 deletions(-) diff --git a/.github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py b/.github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py index fca6b557c6..4709bd8d50 100644 --- a/.github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py +++ b/.github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py @@ -18,8 +18,19 @@ IODEVICE_WRITER_RTTI = 0x3080718 INTERMEDIATE_AP = 0x2F69E30 INTERMEDIATE_RTTI = 0x3080748 -BUFFER_WRITER_HELPER = 0x1960340 -KNOWN_INTERMEDIATE = (0x7DE7F0, 0x7DFD60, 0xC10960, 0xC20290, 0xC20C70) +INTERMEDIATE_PREFIX = (0x7DE7F0, 0x7DFD60, 0xC10960, 0xC20290, 0xC20C70) +CONNECTION_SITE = 0x19716A3 +INVOKER_LEA = 0x1971670 +SIGNAL_META_LEA = 0x1971677 +OUTER_CAPTURE_STORE = 0x197168D +INVOKER = 0x7DD630 +SIGNAL_META = 0x3085B60 +CONNECT_IMPL = 0x4DD800 +CLIENT_MESSAGE_PROCESSOR = 0xC2DF80 +RAW_DATA_PROCESSOR = 0xB47130 +DUAL_ENTRY_78 = 0xB56970 +DUAL_ENTRY_80 = 0xB56D60 +DUAL_PRECONDITION = 0xB40370 def require(value: bool, marker: str) -> None: @@ -40,11 +51,11 @@ def __init__(self, path: Path) -> None: self.loads: list[tuple[int, int, int, int]] = [] for index in range(phnum): off = phoff + index * phentsize - p_type, p_flags, p_offset, p_vaddr, _, _p_filesz, p_memsz, _ = struct.unpack_from( + p_type, p_flags, p_offset, p_vaddr, _, p_filesz, p_memsz, _ = struct.unpack_from( " int: @@ -68,6 +79,16 @@ def executable(self, va: int) -> bool: return bool(flags & 1) return False + def rip_target(self, site: int, length: int = 7, disp_offset: int = 3) -> int: + raw = self.read(site, length) + disp = struct.unpack_from(" int: + raw = self.read(site, 5) + require(raw[0] == 0xE8, f"direct_call_opcode_{site:x}") + return (site + 5 + struct.unpack_from(" str: h = hashlib.sha256() @@ -89,42 +110,57 @@ def disassemble(objdump: Path, client: Path, lo: int, hi: int) -> str: return p.stdout -def normalize_lines(text: str) -> list[str]: - return [line.strip() for line in text.splitlines() if line.strip()] +def insn_lines(text: str) -> list[str]: + return [line.strip() for line in text.splitlines() if re.match(r"^[0-9a-f]+:", line.strip(), re.I)] -def relevant_lines(text: str) -> list[str]: +def bounded_relevant(text: str) -> list[str]: needles = ( - "call", "QBuffer", "QDataStream", "QIODevice", "QByteArray", "[rdi+0x18]", "[rdi+0x20]", - "[rbx+0x18]", "[rbx+0x20]", "[rax+0x18]", "[rax+0x20]", "compress", "encrypt", "write", "send", + "call", "+0xa00", "+0xa08", "+0xa10", "+0xa18", "+0xc18", "+0xc20", + "QBuffer", "QDataStream", "QIODevice", "QByteArray", "write", "send", "compress", "encrypt", + "+0x18", "+0x20", "+0x28", "+0x30", ) - out: list[str] = [] - for line in normalize_lines(text): - if any(n.lower() in line.lower() for n in needles): - out.append(line) - return out[:120] + return [line for line in insn_lines(text) if any(n.lower() in line.lower() for n in needles)][:200] -def vtable_slots(elf: Elf64, ap: int, maximum: int) -> list[dict[str, object]]: +def actual_vtable_slots(elf: Elf64, ap: int, maximum: int) -> list[dict[str, object]]: out: list[dict[str, object]] = [] for index in range(maximum): - va = elf.u64(ap + index * 8) - out.append({"index": index, "va": f"0x{va:x}", "executable": elf.executable(va)}) + value = elf.u64(ap + index * 8) + is_exec = elf.executable(value) + out.append({"index": index, "va": f"0x{value:x}", "executable": is_exec}) + if not is_exec: + break return out -def parse_args() -> argparse.Namespace: +def body_flags(text: str) -> dict[str, object]: + lowered = text.lower() + return { + "outer_a00": bool(re.search(r"\+0xa00\]", lowered)), + "outer_a10": bool(re.search(r"\+0xa10\]", lowered)), + "outer_c18": bool(re.search(r"\+0xc18\]", lowered)), + "qbuffer": "qbuffer" in lowered, + "qdatastream": "qdatastream" in lowered, + "qiodevice": "qiodevice" in lowered, + "qbytearray": "qbytearray" in lowered, + "write_text": "write" in lowered, + "send_text": "send" in lowered, + "compress_text": "compress" in lowered, + "encrypt_text": "encrypt" in lowered, + "calls": [line for line in insn_lines(text) if "call" in line.lower()][:80], + } + + +def main() -> int: p = argparse.ArgumentParser() p.add_argument("--client", type=Path, required=True) p.add_argument("--objdump", type=Path, required=True) p.add_argument("--output-json", type=Path, required=True) p.add_argument("--output-text", type=Path, required=True) p.add_argument("--evidence", type=Path, required=True) - return p.parse_args() - + args = p.parse_args() -def main() -> int: - args = parse_args() require(args.client.is_file(), "client_present") require(args.objdump.is_file(), "objdump_present") require(args.client.stat().st_size == EXPECTED_SIZE, "exact_client_size") @@ -140,77 +176,80 @@ def main() -> int: require(elf.u64(ap - 16) == 0, f"{name}_offset_to_top_zero") require(elf.u64(ap - 8) == rtti, f"{name}_rtti") - for i, expected in enumerate(KNOWN_INTERMEDIATE): + writer_slots = actual_vtable_slots(elf, WRITER_AP, 12) + require([int(str(x["va"]), 16) for x in writer_slots[:4]] == [0x7E3C10, 0x7E3CA0, 0xC262B0, 0xC21EC0], "writer_real_slots_0_3") + require(len(writer_slots) == 5 and writer_slots[4]["va"] == "0x0" and not writer_slots[4]["executable"], "writer_vtable_stops_at_slot4_zero") + require(elf.u64(WRITER_AP + 5 * 8) == 0x3080738, "writer_neighbor_rtti_not_slot") + require(elf.u64(WRITER_AP + 8 * 8) == 0xD114C0, "historical_false_positive_address_present_but_not_writer_slot") + + for i, expected in enumerate(INTERMEDIATE_PREFIX): require(elf.u64(INTERMEDIATE_AP + i * 8) == expected, f"intermediate_slot_{i}") require(elf.executable(expected), f"intermediate_slot_{i}_exec") - writer_slots = vtable_slots(elf, WRITER_AP, 12) - iodevice_slots = vtable_slots(elf, IODEVICE_WRITER_AP, 12) - intermediate_slots = vtable_slots(elf, INTERMEDIATE_AP, 8) - - windows: dict[str, dict[str, object]] = {} - for label, slots in (("writer", writer_slots), ("iodevice_writer", iodevice_slots), ("intermediate", intermediate_slots)): - for slot in slots: - if not slot["executable"]: - continue - va = int(str(slot["va"]), 16) - text = disassemble(args.objdump, args.client, va, va + 0x280) - key = f"{label}_slot_{slot['index']}" - windows[key] = { - "va": slot["va"], - "relevant": relevant_lines(text), - "reads_retained_plus18": bool(re.search(r"\[[^\]]+\+0x18\]", text, re.I)), - "reads_retained_plus20": bool(re.search(r"\[[^\]]+\+0x20\]", text, re.I)), - "qbuffer": "QBuffer" in text, - "qdatastream": "QDataStream" in text, - "qiodevice": "QIODevice" in text, - "qbytearray": "QByteArray" in text, - } - - # Construction/retention neighborhood is a bounded provenance control, not protocol-order proof. - setup = disassemble(args.objdump, args.client, 0x1970C60, 0x1971250) - require("1970d16:" in setup and "1960340" in setup, "persistent_helper_call_revalidated") - require("1970d71:" in setup and "+0x18" in setup, "writer_retention_revalidated") - require("1971068:" in setup and "+0x18" in setup, "processor_retention_revalidated") - - # Candidate means a TYPE-ANCHORED callable slot reads retained-looking +0x18/+0x20 and also - # contains a concrete Qt byte/device/container operation. It is not yet a temporal/framing proof. - candidates: list[dict[str, object]] = [] - for key, item in windows.items(): - if not key.startswith(("writer_", "iodevice_writer_")): - continue - member_read = bool(item["reads_retained_plus18"] or item["reads_retained_plus20"]) - byte_op = bool(item["qbuffer"] or item["qiodevice"] or item["qbytearray"]) - if member_read and byte_op: - candidates.append({"slot": key, "va": item["va"], "relevant": item["relevant"]}) + require(elf.rip_target(INVOKER_LEA) == INVOKER, "connection_invoker_target") + require(elf.rip_target(SIGNAL_META_LEA) == SIGNAL_META, "connection_signal_meta_target") + require(elf.call_target(CONNECTION_SITE) == CONNECT_IMPL, "connection_connectimpl_target") + + connection = disassemble(args.objdump, args.client, 0x1971635, 0x19716A8) + connection_lines = insn_lines(connection) + require(any(line.lower().startswith("197168d:") and "+0x10" in line.lower() for line in connection_lines), "connection_captures_outer_at_slotobject_plus10") + + targets = { + "invoker_0x7dd630": (INVOKER, 0x7DD9A0), + "client_message_processor_0xc2df80": (CLIENT_MESSAGE_PROCESSOR, 0xC2E680), + "raw_data_processor_0xb47130": (RAW_DATA_PROCESSOR, 0xB47880), + "dual_entry_0xb56970": (DUAL_ENTRY_78, 0xB56D60), + "dual_entry_0xb56d60": (DUAL_ENTRY_80, 0xB57280), + "dual_precondition_0xb40370": (DUAL_PRECONDITION, 0xB40880), + } + bodies: dict[str, dict[str, object]] = {} + for name, (lo, hi) in targets.items(): + require(elf.executable(lo), f"target_exec_{lo:x}") + text = disassemble(args.objdump, args.client, lo, hi) + bodies[name] = { + "start": f"0x{lo:x}", + "stop": f"0x{hi:x}", + "flags": body_flags(text), + "relevant": bounded_relevant(text), + "instruction_count": len(insn_lines(text)), + } + + inv_flags = bodies["invoker_0x7dd630"]["flags"] + proc_flags = bodies["client_message_processor_0xc2df80"]["flags"] + raw_flags = bodies["raw_data_processor_0xb47130"]["flags"] + exact_processor_graph_signal = bool(inv_flags["outer_a00"] or inv_flags["outer_a10"] or proc_flags["outer_a10"]) + concrete_byte_type_signal = any( + bool(flags[key]) + for flags in (inv_flags, proc_flags, raw_flags) + for key in ("qbuffer", "qbytearray", "qiodevice") + ) + # A targeted capture is not itself a downstream proof. Only an explicit byte/container signal on + # the exact processor path earns CANDIDATE; otherwise the byte consumer remains UNKNOWN. + first_downstream = "CANDIDATE" if exact_processor_graph_signal and concrete_byte_type_signal else "UNKNOWN" result = { - "schema_version": 1, - "exact_client": { - "sha256": digest, - "size": EXPECTED_SIZE, - "version_mapping": "15.32.df7b29", - "platform": "official_native_linux_only", + "schema_version": 2, + "exact_client": {"sha256": digest, "size": EXPECTED_SIZE, "version_mapping": "15.32.df7b29", "platform": "official_native_linux_only"}, + "writer_vtable": writer_slots, + "false_positive_correction": { + "writer_stops_at_slot4_zero": True, + "writer_ap_plus_0x28_is_neighbor_rtti_0x3080738": True, + "0xd114c0_is_not_tprotocolwriter_slot8": True, + "fixed_0x280_window_candidates_from_run_31904191629_are_not_semantic_proof": True, }, - "pinned": { - "writer_ap": "0x2f69dd0", - "writer_rtti": "0x3080728", - "iodevice_writer_ap": "0x2f69d48", - "iodevice_writer_rtti": "0x3080718", - "intermediate_ap": "0x2f69e30", - "intermediate_rtti": "0x3080748", - "helper": "0x1960340", + "connection": { + "site": "0x19716a3", + "invoker": "0x7dd630", + "signal_meta": "0x3085b60", + "connect_impl": "0x4dd800", + "outer_capture_store": "0x197168d", + "relevant": bounded_relevant(connection), }, - "slots": { - "writer": writer_slots, - "iodevice_writer": iodevice_slots, - "intermediate": intermediate_slots, - }, - "windows": windows, - "candidate_count": len(candidates), - "candidates": candidates, + "targets": bodies, "classification": { - "first_downstream_consumer": "CANDIDATE" if candidates else "UNKNOWN", + "exact_processor_graph_signal": exact_processor_graph_signal, + "concrete_byte_type_signal": concrete_byte_type_signal, + "first_downstream_consumer": first_downstream, "protocol_stage_order": "UNKNOWN", "framing": "UNKNOWN", "sequence": "UNKNOWN", @@ -225,45 +264,44 @@ def main() -> int: "historical_final_socket_run_used_as_proof": False, "direct_dualconnection_writer_ownership_assumed": False, }, - "semantic_result": "TYPED_DOWNSTREAM_CANDIDATES_INVENTORIED" if candidates else "BOUNDED_TYPED_INVENTORY_NO_CONSUMER_PROVEN", + "semantic_result": "TARGETED_PROCESSOR_FLOW_CAPTURED", } args.output_json.parent.mkdir(parents=True, exist_ok=True) args.output_json.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="utf-8") - args.output_text.write_text( - "\n".join( - [ - f"P2_DOWNSTREAM_RESULT={result['semantic_result']}", - f"CLIENT_SHA256={digest}", - f"CLIENT_SIZE={EXPECTED_SIZE}", - f"TYPED_CANDIDATE_COUNT={len(candidates)}", - f"FIRST_DOWNSTREAM_CONSUMER={result['classification']['first_downstream_consumer']}", - "PROTOCOL_STAGE_ORDER=UNKNOWN", - "PROTOCOL_FRAMING=UNKNOWN", - "SEQUENCE=UNKNOWN", - "COMPRESSION=UNKNOWN", - "ENCRYPTION=UNKNOWN", - "FINAL_BINARY_EGRESS=UNKNOWN", - ] - ) + "\n", - encoding="utf-8", - ) - - evidence_lines = [ - "# Exact typed vtable slot inventory", - json.dumps(result["slots"], indent=2, sort_keys=True), + args.output_text.write_text("\n".join([ + "P2_DOWNSTREAM_RESULT=TARGETED_PROCESSOR_FLOW_CAPTURED", + f"CLIENT_SHA256={digest}", + f"CLIENT_SIZE={EXPECTED_SIZE}", + "TPROTOCOLWRITER_SLOT_BOUNDARY=PROVEN_SLOT4_ZERO", + "CONNECTION_INVOKER_EDGE=PROVEN", + f"EXACT_PROCESSOR_GRAPH_SIGNAL={'true' if exact_processor_graph_signal else 'false'}", + f"CONCRETE_BYTE_TYPE_SIGNAL={'true' if concrete_byte_type_signal else 'false'}", + f"FIRST_DOWNSTREAM_CONSUMER={first_downstream}", + "PROTOCOL_STAGE_ORDER=UNKNOWN", + "PROTOCOL_FRAMING=UNKNOWN", + "SEQUENCE=UNKNOWN", + "COMPRESSION=UNKNOWN", + "ENCRYPTION=UNKNOWN", + "FINAL_BINARY_EGRESS=UNKNOWN", + ]) + "\n", encoding="utf-8") + + lines = [ + "# Corrected TProtocolWriter vtable boundary", + json.dumps(writer_slots, indent=2), "", - "# Candidate windows", + "# Exact connection edge 0x19716a3", + *connection_lines, ] - for c in candidates: - evidence_lines.append(f"## {c['slot']} @ {c['va']}") - evidence_lines.extend(str(x) for x in c["relevant"]) - evidence_lines.extend(["", "# Persistent provenance control", *relevant_lines(setup)]) - args.evidence.write_text("\n".join(evidence_lines) + "\n", encoding="utf-8") + for name, body in bodies.items(): + lines.extend(["", f"# {name} {body['start']}..{body['stop']}", *body["relevant"]]) + args.evidence.write_text("\n".join(lines) + "\n", encoding="utf-8") print("P2_DOWNSTREAM_COMPLETE=true") - print(f"P2_DOWNSTREAM_RESULT={result['semantic_result']}") - print(f"P2_DOWNSTREAM_TYPED_CANDIDATE_COUNT={len(candidates)}") + print("P2_DOWNSTREAM_RESULT=TARGETED_PROCESSOR_FLOW_CAPTURED") + print("P2_DOWNSTREAM_TPROTOCOLWRITER_SLOT_BOUNDARY=PROVEN_SLOT4_ZERO") + print("P2_DOWNSTREAM_CONNECTION_INVOKER_EDGE=PROVEN") + print(f"P2_DOWNSTREAM_FIRST_DOWNSTREAM_CONSUMER={first_downstream}") print("P2_DOWNSTREAM_PROTOCOL_STAGE_ORDER=UNKNOWN") print("P2_DOWNSTREAM_FINAL_BINARY_EGRESS=UNKNOWN") return 0 diff --git a/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml b/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml index 09e173358e..5e5598743a 100644 --- a/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml +++ b/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml @@ -14,7 +14,7 @@ permissions: contents: read jobs: - typed-downstream-probe: + targeted-downstream-probe: runs-on: [otclient, synology] timeout-minutes: 8 env: @@ -75,7 +75,7 @@ jobs: echo "TRACK_A_P2_DOWNSTREAM_EXACT_HEAD=$GITHUB_SHA" echo "TRACK_A_P2_DOWNSTREAM_EXACT_CLIENT_VERIFIED=true" - - name: Inventory typed downstream candidates + - name: Capture exact processor flow and corrected writer boundary shell: bash run: | set -Eeuo pipefail @@ -89,14 +89,23 @@ jobs: --evidence "$out/evidence.txt" \ | tee "$out/validation.log" grep -Fxq 'P2_DOWNSTREAM_COMPLETE=true' "$out/validation.log" - grep -Eq '^P2_DOWNSTREAM_RESULT=(TYPED_DOWNSTREAM_CANDIDATES_INVENTORIED|BOUNDED_TYPED_INVENTORY_NO_CONSUMER_PROVEN)$' "$out/validation.log" + grep -Fxq 'P2_DOWNSTREAM_RESULT=TARGETED_PROCESSOR_FLOW_CAPTURED' "$out/validation.log" + grep -Fxq 'P2_DOWNSTREAM_TPROTOCOLWRITER_SLOT_BOUNDARY=PROVEN_SLOT4_ZERO' "$out/validation.log" + grep -Fxq 'P2_DOWNSTREAM_CONNECTION_INVOKER_EDGE=PROVEN' "$out/validation.log" grep -Fxq 'P2_DOWNSTREAM_PROTOCOL_STAGE_ORDER=UNKNOWN' "$out/validation.log" grep -Fxq 'P2_DOWNSTREAM_FINAL_BINARY_EGRESS=UNKNOWN' "$out/validation.log" python3 - "$out/result.json" <<'PY' - import json, sys + import json,sys d=json.load(open(sys.argv[1],encoding='utf-8')) + assert d['schema_version']==2 assert d['exact_client']['sha256']=='e6c244bd39fe2e0632f6f000efd3147164696efa8e901718668e0442325ff7fe' assert d['exact_client']['size']==51965216 + assert d['false_positive_correction']['writer_stops_at_slot4_zero'] + assert d['false_positive_correction']['0xd114c0_is_not_tprotocolwriter_slot8'] + assert d['connection']['site']=='0x19716a3' + assert d['connection']['invoker']=='0x7dd630' + assert d['connection']['connect_impl']=='0x4dd800' + assert d['classification']['first_downstream_consumer'] in ('CANDIDATE','UNKNOWN') assert d['classification']['protocol_stage_order']=='UNKNOWN' assert d['classification']['framing']=='UNKNOWN' assert d['classification']['final_binary_egress']=='UNKNOWN' @@ -105,7 +114,7 @@ jobs: assert not d['negative_controls']['vtable_adjacency_used_as_temporal_proof'] assert not d['negative_controls']['historical_final_socket_run_used_as_proof'] PY - echo TRACK_A_P2_DOWNSTREAM_VALIDATED=true + echo TRACK_A_P2_DOWNSTREAM_TARGETED_VALIDATED=true - name: Upload sanitized static evidence only uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 From 3138b94ebc477ee5075b458c16de72f620f49c67 Mon Sep 17 00:00:00 2001 From: blakinio <75369544+blakinio@users.noreply.github.com> Date: Sat, 15 Aug 2026 21:42:56 +0200 Subject: [PATCH 05/12] research(track-a): preserve exact processor argument flow --- ...al-client-re-p2-buffer-downstream-consumer.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml b/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml index 5e5598743a..2edf592fc4 100644 --- a/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml +++ b/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml @@ -114,6 +114,21 @@ jobs: assert not d['negative_controls']['vtable_adjacency_used_as_temporal_proof'] assert not d['negative_controls']['historical_final_socket_run_used_as_proof'] PY + + # Preserve exact, narrow argument/return flow windows. These are evidence only; + # no semantic assertion is made from their mere presence. + { + echo + echo '# Exact invoker argument flow 0x7dd630..0x7dd720' + "$TRACK_A_P2_OBJDUMP" -d -Mintel --no-show-raw-insn --start-address=0x7dd630 --stop-address=0x7dd720 "$TRACK_A_P2_CLIENT" + echo + echo '# Exact TProtocolClientMessageProcessor entry 0xc2df80..0xc2e080' + "$TRACK_A_P2_OBJDUMP" -d -Mintel --no-show-raw-insn --start-address=0xc2df80 --stop-address=0xc2e080 "$TRACK_A_P2_CLIENT" + echo + echo '# Exact RawDataProcessor entry 0xb47130..0xb47320' + "$TRACK_A_P2_OBJDUMP" -d -Mintel --no-show-raw-insn --start-address=0xb47130 --stop-address=0xb47320 "$TRACK_A_P2_CLIENT" + } >> "$out/evidence.txt" + echo TRACK_A_P2_DOWNSTREAM_EXACT_ARGUMENT_FLOW_PRESERVED=true echo TRACK_A_P2_DOWNSTREAM_TARGETED_VALIDATED=true - name: Upload sanitized static evidence only From 9b80ba494b72b4df4de3264d514c1a284086fe40 Mon Sep 17 00:00:00 2001 From: blakinio <75369544+blakinio@users.noreply.github.com> Date: Sat, 15 Aug 2026 21:48:39 +0200 Subject: [PATCH 06/12] research(track-a): prove post-serialization processor chain --- ...client-re-p2-buffer-downstream-consumer.py | 352 ++++++++++-------- ...lient-re-p2-buffer-downstream-consumer.yml | 68 ++-- 2 files changed, 219 insertions(+), 201 deletions(-) diff --git a/.github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py b/.github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py index 4709bd8d50..19e41974d6 100644 --- a/.github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py +++ b/.github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py @@ -12,25 +12,24 @@ EXPECTED_SHA256 = "e6c244bd39fe2e0632f6f000efd3147164696efa8e901718668e0442325ff7fe" EXPECTED_SIZE = 51965216 + +# Coordinator-promoted #308 exact type / retained-writer fence. WRITER_AP = 0x2F69DD0 WRITER_RTTI = 0x3080728 IODEVICE_WRITER_AP = 0x2F69D48 IODEVICE_WRITER_RTTI = 0x3080718 INTERMEDIATE_AP = 0x2F69E30 INTERMEDIATE_RTTI = 0x3080748 -INTERMEDIATE_PREFIX = (0x7DE7F0, 0x7DFD60, 0xC10960, 0xC20290, 0xC20C70) -CONNECTION_SITE = 0x19716A3 -INVOKER_LEA = 0x1971670 -SIGNAL_META_LEA = 0x1971677 -OUTER_CAPTURE_STORE = 0x197168D -INVOKER = 0x7DD630 -SIGNAL_META = 0x3085B60 -CONNECT_IMPL = 0x4DD800 -CLIENT_MESSAGE_PROCESSOR = 0xC2DF80 -RAW_DATA_PROCESSOR = 0xB47130 -DUAL_ENTRY_78 = 0xB56970 -DUAL_ENTRY_80 = 0xB56D60 + +# Exact setup / processor graph on the fenced client. +CLIENT_PROCESSOR_AP = 0x2F6A208 +RAW_PROCESSOR_AP = 0x2F6A230 +CLIENT_PROCESSOR_ENTRY = 0xC2DF80 +RAW_PROCESSOR_ENTRY = 0xB47130 +CONNECTION_INVOKER = 0x7DD630 DUAL_PRECONDITION = 0xB40370 +DUAL_ENTRY_80 = 0xB56D60 +DUAL_ENTRY_78 = 0xB56970 def require(value: bool, marker: str) -> None: @@ -79,16 +78,6 @@ def executable(self, va: int) -> bool: return bool(flags & 1) return False - def rip_target(self, site: int, length: int = 7, disp_offset: int = 3) -> int: - raw = self.read(site, length) - disp = struct.unpack_from(" int: - raw = self.read(site, 5) - require(raw[0] == 0xE8, f"direct_call_opcode_{site:x}") - return (site + 5 + struct.unpack_from(" str: h = hashlib.sha256() @@ -100,7 +89,10 @@ def sha256(path: Path) -> str: def disassemble(objdump: Path, client: Path, lo: int, hi: int) -> str: p = subprocess.run( - [str(objdump), "-d", "-Mintel", "--no-show-raw-insn", f"--start-address=0x{lo:x}", f"--stop-address=0x{hi:x}", str(client)], + [ + str(objdump), "-d", "-Mintel", "--no-show-raw-insn", + f"--start-address=0x{lo:x}", f"--stop-address=0x{hi:x}", str(client), + ], check=True, text=True, stdout=subprocess.PIPE, @@ -110,46 +102,18 @@ def disassemble(objdump: Path, client: Path, lo: int, hi: int) -> str: return p.stdout -def insn_lines(text: str) -> list[str]: - return [line.strip() for line in text.splitlines() if re.match(r"^[0-9a-f]+:", line.strip(), re.I)] - - -def bounded_relevant(text: str) -> list[str]: - needles = ( - "call", "+0xa00", "+0xa08", "+0xa10", "+0xa18", "+0xc18", "+0xc20", - "QBuffer", "QDataStream", "QIODevice", "QByteArray", "write", "send", "compress", "encrypt", - "+0x18", "+0x20", "+0x28", "+0x30", - ) - return [line for line in insn_lines(text) if any(n.lower() in line.lower() for n in needles)][:200] - - -def actual_vtable_slots(elf: Elf64, ap: int, maximum: int) -> list[dict[str, object]]: - out: list[dict[str, object]] = [] - for index in range(maximum): - value = elf.u64(ap + index * 8) - is_exec = elf.executable(value) - out.append({"index": index, "va": f"0x{value:x}", "executable": is_exec}) - if not is_exec: - break +def instruction_map(text: str) -> dict[int, str]: + out: dict[int, str] = {} + for line in text.splitlines(): + m = re.match(r"^\s*([0-9a-f]+):\s*(.*)$", line, re.I) + if m: + out[int(m.group(1), 16)] = m.group(2).strip() return out -def body_flags(text: str) -> dict[str, object]: - lowered = text.lower() - return { - "outer_a00": bool(re.search(r"\+0xa00\]", lowered)), - "outer_a10": bool(re.search(r"\+0xa10\]", lowered)), - "outer_c18": bool(re.search(r"\+0xc18\]", lowered)), - "qbuffer": "qbuffer" in lowered, - "qdatastream": "qdatastream" in lowered, - "qiodevice": "qiodevice" in lowered, - "qbytearray": "qbytearray" in lowered, - "write_text": "write" in lowered, - "send_text": "send" in lowered, - "compress_text": "compress" in lowered, - "encrypt_text": "encrypt" in lowered, - "calls": [line for line in insn_lines(text) if "call" in line.lower()][:80], - } +def at(insns: dict[int, str], addr: int, *needles: str) -> bool: + line = insns.get(addr, "").lower() + return bool(line) and all(n.lower() in line for n in needles) def main() -> int: @@ -168,6 +132,7 @@ def main() -> int: require(digest == EXPECTED_SHA256, "exact_client_sha256") elf = Elf64(args.client) + # Revalidate promoted type anchors and correct the false-positive writer vtable bleed from run #1. for ap, rtti, name in ( (WRITER_AP, WRITER_RTTI, "writer"), (IODEVICE_WRITER_AP, IODEVICE_WRITER_RTTI, "iodevice_writer"), @@ -175,87 +140,139 @@ def main() -> int: ): require(elf.u64(ap - 16) == 0, f"{name}_offset_to_top_zero") require(elf.u64(ap - 8) == rtti, f"{name}_rtti") - - writer_slots = actual_vtable_slots(elf, WRITER_AP, 12) - require([int(str(x["va"]), 16) for x in writer_slots[:4]] == [0x7E3C10, 0x7E3CA0, 0xC262B0, 0xC21EC0], "writer_real_slots_0_3") - require(len(writer_slots) == 5 and writer_slots[4]["va"] == "0x0" and not writer_slots[4]["executable"], "writer_vtable_stops_at_slot4_zero") + require([elf.u64(WRITER_AP + i * 8) for i in range(5)] == [0x7E3C10, 0x7E3CA0, 0xC262B0, 0xC21EC0, 0], "writer_real_vtable_boundary") require(elf.u64(WRITER_AP + 5 * 8) == 0x3080738, "writer_neighbor_rtti_not_slot") - require(elf.u64(WRITER_AP + 8 * 8) == 0xD114C0, "historical_false_positive_address_present_but_not_writer_slot") - - for i, expected in enumerate(INTERMEDIATE_PREFIX): - require(elf.u64(INTERMEDIATE_AP + i * 8) == expected, f"intermediate_slot_{i}") - require(elf.executable(expected), f"intermediate_slot_{i}_exec") - - require(elf.rip_target(INVOKER_LEA) == INVOKER, "connection_invoker_target") - require(elf.rip_target(SIGNAL_META_LEA) == SIGNAL_META, "connection_signal_meta_target") - require(elf.call_target(CONNECTION_SITE) == CONNECT_IMPL, "connection_connectimpl_target") - - connection = disassemble(args.objdump, args.client, 0x1971635, 0x19716A8) - connection_lines = insn_lines(connection) - require(any(line.lower().startswith("197168d:") and "+0x10" in line.lower() for line in connection_lines), "connection_captures_outer_at_slotobject_plus10") - - targets = { - "invoker_0x7dd630": (INVOKER, 0x7DD9A0), - "client_message_processor_0xc2df80": (CLIENT_MESSAGE_PROCESSOR, 0xC2E680), - "raw_data_processor_0xb47130": (RAW_DATA_PROCESSOR, 0xB47880), - "dual_entry_0xb56970": (DUAL_ENTRY_78, 0xB56D60), - "dual_entry_0xb56d60": (DUAL_ENTRY_80, 0xB57280), - "dual_precondition_0xb40370": (DUAL_PRECONDITION, 0xB40880), - } - bodies: dict[str, dict[str, object]] = {} - for name, (lo, hi) in targets.items(): - require(elf.executable(lo), f"target_exec_{lo:x}") - text = disassemble(args.objdump, args.client, lo, hi) - bodies[name] = { - "start": f"0x{lo:x}", - "stop": f"0x{hi:x}", - "flags": body_flags(text), - "relevant": bounded_relevant(text), - "instruction_count": len(insn_lines(text)), - } - - inv_flags = bodies["invoker_0x7dd630"]["flags"] - proc_flags = bodies["client_message_processor_0xc2df80"]["flags"] - raw_flags = bodies["raw_data_processor_0xb47130"]["flags"] - exact_processor_graph_signal = bool(inv_flags["outer_a00"] or inv_flags["outer_a10"] or proc_flags["outer_a10"]) - concrete_byte_type_signal = any( - bool(flags[key]) - for flags in (inv_flags, proc_flags, raw_flags) - for key in ("qbuffer", "qbytearray", "qiodevice") - ) + require(elf.u64(WRITER_AP + 8 * 8) == 0xD114C0, "historical_bleed_address_present_not_writer_slot") + + # Exact processor vtable identities: this turns the invoker's indirect calls into typed edges. + require(elf.u64(CLIENT_PROCESSOR_AP + 0x10) == CLIENT_PROCESSOR_ENTRY, "client_processor_virtual_plus10") + require(elf.u64(RAW_PROCESSOR_AP + 0x10) == RAW_PROCESSOR_ENTRY, "raw_processor_virtual_plus10") + + setup = disassemble(args.objdump, args.client, 0x1970C80, 0x19710B5) + setup_i = instruction_map(setup) + + # Persistent QBuffer object provenance from #308: actual QBuffer object is r15 = allocation+0x10. + require(at(setup_i, 0x1970C96, "lea", "r15,[rax+0x10]"), "persistent_qbuffer_object_pointer") + require(at(setup_i, 0x1970CAD, "call", "QBufferC2"), "persistent_qbuffer_constructor") + require(at(setup_i, 0x1970CC6, "call", "QBuffer4open"), "persistent_qbuffer_open") + require(at(setup_i, 0x1970CA6, "[rbp-0x218]", "r15"), "persistent_qbuffer_saved_rbpm218") + + # TProtocolClientMessageProcessor actual object begins at allocation+0x10. Its this+0x18 + # receives the SAME saved persistent QBuffer object from rbp-0x218. + require(at(setup_i, 0x197104F, "lea", "rdx,[rax+0x10]"), "client_processor_actual_object_pointer") + require(at(setup_i, 0x1971056, "0x2f6a208"), "client_processor_ap_loaded") + require(at(setup_i, 0x197105D, "[rax+0x10]", "rcx"), "client_processor_vptr_store") + require(at(setup_i, 0x1971084, "rsi", "[rbp-0x218]"), "client_processor_reloads_same_qbuffer") + require(at(setup_i, 0x197108F, "[rax+0x28]", "rsi"), "client_processor_this_plus18_qbuffer_store") + require(at(setup_i, 0x19710A7, "[rcx+0xa00]", "rdx"), "outer_retains_client_processor") + + invoker = disassemble(args.objdump, args.client, 0x7DD630, 0x7DD720) + inv_i = instruction_map(invoker) + client_proc = disassemble(args.objdump, args.client, 0xC2DF80, 0xC2E080) + cp_i = instruction_map(client_proc) + raw_proc = disassemble(args.objdump, args.client, 0xB47130, 0xB47320) + rp_i = instruction_map(raw_proc) + + # Exact invoker pipeline. rbp=rsp is the same stack message object across all downstream calls. + require(at(inv_i, 0x7DD66C, "mov", "rbp,rsp"), "invoker_message_object_is_rsp") + require(at(inv_i, 0x7DD66F, "rdx,r12"), "invoker_signal_argument_to_client_processor") + require(at(inv_i, 0x7DD672, "rdi,rbp"), "invoker_client_processor_sret_message") + require(at(inv_i, 0x7DD675, "rsi", "[rax+0xa00]"), "invoker_client_processor_this") + require(at(inv_i, 0x7DD67F, "call", "[rax+0x10]"), "invoker_calls_client_processor_plus10") + require(at(inv_i, 0x7DD686, "rsi,rbp"), "invoker_same_message_to_raw_processor") + require(at(inv_i, 0x7DD689, "rdi", "[rax+0xa10]"), "invoker_raw_processor_this") + require(at(inv_i, 0x7DD693, "call", "[rax+0x10]"), "invoker_calls_raw_processor_plus10") + require(at(inv_i, 0x7DD69A, "rsi,rbp"), "invoker_same_message_to_dual_plus80") + require(at(inv_i, 0x7DD69D, "rdi", "[rax+0xc18]"), "invoker_dual_this_plus80") + require(at(inv_i, 0x7DD6A7, "call", "[rax+0x80]"), "invoker_calls_dual_plus80") + require(at(inv_i, 0x7DD6B1, "rsi,rbp"), "invoker_same_message_to_dual_plus78") + require(at(inv_i, 0x7DD6B4, "rdi", "[rax+0xc18]"), "invoker_dual_this_plus78") + require(at(inv_i, 0x7DD6BE, "call", "[rax+0x78]"), "invoker_calls_dual_plus78") + + # Client processor exact ABI/data flow: (sret message, this, signal arg). + require(at(cp_i, 0xC2DF86, "r12,rdx"), "client_processor_captures_signal_arg") + require(at(cp_i, 0xC2DF8A, "rbp,rsi"), "client_processor_captures_this") + require(at(cp_i, 0xC2DF8E, "rbx,rdi"), "client_processor_captures_sret_message") + require(at(cp_i, 0xC2DF95, "rdi", "[rsi+0x8]"), "client_processor_retained_intermediate_this") + require(at(cp_i, 0xC2DF99, "rsi,rdx"), "client_processor_signal_to_intermediate") + require(at(cp_i, 0xC2DFA2, "call", "[rax+0x10]"), "client_processor_invokes_retained_intermediate") + + # This is the first exact consumer of the promoted persistent QBuffer: same this+0x18 pointer + # established above is passed to QIODevice::readAll(). + require(at(cp_i, 0xC2DFA5, "rdi", "[rbp+0x18]"), "client_processor_reads_persistent_qbuffer_member") + require(at(cp_i, 0xC2DFD5, "call", "QIODevice7readAll"), "persistent_qbuffer_qiodevice_readall") + require(at(cp_i, 0xC2DFEB, "lea", "rbp,[rbx+0x8]"), "client_processor_output_qbytearray_field") + require(at(cp_i, 0xC2E012, "call", "QByteArrayaSERKS"), "client_processor_assigns_qbytearray_output") + require(at(cp_i, 0xC2E040, "rax,rbx"), "client_processor_returns_message_object") + + # RawDataProcessor consumes the SAME message object and transforms its QByteArray at +0x8 in place. + require(at(rp_i, 0xB47132, "lea", "rax,[rsi+0x8]"), "raw_processor_message_qbytearray_pointer") + require(at(rp_i, 0xB47151, "[rsp+0x8]", "rax"), "raw_processor_saves_input_qbytearray_pointer") + require(at(rp_i, 0xB47189, "call", "QByteArray6insert"), "raw_processor_qbytearray_insert") + require(at(rp_i, 0xB47206, "call", "QByteArray6append"), "raw_processor_qbytearray_append") + require(at(rp_i, 0xB47287, "[r12+0x28]"), "raw_processor_reads_same_message_state") + require(at(rp_i, 0xB472F8, "rdi", "[rsp+0x8]"), "raw_processor_reloads_input_qbytearray_pointer") + require(at(rp_i, 0xB47300, "call", "QByteArrayaSERKS"), "raw_processor_assigns_transformed_qbytearray_in_place") - # A targeted capture is not itself a downstream proof. Only an explicit byte/container signal on - # the exact processor path earns CANDIDATE; otherwise the byte consumer remains UNKNOWN. - first_downstream = "CANDIDATE" if exact_processor_graph_signal and concrete_byte_type_signal else "UNKNOWN" result = { - "schema_version": 2, - "exact_client": {"sha256": digest, "size": EXPECTED_SIZE, "version_mapping": "15.32.df7b29", "platform": "official_native_linux_only"}, - "writer_vtable": writer_slots, + "schema_version": 3, + "exact_client": { + "sha256": digest, + "size": EXPECTED_SIZE, + "version_mapping": "15.32.df7b29", + "platform": "official_native_linux_only", + }, "false_positive_correction": { - "writer_stops_at_slot4_zero": True, - "writer_ap_plus_0x28_is_neighbor_rtti_0x3080738": True, + "tprotocolwriter_real_slots": ["0x7e3c10", "0x7e3ca0", "0xc262b0", "0xc21ec0"], + "tprotocolwriter_slot4": "0x0", "0xd114c0_is_not_tprotocolwriter_slot8": True, - "fixed_0x280_window_candidates_from_run_31904191629_are_not_semantic_proof": True, + "run_31904191629_fixed_window_candidates_rejected": True, }, - "connection": { - "site": "0x19716a3", - "invoker": "0x7dd630", - "signal_meta": "0x3085b60", - "connect_impl": "0x4dd800", - "outer_capture_store": "0x197168d", - "relevant": bounded_relevant(connection), + "provenance": { + "persistent_qbuffer_saved_at_setup_scratch": "rbp-0x218", + "persistent_qbuffer_stored_in_client_processor_member": "this+0x18", + "client_processor_outer_member": "outer+0xa00", + "raw_processor_outer_member": "outer+0xa10", + "dualconnection_outer_member": "outer+0xc18", }, - "targets": bodies, + "stage_order": [ + { + "stage": "TProtocolClientMessageProcessor virtual +0x10", + "entry": "0xc2df80", + "input": "signal argument", + "effect": "invoke retained intermediate, then QIODevice::readAll on exact persistent QBuffer at this+0x18, assign bytes to output QByteArray at message+0x8", + }, + { + "stage": "TGameserverNetworkPacketRawDataProcessor virtual +0x10", + "entry": "0xb47130", + "input": "same message object", + "effect": "QByteArray insert/append/reallocation path and in-place assignment back to message+0x8", + }, + { + "stage": "TGameserverDualConnection virtual +0x80", + "entry": "0xb56d60", + "input": "same post-raw message object", + "effect": "consumer call proven; transport semantics not classified here", + }, + { + "stage": "TGameserverDualConnection virtual +0x78", + "entry": "0xb56970", + "input": "same post-raw message object", + "effect": "consumer call proven; transport semantics not classified here", + }, + ], "classification": { - "exact_processor_graph_signal": exact_processor_graph_signal, - "concrete_byte_type_signal": concrete_byte_type_signal, - "first_downstream_consumer": first_downstream, - "protocol_stage_order": "UNKNOWN", + "persistent_qbuffer_direct_readall": "PROVEN", + "first_downstream_consumer": "PROVEN:TProtocolClientMessageProcessor+0x10@0xc2df80", + "first_downstream_transform": "PROVEN:TGameserverNetworkPacketRawDataProcessor+0x10@0xb47130", + "same_message_handoff_to_dualconnection": "PROVEN", + "protocol_stage_order": "PROVEN_PARTIAL", "framing": "UNKNOWN", "sequence": "UNKNOWN", "compression": "UNKNOWN", "encryption": "UNKNOWN", "final_binary_egress": "UNKNOWN", + "causal_local_harness": "UNKNOWN", }, "negative_controls": { "generic_qiodevice_census_used_as_proof": False, @@ -263,46 +280,63 @@ def main() -> int: "vtable_adjacency_used_as_temporal_proof": False, "historical_final_socket_run_used_as_proof": False, "direct_dualconnection_writer_ownership_assumed": False, + "dual_plus80_or_plus78_labeled_final_egress": False, + "raw_byte_transform_labeled_framing_without_semantics": False, }, - "semantic_result": "TARGETED_PROCESSOR_FLOW_CAPTURED", + "semantic_result": "POST_SERIALIZATION_PROCESSOR_CHAIN_PROVEN", } args.output_json.parent.mkdir(parents=True, exist_ok=True) args.output_json.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="utf-8") - args.output_text.write_text("\n".join([ - "P2_DOWNSTREAM_RESULT=TARGETED_PROCESSOR_FLOW_CAPTURED", - f"CLIENT_SHA256={digest}", - f"CLIENT_SIZE={EXPECTED_SIZE}", - "TPROTOCOLWRITER_SLOT_BOUNDARY=PROVEN_SLOT4_ZERO", - "CONNECTION_INVOKER_EDGE=PROVEN", - f"EXACT_PROCESSOR_GRAPH_SIGNAL={'true' if exact_processor_graph_signal else 'false'}", - f"CONCRETE_BYTE_TYPE_SIGNAL={'true' if concrete_byte_type_signal else 'false'}", - f"FIRST_DOWNSTREAM_CONSUMER={first_downstream}", - "PROTOCOL_STAGE_ORDER=UNKNOWN", - "PROTOCOL_FRAMING=UNKNOWN", - "SEQUENCE=UNKNOWN", - "COMPRESSION=UNKNOWN", - "ENCRYPTION=UNKNOWN", - "FINAL_BINARY_EGRESS=UNKNOWN", - ]) + "\n", encoding="utf-8") - - lines = [ - "# Corrected TProtocolWriter vtable boundary", - json.dumps(writer_slots, indent=2), + args.output_text.write_text( + "\n".join( + [ + "P2_DOWNSTREAM_RESULT=POST_SERIALIZATION_PROCESSOR_CHAIN_PROVEN", + f"CLIENT_SHA256={digest}", + f"CLIENT_SIZE={EXPECTED_SIZE}", + "TPROTOCOLWRITER_SLOT_BOUNDARY=PROVEN_SLOT4_ZERO", + "PERSISTENT_QBUFFER_DIRECT_READALL=PROVEN", + "FIRST_DOWNSTREAM_CONSUMER=PROVEN:TProtocolClientMessageProcessor+0x10@0xc2df80", + "FIRST_DOWNSTREAM_TRANSFORM=PROVEN:TGameserverNetworkPacketRawDataProcessor+0x10@0xb47130", + "SAME_MESSAGE_HANDOFF_TO_DUALCONNECTION=PROVEN", + "PROTOCOL_STAGE_ORDER=PROVEN_PARTIAL", + "PROTOCOL_FRAMING=UNKNOWN", + "SEQUENCE=UNKNOWN", + "COMPRESSION=UNKNOWN", + "ENCRYPTION=UNKNOWN", + "FINAL_BINARY_EGRESS=UNKNOWN", + "CAUSAL_LOCAL_HARNESS=UNKNOWN", + ] + ) + "\n", + encoding="utf-8", + ) + + evidence = [ + "# Persistent QBuffer -> ClientMessageProcessor setup", + *[f"{a:x}: {setup_i[a]}" for a in sorted(setup_i) if a in { + 0x1970C96,0x1970CA6,0x1970CAD,0x1970CC6,0x197104F,0x1971056,0x197105D, + 0x1971084,0x197108F,0x19710A7, + }], + "", + "# Exact invoker stage order", + *[f"{a:x}: {inv_i[a]}" for a in sorted(inv_i)], + "", + "# TProtocolClientMessageProcessor exact downstream read", + *[f"{a:x}: {cp_i[a]}" for a in sorted(cp_i)], "", - "# Exact connection edge 0x19716a3", - *connection_lines, + "# TGameserverNetworkPacketRawDataProcessor exact in-place transform", + *[f"{a:x}: {rp_i[a]}" for a in sorted(rp_i)], ] - for name, body in bodies.items(): - lines.extend(["", f"# {name} {body['start']}..{body['stop']}", *body["relevant"]]) - args.evidence.write_text("\n".join(lines) + "\n", encoding="utf-8") + args.evidence.write_text("\n".join(evidence) + "\n", encoding="utf-8") print("P2_DOWNSTREAM_COMPLETE=true") - print("P2_DOWNSTREAM_RESULT=TARGETED_PROCESSOR_FLOW_CAPTURED") - print("P2_DOWNSTREAM_TPROTOCOLWRITER_SLOT_BOUNDARY=PROVEN_SLOT4_ZERO") - print("P2_DOWNSTREAM_CONNECTION_INVOKER_EDGE=PROVEN") - print(f"P2_DOWNSTREAM_FIRST_DOWNSTREAM_CONSUMER={first_downstream}") - print("P2_DOWNSTREAM_PROTOCOL_STAGE_ORDER=UNKNOWN") + print("P2_DOWNSTREAM_RESULT=POST_SERIALIZATION_PROCESSOR_CHAIN_PROVEN") + print("P2_DOWNSTREAM_PERSISTENT_QBUFFER_DIRECT_READALL=PROVEN") + print("P2_DOWNSTREAM_FIRST_CONSUMER=PROVEN") + print("P2_DOWNSTREAM_FIRST_TRANSFORM=PROVEN") + print("P2_DOWNSTREAM_SAME_MESSAGE_TO_DUALCONNECTION=PROVEN") + print("P2_DOWNSTREAM_PROTOCOL_STAGE_ORDER=PROVEN_PARTIAL") + print("P2_DOWNSTREAM_PROTOCOL_FRAMING=UNKNOWN") print("P2_DOWNSTREAM_FINAL_BINARY_EGRESS=UNKNOWN") return 0 diff --git a/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml b/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml index 2edf592fc4..e4a57f69eb 100644 --- a/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml +++ b/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml @@ -14,7 +14,7 @@ permissions: contents: read jobs: - targeted-downstream-probe: + hardened-post-serialization-chain: runs-on: [otclient, synology] timeout-minutes: 8 env: @@ -45,9 +45,7 @@ jobs: size="$(stat -c '%s' "$probe")" sha="$(sha256sum "$probe" | awk '{print $1}')" if [[ "$size" == "$EXPECTED_CLIENT_SIZE" && "$sha" == "$EXPECTED_CLIENT_SHA256" ]]; then - client="$probe" - state="$candidate" - break + client="$probe"; state="$candidate"; break fi done [[ -n "$client" && -n "$state" ]] @@ -66,16 +64,14 @@ jobs: fi [[ -n "$objdump" && -x "$objdump" ]] "$objdump" --version >/dev/null - { echo "TRACK_A_P2_CLIENT=$client" echo "TRACK_A_P2_OBJDUMP=$objdump" - echo "TRACK_A_P2_STATE=$state" } >> "$GITHUB_ENV" echo "TRACK_A_P2_DOWNSTREAM_EXACT_HEAD=$GITHUB_SHA" - echo "TRACK_A_P2_DOWNSTREAM_EXACT_CLIENT_VERIFIED=true" + echo TRACK_A_P2_DOWNSTREAM_EXACT_CLIENT_VERIFIED=true - - name: Capture exact processor flow and corrected writer boundary + - name: Prove persistent QBuffer downstream processor chain shell: bash run: | set -Eeuo pipefail @@ -88,48 +84,36 @@ jobs: --output-text "$out/result.txt" \ --evidence "$out/evidence.txt" \ | tee "$out/validation.log" + grep -Fxq 'P2_DOWNSTREAM_COMPLETE=true' "$out/validation.log" - grep -Fxq 'P2_DOWNSTREAM_RESULT=TARGETED_PROCESSOR_FLOW_CAPTURED' "$out/validation.log" - grep -Fxq 'P2_DOWNSTREAM_TPROTOCOLWRITER_SLOT_BOUNDARY=PROVEN_SLOT4_ZERO' "$out/validation.log" - grep -Fxq 'P2_DOWNSTREAM_CONNECTION_INVOKER_EDGE=PROVEN' "$out/validation.log" - grep -Fxq 'P2_DOWNSTREAM_PROTOCOL_STAGE_ORDER=UNKNOWN' "$out/validation.log" + grep -Fxq 'P2_DOWNSTREAM_RESULT=POST_SERIALIZATION_PROCESSOR_CHAIN_PROVEN' "$out/validation.log" + grep -Fxq 'P2_DOWNSTREAM_PERSISTENT_QBUFFER_DIRECT_READALL=PROVEN' "$out/validation.log" + grep -Fxq 'P2_DOWNSTREAM_FIRST_CONSUMER=PROVEN' "$out/validation.log" + grep -Fxq 'P2_DOWNSTREAM_FIRST_TRANSFORM=PROVEN' "$out/validation.log" + grep -Fxq 'P2_DOWNSTREAM_SAME_MESSAGE_TO_DUALCONNECTION=PROVEN' "$out/validation.log" + grep -Fxq 'P2_DOWNSTREAM_PROTOCOL_STAGE_ORDER=PROVEN_PARTIAL' "$out/validation.log" + grep -Fxq 'P2_DOWNSTREAM_PROTOCOL_FRAMING=UNKNOWN' "$out/validation.log" grep -Fxq 'P2_DOWNSTREAM_FINAL_BINARY_EGRESS=UNKNOWN' "$out/validation.log" + python3 - "$out/result.json" <<'PY' import json,sys d=json.load(open(sys.argv[1],encoding='utf-8')) - assert d['schema_version']==2 + assert d['schema_version']==3 assert d['exact_client']['sha256']=='e6c244bd39fe2e0632f6f000efd3147164696efa8e901718668e0442325ff7fe' assert d['exact_client']['size']==51965216 - assert d['false_positive_correction']['writer_stops_at_slot4_zero'] - assert d['false_positive_correction']['0xd114c0_is_not_tprotocolwriter_slot8'] - assert d['connection']['site']=='0x19716a3' - assert d['connection']['invoker']=='0x7dd630' - assert d['connection']['connect_impl']=='0x4dd800' - assert d['classification']['first_downstream_consumer'] in ('CANDIDATE','UNKNOWN') - assert d['classification']['protocol_stage_order']=='UNKNOWN' - assert d['classification']['framing']=='UNKNOWN' - assert d['classification']['final_binary_egress']=='UNKNOWN' - assert not d['negative_controls']['generic_qiodevice_census_used_as_proof'] - assert not d['negative_controls']['generic_qbuffer_census_used_as_proof'] - assert not d['negative_controls']['vtable_adjacency_used_as_temporal_proof'] - assert not d['negative_controls']['historical_final_socket_run_used_as_proof'] + assert d['semantic_result']=='POST_SERIALIZATION_PROCESSOR_CHAIN_PROVEN' + c=d['classification'] + assert c['persistent_qbuffer_direct_readall']=='PROVEN' + assert c['first_downstream_consumer'].startswith('PROVEN:') + assert c['first_downstream_transform'].startswith('PROVEN:') + assert c['same_message_handoff_to_dualconnection']=='PROVEN' + assert c['protocol_stage_order']=='PROVEN_PARTIAL' + for k in ('framing','sequence','compression','encryption','final_binary_egress','causal_local_harness'): + assert c[k]=='UNKNOWN' + n=d['negative_controls'] + assert not any(n.values()) PY - - # Preserve exact, narrow argument/return flow windows. These are evidence only; - # no semantic assertion is made from their mere presence. - { - echo - echo '# Exact invoker argument flow 0x7dd630..0x7dd720' - "$TRACK_A_P2_OBJDUMP" -d -Mintel --no-show-raw-insn --start-address=0x7dd630 --stop-address=0x7dd720 "$TRACK_A_P2_CLIENT" - echo - echo '# Exact TProtocolClientMessageProcessor entry 0xc2df80..0xc2e080' - "$TRACK_A_P2_OBJDUMP" -d -Mintel --no-show-raw-insn --start-address=0xc2df80 --stop-address=0xc2e080 "$TRACK_A_P2_CLIENT" - echo - echo '# Exact RawDataProcessor entry 0xb47130..0xb47320' - "$TRACK_A_P2_OBJDUMP" -d -Mintel --no-show-raw-insn --start-address=0xb47130 --stop-address=0xb47320 "$TRACK_A_P2_CLIENT" - } >> "$out/evidence.txt" - echo TRACK_A_P2_DOWNSTREAM_EXACT_ARGUMENT_FLOW_PRESERVED=true - echo TRACK_A_P2_DOWNSTREAM_TARGETED_VALIDATED=true + echo TRACK_A_P2_DOWNSTREAM_HARDENED_VALIDATED=true - name: Upload sanitized static evidence only uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 From 8e49f9d487f9e5253d837d2a11b7bbe163e66769 Mon Sep 17 00:00:00 2001 From: blakinio <75369544+blakinio@users.noreply.github.com> Date: Sun, 16 Aug 2026 13:19:23 +0200 Subject: [PATCH 07/12] docs(track-a): refresh P2 hosted no-runtime admission --- ...5-track-a-p2-buffer-downstream-consumer.md | 120 ++++++++++++++---- 1 file changed, 97 insertions(+), 23 deletions(-) diff --git a/docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md b/docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md index 1c1bbf915d..8e0c1640df 100644 --- a/docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md +++ b/docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md @@ -1,48 +1,86 @@ --- task_id: OTC-20260815-track-a-p2-buffer-downstream-consumer -status: active +status: investigating agent: ChatGPT -session_id: chatgpt-p2-downstream-researcher-20260815-2140 +session_id: chatgpt-p2-hosted-resume-20260816-1317 session_role: researcher -session_rotation_count: 1 +session_rotation_count: 2 project_lane: otclient lane: P2-NETWORK track_id: official-client-re -task_kind: runtime-research -phase: p2-buffer-downstream-consumer +task_kind: validation +phase: validate branch: research/OTC-20260815-track-a-p2-buffer-downstream-consumer base_branch: main -base_main: 8fca1c3eee453d0d4ef8a47e0f15c9dbae491b45 +base_main: 0d7b2607912552599ae501891491aab439cfde7b worktree: github-only://blakinio/otclient/refs/heads/research/OTC-20260815-track-a-p2-buffer-downstream-consumer worktree_mode: isolated_branch_checkout_equivalent risk: medium -related_pr: null +related_pr: 310 created: 2026-08-15T21:40:00+02:00 -updated: 2026-08-15T21:40:00+02:00 -lease_expires_at: 2026-08-15T22:25:00+02:00 +updated: 2026-08-16T13:17:33+02:00 +lease_expires_at: 2026-08-16T14:02:33+02:00 owned_paths: - docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md - docs/agents/evidence/OTC-20260815-track-a-p2-buffer-downstream-consumer/** - .github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml - .github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py +modules_touched: [] +reuses: + - coordinator-promoted PR #308 exact retained QBuffer/QDataStream boundary + - PR #310 run 31904696996 sanitized static artifact as historical discovery input only + - PR #310 run 31904967728 exact-client failure log as historical discovery input only depends_on: - - main@8fca1c3eee453d0d4ef8a47e0f15c9dbae491b45 - - coordinator PR #300 ACCEPT_WITH_EDITS promotion of closed-unmerged PR #308 as pinned evidence only + - main@0d7b2607912552599ae501891491aab439cfde7b + - coordinator promotion of closed-unmerged PR #308 as pinned evidence only blocks: [] policy_version: 2 prompting_standard_version: 2.1 prompt_contract_version: 1.0.0 +routing_contract: docs/agents/programs/OTCLIENT_TIBIA_RE_HYBRID_EXECUTION_ROUTING.md +track_a_runtime_agent_admission_version: 1 execution_mode: github-only -execution_reason: exact-build static ELF/disassembly discriminator from promoted retained QBuffer-backed QDataStream boundary +execution_reason: deterministic exact-build static ELF/disassembly validation on GitHub-hosted execution; no live client or retained Synology state is required or permitted +execution_class: github_hosted +runtime_access: none +persistent_session_role: consumer_of_runtime_evidence +physical_e2e_required: false +runtime_owner_task: NOT_APPLICABLE +runtime_namespace: NOT_APPLICABLE +canonical_registration: NOT_APPLICABLE +canonical_lease_generation: NOT_APPLICABLE +registration_lease_generation: NOT_APPLICABLE +gate_a: NOT_APPLICABLE +generation_rebind: NOT_APPLICABLE +gate_b: NOT_APPLICABLE +bootstrap: NOT_APPLICABLE +target_uniqueness: NOT_APPLICABLE +mutation_authorized: false +owner_funded_ai_api_authorized: false run_scope: single_task continuation_policy: continue_until_real_stop task_completion_policy: draft_pr_only -user_communication: terminal_only +user_communication: milestone_and_terminal implementation_authorized: true -invocation_started_at: 2026-08-15T21:40:00+02:00 -last_progress_at: 2026-08-15T21:40:00+02:00 +feature_scope: + type: infrastructure + user_facing: false + backend_required: false + frontend_required: false + integration_required: false + e2e_required: false + completion_claim: internal_research_only +context_pressure: medium +context_growth: stable +context_score: 8 +estimate_confidence: high +decomposition_decision: single +decomposition_reason: one bounded P2 post-serialization chain question with one owned validator/workflow and no runtime dependency +validation_level: focused +invocation_started_at: 2026-08-16T13:07:00+02:00 +last_progress_at: 2026-08-16T13:17:33+02:00 ci_checks_for_current_head: 0 -ci_check_generation: initial-downstream-inventory +ci_check_generation: hosted-redispatch terminal_ci_wait_started_at: null terminal_ci_checks_for_current_generation: 0 unchanged_state_checks: 0 @@ -50,8 +88,31 @@ identical_failure_retries: 0 repair_cycles_for_current_gate: 0 context_reconstruction_attempts: 1 stall_warnings: 0 -active_operation: recover the first exact downstream consumer/transform after the promoted persistent QBuffer-backed byte container using type-anchored static evidence -next_action: inventory exact TProtocolWriter and retained-intermediate callable slots plus bounded construction/use references, then isolate the first function that reads/exposes/transforms retained byte-container state; produce a semantic result without using generic census or historical final-socket evidence +heavy_validation_runs: 0 +runtime_nonclaims: + display_98_current_canonical_status: UNKNOWN + rfb_6082_current_backend_mapping: UNKNOWN + current_exact_client_pid: NOT_REGISTERED + current_exact_client_session: NOT_REGISTERED +historical_run_disposition: + run_31904696996: sanitized_static_evidence_only_do_not_treat_as_current_execution + run_31904967728: exact_client_static_failure_log_only_do_not_repeat_on_synology + synology_static_rerun: FORBIDDEN_BY_CURRENT_ROUTING +known_validator_defect: + marker: client_processor_ap_loaded + failing_run: 31904967728 + first_actionable_error: GNU_objdump_rip_comment_contains_2f6a208_without_required_0x_prefix + repair: accept the exact address token independent of the optional 0x presentation prefix +hosted_input_policy: + source: official Linux Tibia download endpoint only + use: ephemeral read-only archive extraction and exact-fence static analysis + execute_client: false + persist_client_bytes: false + upload_client_bytes: false + exact_fence_required: true + on_download_or_fence_mismatch: fail_closed_INPUT_BLOCKED_no_synology_fallback +active_operation: refresh the stale P2 Draft to current hosted/no-runtime routing, repair the proven validator formatting defect, and rerun the exact static discriminator without touching canonical or Synology runtime state +next_action: replace the stale Synology workflow with an ubuntu-latest fail-closed official-artifact static validator, repair client_processor_ap_loaded matching, run once, and classify the first downstream consumer/transform without promoting framing or egress beyond evidence --- # Objective @@ -69,6 +130,8 @@ TProtocolClientMessageProcessor Recover the **first exact downstream consumer or transform** of that retained byte-container state toward framing/final binary egress. Distinguish concrete data flow from lifecycle adjacency. +This resumed dispatch is static-only and GitHub-hosted. It must not touch a running client, canonical runtime, VNC/display, login/session state, retained Synology package paths or Synology runner execution. + # Exact client fence ```yaml @@ -76,9 +139,12 @@ version_mapping: 15.32.df7b29 size: 51965216 sha256: e6c244bd39fe2e0632f6f000efd3147164696efa8e901718668e0442325ff7fe platform: official_native_linux_only -runner: synology-otclient-01 +execution_class: github_hosted +runtime_access: none ``` +The hosted job may consume only an ephemeral official Linux download. It must verify size and SHA-256 before semantic analysis, must never execute the client, and must never upload client/package bytes. If the exact fenced build is no longer obtainable from the official public endpoint, the result is `INPUT_BLOCKED`; it is not permission to use Synology. + # Pinned promoted facts - helper `0x1960340` binds supplied QIODevice shared pair to QDataStream and retains device at `+0x8/+0x10`, stream at `+0x18/+0x20`; @@ -86,11 +152,17 @@ runner: synology-otclient-01 - retained writer is retained by intermediate, which is retained by `TProtocolClientMessageProcessor`; - serializer slots `0xc10960` and `0xc20290` use the retained QDataStream; - local QBuffer slot `0xc20c70` proves QBuffer-backed serialization/exposure; -- only local object lifecycle order is proven. +- only local object lifecycle order was coordinator-promoted by predecessor #308. + +# Historical #310 evidence boundary + +Run `31904967728` directly verified the exact client fence and all checks through `client_processor_actual_object_pointer`, then failed on `client_processor_ap_loaded`. The validator required literal `0x2f6a208` in an objdump instruction line even though the established GNU objdump RIP-relative comment form emits the same address as `2f6a208` without a mandatory `0x` prefix. This resume may repair only that presentation-sensitive assertion and must rerun before claiming any later marker as proven. + +Run `31904696996` provides sanitized prior disassembly useful for hypothesis selection. It does not by itself upgrade its prior `CANDIDATE` first-consumer classification. # Acceptance boundary -A positive downstream claim requires exact type/member provenance plus a concrete read/call/data-flow edge from the retained QBuffer/helper/writer state into the candidate transform/consumer. A symbol-name hit, generic Qt/QBuffer census, vtable adjacency, unrelated local QBuffer use, or a historical final-socket observation is insufficient. +A positive downstream claim requires exact type/member provenance plus a concrete read/call/data-flow edge from the retained QBuffer/helper/writer state into the candidate transform/consumer. A symbol-name hit, generic Qt/QBuffer census, vtable adjacency, unrelated local QBuffer use, historical final-socket observation, or passing workflow without the exact client fence is insufficient. Classify separately: @@ -112,6 +184,8 @@ Do not use: - historical final-socket run `31825417040` as proof; - stale/superseded sink models; - unproven direct DualConnection writer ownership; -- live traffic, credentials, login, attach, gameplay or account state. +- live traffic, credentials, login, attach, gameplay or account state; +- Synology/self-hosted execution for this deterministic static task; +- owner-funded Codex/OpenAI API capacity. -Static exact-build analysis only. Track B is out of scope. Research stays Draft-only; coordinator PR #300 owns promotion. +Research stays Draft-only; coordinator owns promotion. \ No newline at end of file From 366085e6f6a65970b2132e491bd32bf6c05f51b7 Mon Sep 17 00:00:00 2001 From: blakinio <75369544+blakinio@users.noreply.github.com> Date: Sun, 16 Aug 2026 13:20:13 +0200 Subject: [PATCH 08/12] fix(track-a): make P2 address assertion objdump-stable --- ...a-official-client-re-p2-buffer-downstream-consumer.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py b/.github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py index 19e41974d6..e3dfcae4d2 100644 --- a/.github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py +++ b/.github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py @@ -157,10 +157,11 @@ def main() -> int: require(at(setup_i, 0x1970CC6, "call", "QBuffer4open"), "persistent_qbuffer_open") require(at(setup_i, 0x1970CA6, "[rbp-0x218]", "r15"), "persistent_qbuffer_saved_rbpm218") - # TProtocolClientMessageProcessor actual object begins at allocation+0x10. Its this+0x18 - # receives the SAME saved persistent QBuffer object from rbp-0x218. + # TProtocolClientMessageProcessor actual object begins at allocation+0x10. Its this+0x18 + # receives the SAME saved persistent QBuffer object from rbp-0x218. GNU objdump renders + # RIP-relative comment targets without guaranteeing an optional "0x" prefix. require(at(setup_i, 0x197104F, "lea", "rdx,[rax+0x10]"), "client_processor_actual_object_pointer") - require(at(setup_i, 0x1971056, "0x2f6a208"), "client_processor_ap_loaded") + require(at(setup_i, 0x1971056, "2f6a208"), "client_processor_ap_loaded") require(at(setup_i, 0x197105D, "[rax+0x10]", "rcx"), "client_processor_vptr_store") require(at(setup_i, 0x1971084, "rsi", "[rbp-0x218]"), "client_processor_reloads_same_qbuffer") require(at(setup_i, 0x197108F, "[rax+0x28]", "rsi"), "client_processor_this_plus18_qbuffer_store") @@ -342,4 +343,4 @@ def main() -> int: if __name__ == "__main__": - raise SystemExit(main()) + raise SystemExit(main()) \ No newline at end of file From 85f6eee992ea8acc3d0dbe9e7088e156a64fdfcd Mon Sep 17 00:00:00 2001 From: blakinio <75369544+blakinio@users.noreply.github.com> Date: Sun, 16 Aug 2026 13:20:46 +0200 Subject: [PATCH 09/12] ci(track-a): route P2 static proof to GitHub-hosted --- ...lient-re-p2-buffer-downstream-consumer.yml | 135 +++++++++++++----- 1 file changed, 97 insertions(+), 38 deletions(-) diff --git a/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml b/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml index e4a57f69eb..27c686613b 100644 --- a/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml +++ b/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml @@ -13,70 +13,119 @@ on: permissions: contents: read +concurrency: + group: track-a-p2-buffer-downstream-consumer-${{ github.ref }} + cancel-in-progress: true + jobs: hardened-post-serialization-chain: - runs-on: [otclient, synology] - timeout-minutes: 8 + runs-on: ubuntu-latest + timeout-minutes: 20 env: EXPECTED_CLIENT_SHA256: e6c244bd39fe2e0632f6f000efd3147164696efa8e901718668e0442325ff7fe EXPECTED_CLIENT_SIZE: '51965216' EXPECTED_HEAD_BRANCH: research/OTC-20260815-track-a-p2-buffer-downstream-consumer + OFFICIAL_LINUX_ARCHIVE_URL: https://download.tibia.com/tibia.x64.tar.gz steps: - name: Checkout exact Draft head - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - name: Verify exact client and static tool context + - name: Verify hosted no-runtime execution boundary shell: bash run: | set -Eeuo pipefail [[ "$GITHUB_REPOSITORY" == blakinio/otclient ]] [[ "$GITHUB_REF_NAME" == "$EXPECTED_HEAD_BRANCH" ]] - [[ "$RUNNER_NAME" == synology-otclient-01 ]] [[ "$RUNNER_OS" == Linux ]] + [[ "$RUNNER_ARCH" == X64 ]] + [[ "${RUNNER_ENVIRONMENT:-}" == github-hosted ]] python3 -m py_compile .github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py + objdump="$(command -v objdump)" + [[ -x "$objdump" ]] + "$objdump" --version | head -n 1 + echo "TRACK_A_P2_OBJDUMP=$objdump" >> "$GITHUB_ENV" + echo "TRACK_A_P2_RUNTIME_ACCESS=none" + echo "TRACK_A_P2_PERSISTENT_SESSION=none" + echo "TRACK_A_P2_PHYSICAL_E2E=false" + echo "TRACK_A_P2_SYNLOGY_EXECUTION=false" + + - name: Materialize exact official Linux client without executing it + shell: bash + run: | + set -Eeuo pipefail + out=artifacts/p2-buffer-downstream-consumer + mkdir -p "$out" + archive="$RUNNER_TEMP/tibia.x64.tar.gz" + client="$RUNNER_TEMP/tibia-static-client" - client='' - state='' - for candidate in /work/_otclient_tibia_re_state /home/runner/_work/_otclient_tibia_re_state; do - probe="$candidate/home/.local/share/CipSoft GmbH/Tibia/packages/Tibia/bin/client" - [[ -x "$probe" ]] || continue - size="$(stat -c '%s' "$probe")" - sha="$(sha256sum "$probe" | awk '{print $1}')" - if [[ "$size" == "$EXPECTED_CLIENT_SIZE" && "$sha" == "$EXPECTED_CLIENT_SHA256" ]]; then - client="$probe"; state="$candidate"; break - fi - done - [[ -n "$client" && -n "$state" ]] - - objdump="$(command -v objdump || true)" - if [[ -z "$objdump" ]]; then - pinned="$state/toolroot/usr/bin/objdump" - [[ -x "$pinned" ]] - wrapper="$RUNNER_TEMP/track-a-p2-downstream-objdump" - cat > "$wrapper" </dev/null + + python3 - "$archive" "$client" <<'PY' + import shutil + import sys + import tarfile + from pathlib import PurePosixPath + + archive, target = sys.argv[1:] + with tarfile.open(archive, mode='r:*') as tf: + candidates = [] + for member in tf.getmembers(): + if not member.isfile(): + continue + path = PurePosixPath(member.name) + if path.is_absolute() or '..' in path.parts: + continue + parts = path.parts + if len(parts) >= 2 and parts[-2:] == ('bin', 'client'): + candidates.append(member) + if len(candidates) != 1: + names = ','.join(m.name for m in candidates[:10]) or '' + raise SystemExit(f'P2_HOSTED_INPUT_BLOCKED=client_member_count:{len(candidates)}:{names}') + src = tf.extractfile(candidates[0]) + if src is None: + raise SystemExit('P2_HOSTED_INPUT_BLOCKED=client_member_unreadable') + with src, open(target, 'wb') as dst: + shutil.copyfileobj(src, dst, length=1024 * 1024) + print(f'P2_HOSTED_CLIENT_MEMBER={candidates[0].name}') + PY + + size="$(stat -c '%s' "$client")" + sha="$(sha256sum "$client" | awk '{print $1}')" { - echo "TRACK_A_P2_CLIENT=$client" - echo "TRACK_A_P2_OBJDUMP=$objdump" - } >> "$GITHUB_ENV" + echo "P2_HOSTED_INPUT_SIZE=$size" + echo "P2_HOSTED_INPUT_SHA256=$sha" + echo "P2_HOSTED_EXPECTED_SIZE=$EXPECTED_CLIENT_SIZE" + echo "P2_HOSTED_EXPECTED_SHA256=$EXPECTED_CLIENT_SHA256" + } | tee "$out/input-status.txt" + + if [[ "$size" != "$EXPECTED_CLIENT_SIZE" || "$sha" != "$EXPECTED_CLIENT_SHA256" ]]; then + echo 'P2_HOSTED_INPUT_STATUS=INPUT_BLOCKED' | tee -a "$out/input-status.txt" + echo 'P2_HOSTED_INPUT_REASON=exact_client_fence_mismatch' | tee -a "$out/input-status.txt" + exit 3 + fi + + echo 'P2_HOSTED_INPUT_STATUS=EXACT_FENCE_VERIFIED' | tee -a "$out/input-status.txt" + echo "TRACK_A_P2_CLIENT=$client" >> "$GITHUB_ENV" echo "TRACK_A_P2_DOWNSTREAM_EXACT_HEAD=$GITHUB_SHA" - echo TRACK_A_P2_DOWNSTREAM_EXACT_CLIENT_VERIFIED=true + echo 'TRACK_A_P2_DOWNSTREAM_EXACT_CLIENT_VERIFIED=true' - name: Prove persistent QBuffer downstream processor chain shell: bash run: | set -Eeuo pipefail out=artifacts/p2-buffer-downstream-consumer - mkdir -p "$out" python3 .github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py \ --client "$TRACK_A_P2_CLIENT" \ --objdump "$TRACK_A_P2_OBJDUMP" \ @@ -113,10 +162,20 @@ jobs: n=d['negative_controls'] assert not any(n.values()) PY - echo TRACK_A_P2_DOWNSTREAM_HARDENED_VALIDATED=true + echo 'TRACK_A_P2_DOWNSTREAM_HARDENED_VALIDATED=true' + + - name: Remove proprietary input before evidence upload + if: always() + shell: bash + run: | + set -Eeuo pipefail + rm -f "$RUNNER_TEMP/tibia-static-client" "$RUNNER_TEMP/tibia.x64.tar.gz" + test ! -e "$RUNNER_TEMP/tibia-static-client" + test ! -e "$RUNNER_TEMP/tibia.x64.tar.gz" - name: Upload sanitized static evidence only - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: track-a-p2-buffer-downstream-consumer-${{ github.run_id }} path: artifacts/p2-buffer-downstream-consumer/** From 61b84f4a770dfcb130511ca89e335f0e2f23fd2c Mon Sep 17 00:00:00 2001 From: blakinio <75369544+blakinio@users.noreply.github.com> Date: Sun, 16 Aug 2026 13:21:50 +0200 Subject: [PATCH 10/12] ci(track-a): use static Tibia Linux download endpoint --- ...bia-official-client-re-p2-buffer-downstream-consumer.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml b/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml index 27c686613b..4a8dd149f8 100644 --- a/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml +++ b/.github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml @@ -6,8 +6,6 @@ on: paths: - .github/workflows/tibia-official-client-re-p2-buffer-downstream-consumer.yml - .github/scripts/tibia-official-client-re-p2-buffer-downstream-consumer.py - - docs/agents/evidence/OTC-20260815-track-a-p2-buffer-downstream-consumer/** - - docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md workflow_dispatch: permissions: @@ -25,7 +23,7 @@ jobs: EXPECTED_CLIENT_SHA256: e6c244bd39fe2e0632f6f000efd3147164696efa8e901718668e0442325ff7fe EXPECTED_CLIENT_SIZE: '51965216' EXPECTED_HEAD_BRANCH: research/OTC-20260815-track-a-p2-buffer-downstream-consumer - OFFICIAL_LINUX_ARCHIVE_URL: https://download.tibia.com/tibia.x64.tar.gz + OFFICIAL_LINUX_ARCHIVE_URL: https://static.tibia.com/download/tibia.x64.tar.gz steps: - name: Checkout exact Draft head uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 @@ -49,7 +47,7 @@ jobs: echo "TRACK_A_P2_RUNTIME_ACCESS=none" echo "TRACK_A_P2_PERSISTENT_SESSION=none" echo "TRACK_A_P2_PHYSICAL_E2E=false" - echo "TRACK_A_P2_SYNLOGY_EXECUTION=false" + echo "TRACK_A_P2_SYNOLOGY_EXECUTION=false" - name: Materialize exact official Linux client without executing it shell: bash From a01281648c35dc04bf20437acc584b55b11ea727 Mon Sep 17 00:00:00 2001 From: blakinio <75369544+blakinio@users.noreply.github.com> Date: Sun, 16 Aug 2026 13:24:40 +0200 Subject: [PATCH 11/12] docs(track-a): checkpoint P2 hosted input blocker --- ...5-track-a-p2-buffer-downstream-consumer.md | 109 ++++++++++++++---- 1 file changed, 87 insertions(+), 22 deletions(-) diff --git a/docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md b/docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md index 8e0c1640df..ff760e3487 100644 --- a/docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md +++ b/docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md @@ -1,7 +1,7 @@ --- task_id: OTC-20260815-track-a-p2-buffer-downstream-consumer -status: investigating -agent: ChatGPT +status: blocked +agent: unassigned session_id: chatgpt-p2-hosted-resume-20260816-1317 session_role: researcher session_rotation_count: 2 @@ -18,8 +18,8 @@ worktree_mode: isolated_branch_checkout_equivalent risk: medium related_pr: 310 created: 2026-08-15T21:40:00+02:00 -updated: 2026-08-16T13:17:33+02:00 -lease_expires_at: 2026-08-16T14:02:33+02:00 +updated: 2026-08-16T13:22:53+02:00 +lease_expires_at: 2026-08-16T13:22:53+02:00 owned_paths: - docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md - docs/agents/evidence/OTC-20260815-track-a-p2-buffer-downstream-consumer/** @@ -78,17 +78,19 @@ decomposition_decision: single decomposition_reason: one bounded P2 post-serialization chain question with one owned validator/workflow and no runtime dependency validation_level: focused invocation_started_at: 2026-08-16T13:07:00+02:00 -last_progress_at: 2026-08-16T13:17:33+02:00 +last_progress_at: 2026-08-16T13:22:03+02:00 ci_checks_for_current_head: 0 -ci_check_generation: hosted-redispatch +ci_check_generation: blocked-checkpoint terminal_ci_wait_started_at: null terminal_ci_checks_for_current_generation: 0 unchanged_state_checks: 0 identical_failure_retries: 0 -repair_cycles_for_current_gate: 0 +repair_cycles_for_current_gate: 1 context_reconstruction_attempts: 1 stall_warnings: 0 -heavy_validation_runs: 0 +heavy_validation_runs: 2 +heavy_validation_result: blocked_before_semantic_validator +terminal_invocation_result: BLOCKED runtime_nonclaims: display_98_current_canonical_status: UNKNOWN rfb_6082_current_backend_mapping: UNKNOWN @@ -97,12 +99,16 @@ runtime_nonclaims: historical_run_disposition: run_31904696996: sanitized_static_evidence_only_do_not_treat_as_current_execution run_31904967728: exact_client_static_failure_log_only_do_not_repeat_on_synology + run_31944051248: QUARANTINED_ROUTING_VIOLATION_successful_static_synology_run_not_current_proof + run_31944074222: HOSTED_INPUT_BLOCKED_download.tibia.com_DNS_unresolved + run_31944119641: HOSTED_INPUT_BLOCKED_static.tibia.com_HTTP_403 synology_static_rerun: FORBIDDEN_BY_CURRENT_ROUTING known_validator_defect: marker: client_processor_ap_loaded failing_run: 31904967728 first_actionable_error: GNU_objdump_rip_comment_contains_2f6a208_without_required_0x_prefix repair: accept the exact address token independent of the optional 0x presentation prefix + repair_commit: 366085e6f6a65970b2132e491bd32bf6c05f51b7 hosted_input_policy: source: official Linux Tibia download endpoint only use: ephemeral read-only archive extraction and exact-fence static analysis @@ -111,8 +117,64 @@ hosted_input_policy: upload_client_bytes: false exact_fence_required: true on_download_or_fence_mismatch: fail_closed_INPUT_BLOCKED_no_synology_fallback -active_operation: refresh the stale P2 Draft to current hosted/no-runtime routing, repair the proven validator formatting defect, and rerun the exact static discriminator without touching canonical or Synology runtime state -next_action: replace the stale Synology workflow with an ubuntu-latest fail-closed official-artifact static validator, repair client_processor_ap_loaded matching, run once, and classify the first downstream consumer/transform without promoting framing or egress beyond evidence +hosted_attempts: + count: 2 + exhausted_for_session: true + first: + run: 31944074222 + head: 85f6eee992ea8acc3d0dbe9e7088e156a64fdfcd + job: 95157361223 + runner: github_hosted_ubuntu_24_04 + runtime_access: none + result: INPUT_BLOCKED + first_actionable_error: download.tibia.com_DNS_resolution_failed + artifact: 9262801026 + second: + run: 31944119641 + head: 61b84f4a770dfcb130511ca89e335f0e2f23fd2c + job: 95157468020 + runner: github_hosted_ubuntu_24_04 + runtime_access: none + result: INPUT_BLOCKED + first_actionable_error: static.tibia.com_returned_HTTP_403 + artifact: 9262812558 + proprietary_input_cleanup: PASS +current_compliant_result: + exact_binary_materialized: false + semantic_validator_ran: false + first_downstream_consumer: UNKNOWN + protocol_stage_order: UNKNOWN + framing: UNKNOWN + sequence: UNKNOWN + compression: UNKNOWN + encryption: UNKNOWN + final_binary_egress: UNKNOWN +blocker: + type: INPUT_BLOCKED + execution_class: github_hosted + runtime_access: none + validated_experiment_head: 61b84f4a770dfcb130511ca89e335f0e2f23fd2c + run: 31944119641 + job: 95157468020 + exact_failure: official_native_Linux_archive_static.tibia.com_returned_HTTP_403_to_GitHub_hosted_runner + previous_attempt: download.tibia.com_DNS_resolution_failed_on_GitHub_hosted_runner + exact_binary_materialized: false + semantic_validator_ran: false + synology_fallback_allowed: false + repair_budget_exhausted: true +e2e: + result: NOT_APPLICABLE + reason: static reverse-engineering validation only; no live/client runtime behavior changed or authorized +audit: + result: NOT_TERMINAL_COMPLETION_AUDIT + material_findings_open: 0 + notes: + - compliant hosted attempts preserved runtime_access none and did not execute the client + - exact client/package bytes were not uploaded by the compliant hosted attempts + - run 31944051248 is explicitly quarantined and cannot be used as current routing-compliant proof +active_operation: stopped after the second permitted GitHub-hosted exact-input materialization attempt failed closed before semantic analysis +last_completed_step: second and final permitted hosted attempt 31944119641 passed the hosted/no-runtime boundary, received HTTP 403 while materializing the official Linux archive, removed any temporary proprietary input, and uploaded sanitized blocker artifact 9262812558 +next_action: coordinator must provide or approve a legally and technically compliant GitHub-hosted-readable staging source for the exact fenced native-Linux client, without Synology execution or runtime access, then redispatch this same validator from current main --- # Objective @@ -143,7 +205,7 @@ execution_class: github_hosted runtime_access: none ``` -The hosted job may consume only an ephemeral official Linux download. It must verify size and SHA-256 before semantic analysis, must never execute the client, and must never upload client/package bytes. If the exact fenced build is no longer obtainable from the official public endpoint, the result is `INPUT_BLOCKED`; it is not permission to use Synology. +The hosted job may consume only an ephemeral official Linux download. It must verify size and SHA-256 before semantic analysis, must never execute the client, and must never upload client/package bytes. If the exact fenced build is not legally and technically obtainable on GitHub-hosted execution, the result is `INPUT_BLOCKED`; it is not permission to use Synology. # Pinned promoted facts @@ -156,24 +218,26 @@ The hosted job may consume only an ephemeral official Linux download. It must ve # Historical #310 evidence boundary -Run `31904967728` directly verified the exact client fence and all checks through `client_processor_actual_object_pointer`, then failed on `client_processor_ap_loaded`. The validator required literal `0x2f6a208` in an objdump instruction line even though the established GNU objdump RIP-relative comment form emits the same address as `2f6a208` without a mandatory `0x` prefix. This resume may repair only that presentation-sensitive assertion and must rerun before claiming any later marker as proven. +Run `31904967728` directly verified the exact client fence and all checks through `client_processor_actual_object_pointer`, then failed on `client_processor_ap_loaded`. The validator required literal `0x2f6a208` in an objdump instruction line even though GNU objdump's RIP-relative comment target can emit the same exact address as `2f6a208` without a mandatory `0x` prefix. Commit `366085e6f6a65970b2132e491bd32bf6c05f51b7` repairs only that presentation-sensitive assertion. -Run `31904696996` provides sanitized prior disassembly useful for hypothesis selection. It does not by itself upgrade its prior `CANDIDATE` first-consumer classification. +Run `31944051248` subsequently completed the static validator under the stale pre-routing Synology workflow. Because current policy and owner direction require GitHub-hosted execution for this deterministic task, that success is quarantined as a routing violation and is not accepted as current proof. + +The two compliant hosted attempts did not reach semantic analysis. Run `31944074222` failed closed because `download.tibia.com` could not be resolved; after one evidence-based endpoint repair, run `31944119641` reached `static.tibia.com` but received HTTP 403. The second attempt then removed temporary proprietary input and uploaded only sanitized blocker evidence. The session's two-full-attempt budget is exhausted. # Acceptance boundary -A positive downstream claim requires exact type/member provenance plus a concrete read/call/data-flow edge from the retained QBuffer/helper/writer state into the candidate transform/consumer. A symbol-name hit, generic Qt/QBuffer census, vtable adjacency, unrelated local QBuffer use, historical final-socket observation, or passing workflow without the exact client fence is insufficient. +A positive downstream claim requires exact type/member provenance plus a concrete read/call/data-flow edge from the retained QBuffer/helper/writer state into the candidate transform/consumer. A symbol-name hit, generic Qt/QBuffer census, vtable adjacency, unrelated local QBuffer use, historical final-socket observation, quarantined routing-violating execution, or passing workflow without the exact client fence is insufficient. -Classify separately: +Until a routing-compliant hosted run materializes and verifies the exact fenced binary, the current task does not promote a new downstream semantic claim. Current compliant classifications remain: ```yaml -first_downstream_consumer: PROVEN | CANDIDATE | UNKNOWN -protocol_stage_order: PROVEN_PARTIAL | UNKNOWN -framing: PROVEN | CANDIDATE | UNKNOWN -sequence: PROVEN | CANDIDATE | UNKNOWN -compression: PROVEN | CANDIDATE | UNKNOWN -encryption: PROVEN | CANDIDATE | UNKNOWN -final_binary_egress: PROVEN | CANDIDATE | UNKNOWN +first_downstream_consumer: UNKNOWN +protocol_stage_order: UNKNOWN +framing: UNKNOWN +sequence: UNKNOWN +compression: UNKNOWN +encryption: UNKNOWN +final_binary_egress: UNKNOWN ``` # Forbidden shortcuts @@ -186,6 +250,7 @@ Do not use: - unproven direct DualConnection writer ownership; - live traffic, credentials, login, attach, gameplay or account state; - Synology/self-hosted execution for this deterministic static task; +- a third full hosted attempt in this invocation after the two-attempt budget is exhausted; - owner-funded Codex/OpenAI API capacity. Research stays Draft-only; coordinator owns promotion. \ No newline at end of file From e664b07e231fde68a0b801e11e4e4b9456dfdf3c Mon Sep 17 00:00:00 2001 From: blakinio <75369544+blakinio@users.noreply.github.com> Date: Sun, 16 Aug 2026 16:37:54 +0200 Subject: [PATCH 12/12] docs(track-a): checkpoint P2 sanitized evidence review --- ...5-track-a-p2-buffer-downstream-consumer.md | 217 +++++++----------- 1 file changed, 88 insertions(+), 129 deletions(-) diff --git a/docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md b/docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md index ff760e3487..db1b0954e1 100644 --- a/docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md +++ b/docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md @@ -1,25 +1,25 @@ --- task_id: OTC-20260815-track-a-p2-buffer-downstream-consumer -status: blocked +status: ready agent: unassigned -session_id: chatgpt-p2-hosted-resume-20260816-1317 +session_id: chatgpt-p2-sanitized-evidence-20260816-1635 session_role: researcher -session_rotation_count: 2 +session_rotation_count: 3 project_lane: otclient lane: P2-NETWORK track_id: official-client-re task_kind: validation -phase: validate +phase: coordinator-promotion-ready branch: research/OTC-20260815-track-a-p2-buffer-downstream-consumer base_branch: main -base_main: 0d7b2607912552599ae501891491aab439cfde7b +base_main: c66e8b563f748e0595e3b7144c3fac3dc744c60c worktree: github-only://blakinio/otclient/refs/heads/research/OTC-20260815-track-a-p2-buffer-downstream-consumer worktree_mode: isolated_branch_checkout_equivalent risk: medium related_pr: 310 created: 2026-08-15T21:40:00+02:00 -updated: 2026-08-16T13:22:53+02:00 -lease_expires_at: 2026-08-16T13:22:53+02:00 +updated: 2026-08-16T16:38:00+02:00 +lease_expires_at: 2026-08-16T16:38:00+02:00 owned_paths: - docs/agents/tasks/active/OTC-20260815-track-a-p2-buffer-downstream-consumer.md - docs/agents/evidence/OTC-20260815-track-a-p2-buffer-downstream-consumer/** @@ -28,10 +28,10 @@ owned_paths: modules_touched: [] reuses: - coordinator-promoted PR #308 exact retained QBuffer/QDataStream boundary - - PR #310 run 31904696996 sanitized static artifact as historical discovery input only - - PR #310 run 31904967728 exact-client failure log as historical discovery input only + - PR #310 run 31904696996 sanitized exact-fence evidence bundle artifact 9252025461 + - coordinator PR #374 terminal disposition permitting resume on a pre-sanitized exact-binary evidence bundle depends_on: - - main@0d7b2607912552599ae501891491aab439cfde7b + - current main@c66e8b563f748e0595e3b7144c3fac3dc744c60c - coordinator promotion of closed-unmerged PR #308 as pinned evidence only blocks: [] policy_version: 2 @@ -40,7 +40,7 @@ prompt_contract_version: 1.0.0 routing_contract: docs/agents/programs/OTCLIENT_TIBIA_RE_HYBRID_EXECUTION_ROUTING.md track_a_runtime_agent_admission_version: 1 execution_mode: github-only -execution_reason: deterministic exact-build static ELF/disassembly validation on GitHub-hosted execution; no live client or retained Synology state is required or permitted +execution_reason: review of a pre-sanitized exact-binary evidence bundle; no client rematerialization, live runtime or Synology execution is required or permitted execution_class: github_hosted runtime_access: none persistent_session_role: consumer_of_runtime_evidence @@ -63,7 +63,7 @@ task_completion_policy: draft_pr_only user_communication: milestone_and_terminal implementation_authorized: true feature_scope: - type: infrastructure + type: protocol user_facing: false backend_required: false frontend_required: false @@ -77,122 +77,102 @@ estimate_confidence: high decomposition_decision: single decomposition_reason: one bounded P2 post-serialization chain question with one owned validator/workflow and no runtime dependency validation_level: focused -invocation_started_at: 2026-08-16T13:07:00+02:00 -last_progress_at: 2026-08-16T13:22:03+02:00 +invocation_started_at: 2026-08-16T16:35:00+02:00 +last_progress_at: 2026-08-16T16:38:00+02:00 ci_checks_for_current_head: 0 -ci_check_generation: blocked-checkpoint +ci_check_generation: sanitized-evidence-checkpoint terminal_ci_wait_started_at: null terminal_ci_checks_for_current_generation: 0 unchanged_state_checks: 0 identical_failure_retries: 0 -repair_cycles_for_current_gate: 1 +repair_cycles_for_current_gate: 0 context_reconstruction_attempts: 1 stall_warnings: 0 -heavy_validation_runs: 2 -heavy_validation_result: blocked_before_semantic_validator -terminal_invocation_result: BLOCKED +heavy_validation_runs: 0 +heavy_validation_result: NOT_RUN_NO_BINARY_REMATERIALIZATION +terminal_invocation_result: WAITING_COORDINATOR_PROMOTION runtime_nonclaims: display_98_current_canonical_status: UNKNOWN rfb_6082_current_backend_mapping: UNKNOWN current_exact_client_pid: NOT_REGISTERED current_exact_client_session: NOT_REGISTERED historical_run_disposition: - run_31904696996: sanitized_static_evidence_only_do_not_treat_as_current_execution - run_31904967728: exact_client_static_failure_log_only_do_not_repeat_on_synology + run_31904696996: ACCEPTED_AS_SANITIZED_EXACT_FENCE_EVIDENCE_BUNDLE_FOR_REVIEW_NOT_AS_CURRENT_EXECUTION + run_31904967728: exact_client_static_failure_log_only run_31944051248: QUARANTINED_ROUTING_VIOLATION_successful_static_synology_run_not_current_proof run_31944074222: HOSTED_INPUT_BLOCKED_download.tibia.com_DNS_unresolved run_31944119641: HOSTED_INPUT_BLOCKED_static.tibia.com_HTTP_403 + run_31951153838: SHARED_HOSTED_STAGING_DISCOVERY_INPUT_BLOCKED_PR374 synology_static_rerun: FORBIDDEN_BY_CURRENT_ROUTING -known_validator_defect: - marker: client_processor_ap_loaded - failing_run: 31904967728 - first_actionable_error: GNU_objdump_rip_comment_contains_2f6a208_without_required_0x_prefix - repair: accept the exact address token independent of the optional 0x presentation prefix - repair_commit: 366085e6f6a65970b2132e491bd32bf6c05f51b7 -hosted_input_policy: - source: official Linux Tibia download endpoint only - use: ephemeral read-only archive extraction and exact-fence static analysis - execute_client: false - persist_client_bytes: false - upload_client_bytes: false - exact_fence_required: true - on_download_or_fence_mismatch: fail_closed_INPUT_BLOCKED_no_synology_fallback -hosted_attempts: - count: 2 - exhausted_for_session: true - first: - run: 31944074222 - head: 85f6eee992ea8acc3d0dbe9e7088e156a64fdfcd - job: 95157361223 - runner: github_hosted_ubuntu_24_04 - runtime_access: none - result: INPUT_BLOCKED - first_actionable_error: download.tibia.com_DNS_resolution_failed - artifact: 9262801026 - second: - run: 31944119641 - head: 61b84f4a770dfcb130511ca89e335f0e2f23fd2c - job: 95157468020 - runner: github_hosted_ubuntu_24_04 - runtime_access: none - result: INPUT_BLOCKED - first_actionable_error: static.tibia.com_returned_HTTP_403 - artifact: 9262812558 - proprietary_input_cleanup: PASS +sanitized_evidence_bundle: + run: 31904696996 + artifact: 9252025461 + artifact_digest: sha256:2a866247558b079944d81c9ad33bd4c5361c8144a7f367b273ab3bc19a080991 + expires_at: 2026-08-22T19:44:07Z + contains_client_or_package_bytes: false + contains_exact_fence_validation: true + exact_client_size: 51965216 + exact_client_sha256: e6c244bd39fe2e0632f6f000efd3147164696efa8e901718668e0442325ff7fe + evidence_files: + - validation.log + - result.json + - result.txt + - evidence.txt current_compliant_result: - exact_binary_materialized: false - semantic_validator_ran: false - first_downstream_consumer: UNKNOWN - protocol_stage_order: UNKNOWN + exact_binary_rematerialized_this_invocation: false + semantic_evidence_reviewed: true + persistent_qbuffer_direct_readall: PROVEN + first_downstream_consumer: PROVEN:TProtocolClientMessageProcessor+0x10@0xc2df80 + first_downstream_transform: PROVEN:TGameserverNetworkPacketRawDataProcessor+0x10@0xb47130 + same_message_handoff_to_dualconnection: PROVEN + protocol_stage_order: PROVEN_PARTIAL framing: UNKNOWN sequence: UNKNOWN compression: UNKNOWN encryption: UNKNOWN final_binary_egress: UNKNOWN +proof_chain: + - setup provenance binds the persistent QBuffer to TProtocolClientMessageProcessor this+0x18 + - TProtocolClientMessageProcessor+0x10 reads that same member and calls QIODevice::readAll + - returned bytes are assigned to message QByteArray at message+0x8 + - the invoker passes the same message object to TGameserverNetworkPacketRawDataProcessor+0x10 + - RawDataProcessor performs QByteArray insert/append and assigns the transformed QByteArray back in place + - the same post-transform message object is then passed to TGameserverDualConnection virtual +0x80 and +0x78 +negative_controls: + generic_qiodevice_census_used_as_proof: false + generic_qbuffer_census_used_as_proof: false + vtable_adjacency_used_as_temporal_proof: false + historical_final_socket_run_used_as_proof: false + direct_dualconnection_writer_ownership_assumed: false + dual_plus80_or_plus78_labeled_final_egress: false + raw_byte_transform_labeled_framing_without_semantics: false blocker: - type: INPUT_BLOCKED - execution_class: github_hosted - runtime_access: none - validated_experiment_head: 61b84f4a770dfcb130511ca89e335f0e2f23fd2c - run: 31944119641 - job: 95157468020 - exact_failure: official_native_Linux_archive_static.tibia.com_returned_HTTP_403_to_GitHub_hosted_runner - previous_attempt: download.tibia.com_DNS_resolution_failed_on_GitHub_hosted_runner - exact_binary_materialized: false - semantic_validator_ran: false + type: COORDINATOR_PROMOTION_REVIEW + direct_exact_client_staging: INPUT_BLOCKED + shared_unblocker_pr: 374 + shared_unblocker_disposition: INPUT_BLOCKED_CLOSE_UNMERGED + pre_sanitized_bundle_resume_condition: SATISFIED_FOR_RESEARCH_REVIEW + coordinator_acceptance_of_bundle_for_promotion: PENDING synology_fallback_allowed: false - repair_budget_exhausted: true e2e: result: NOT_APPLICABLE - reason: static reverse-engineering validation only; no live/client runtime behavior changed or authorized + reason: static reverse-engineering evidence review only; no live/client runtime behavior changed or authorized audit: - result: NOT_TERMINAL_COMPLETION_AUDIT + result: PASS_WITH_BOUNDED_CLAIMS material_findings_open: 0 notes: - - compliant hosted attempts preserved runtime_access none and did not execute the client - - exact client/package bytes were not uploaded by the compliant hosted attempts - - run 31944051248 is explicitly quarantined and cannot be used as current routing-compliant proof -active_operation: stopped after the second permitted GitHub-hosted exact-input materialization attempt failed closed before semantic analysis -last_completed_step: second and final permitted hosted attempt 31944119641 passed the hosted/no-runtime boundary, received HTTP 403 while materializing the official Linux archive, removed any temporary proprietary input, and uploaded sanitized blocker artifact 9262812558 -next_action: coordinator must provide or approve a legally and technically compliant GitHub-hosted-readable staging source for the exact fenced native-Linux client, without Synology execution or runtime access, then redispatch this same validator from current main + - exact-fence evidence is carried by the preserved sanitized bundle and was not regenerated in this invocation + - no proprietary client/package bytes were downloaded, executed or uploaded in this invocation + - framing, sequence, compression, encryption and final binary egress remain UNKNOWN + - run 31944051248 remains quarantined and is not used as proof +active_operation: stopped at coordinator promotion boundary after reviewing the pre-sanitized exact-binary evidence bundle +last_completed_step: reviewed artifact 9252025461, verified exact-fence markers and concrete same-object data flow from persistent QBuffer readAll through RawDataProcessor into DualConnection handoff, and updated Draft PR #310 with the narrow supported classification +next_action: coordinator must independently review artifact 9252025461 and the exact PR #310 diff; if accepted, refresh/replay the three owned paths on current main without rerunning blocked exact-client staging, obtain exact-head governance/CI, then promote or close according to coordinator authority --- # Objective -Start from the coordinator-promoted P2 boundary: - -```text -TProtocolClientMessageProcessor - -> retained intermediate AP 0x2f69e30 / RTTI 0x3080748 - -> retained TProtocolWriter AP 0x2f69dd0 / RTTI 0x3080728 - -> retained helper 0x1960340 / TIODeviceWriter AP 0x2f69d48 - -> retained QDataStream serialization - -> persistent QBuffer-backed QIODevice byte container -``` - -Recover the **first exact downstream consumer or transform** of that retained byte-container state toward framing/final binary egress. Distinguish concrete data flow from lifecycle adjacency. - -This resumed dispatch is static-only and GitHub-hosted. It must not touch a running client, canonical runtime, VNC/display, login/session state, retained Synology package paths or Synology runner execution. +Start from the coordinator-promoted P2 boundary and recover the first exact downstream consumer or transform of the retained byte-container state toward framing/final binary egress while separating proven data flow from unknown transport semantics. # Exact client fence @@ -205,34 +185,20 @@ execution_class: github_hosted runtime_access: none ``` -The hosted job may consume only an ephemeral official Linux download. It must verify size and SHA-256 before semantic analysis, must never execute the client, and must never upload client/package bytes. If the exact fenced build is not legally and technically obtainable on GitHub-hosted execution, the result is `INPUT_BLOCKED`; it is not permission to use Synology. - -# Pinned promoted facts - -- helper `0x1960340` binds supplied QIODevice shared pair to QDataStream and retains device at `+0x8/+0x10`, stream at `+0x18/+0x20`; -- persistent QBuffer pair is passed to that helper and helper is retained at `TProtocolWriter+0x18/+0x20`; -- retained writer is retained by intermediate, which is retained by `TProtocolClientMessageProcessor`; -- serializer slots `0xc10960` and `0xc20290` use the retained QDataStream; -- local QBuffer slot `0xc20c70` proves QBuffer-backed serialization/exposure; -- only local object lifecycle order was coordinator-promoted by predecessor #308. +# Evidence-reviewed result -# Historical #310 evidence boundary +The preserved sanitized exact-fence bundle from run `31904696996` / artifact `9252025461` contains exact size/SHA validation and sanitized disassembly. It proves the setup provenance from the persistent QBuffer into `TProtocolClientMessageProcessor this+0x18`; at `0xc2dfa5` that same member is loaded and at `0xc2dfd5` it is consumed by `QIODevice::readAll`. The bytes are assigned into the output message `QByteArray` at `message+0x8`. -Run `31904967728` directly verified the exact client fence and all checks through `client_processor_actual_object_pointer`, then failed on `client_processor_ap_loaded`. The validator required literal `0x2f6a208` in an objdump instruction line even though GNU objdump's RIP-relative comment target can emit the same exact address as `2f6a208` without a mandatory `0x` prefix. Commit `366085e6f6a65970b2132e491bd32bf6c05f51b7` repairs only that presentation-sensitive assertion. +The exact invoker at `0x7dd630` then passes the same stack message object to `TGameserverNetworkPacketRawDataProcessor+0x10@0xb47130`, whose captured disassembly performs `QByteArray::insert`, `QByteArray::append` and an in-place `QByteArray::operator=` back to the same message field. The same message object is subsequently handed to `TGameserverDualConnection` virtual `+0x80` and `+0x78`. -Run `31944051248` subsequently completed the static validator under the stale pre-routing Synology workflow. Because current policy and owner direction require GitHub-hosted execution for this deterministic task, that success is quarantined as a routing violation and is not accepted as current proof. - -The two compliant hosted attempts did not reach semantic analysis. Run `31944074222` failed closed because `download.tibia.com` could not be resolved; after one evidence-based endpoint repair, run `31944119641` reached `static.tibia.com` but received HTTP 403. The second attempt then removed temporary proprietary input and uploaded only sanitized blocker evidence. The session's two-full-attempt budget is exhausted. - -# Acceptance boundary - -A positive downstream claim requires exact type/member provenance plus a concrete read/call/data-flow edge from the retained QBuffer/helper/writer state into the candidate transform/consumer. A symbol-name hit, generic Qt/QBuffer census, vtable adjacency, unrelated local QBuffer use, historical final-socket observation, quarantined routing-violating execution, or passing workflow without the exact client fence is insufficient. - -Until a routing-compliant hosted run materializes and verifies the exact fenced binary, the current task does not promote a new downstream semantic claim. Current compliant classifications remain: +This supports only the following classifications: ```yaml -first_downstream_consumer: UNKNOWN -protocol_stage_order: UNKNOWN +persistent_qbuffer_direct_readall: PROVEN +first_downstream_consumer: PROVEN:TProtocolClientMessageProcessor+0x10@0xc2df80 +first_downstream_transform: PROVEN:TGameserverNetworkPacketRawDataProcessor+0x10@0xb47130 +same_message_handoff_to_dualconnection: PROVEN +protocol_stage_order: PROVEN_PARTIAL framing: UNKNOWN sequence: UNKNOWN compression: UNKNOWN @@ -240,17 +206,10 @@ encryption: UNKNOWN final_binary_egress: UNKNOWN ``` -# Forbidden shortcuts +No claim is made that the RawDataProcessor transform is framing, compression or encryption, and no DualConnection virtual is labelled final egress without exact transport semantics. + +# Current staging boundary -Do not use: -- generic QIODevice/QBuffer/QByteArray callsite census as proof; -- vtable adjacency as temporal order; -- historical final-socket run `31825417040` as proof; -- stale/superseded sink models; -- unproven direct DualConnection writer ownership; -- live traffic, credentials, login, attach, gameplay or account state; -- Synology/self-hosted execution for this deterministic static task; -- a third full hosted attempt in this invocation after the two-attempt budget is exhausted; -- owner-funded Codex/OpenAI API capacity. +Shared hosted staging discovery PR #374 was closed `INPUT_BLOCKED`. Do not add another guessed/direct/WARP HTTP retry and do not fall back to Synology. The coordinator explicitly permitted resume if a legally/technically compliant hosted exact input or a pre-sanitized exact-binary evidence bundle becomes available; artifact `9252025461` satisfies the latter condition for research review. Coordinator promotion authority remains separate and pending. -Research stays Draft-only; coordinator owns promotion. \ No newline at end of file +Research stays Draft-only; coordinator owns promotion/merge/terminal closeout. \ No newline at end of file