Files
felhom.eu/documentation/audits/night-fixes-2026-10-26/tools/r1187_r1220_live.py
T
admin ae3db89ab3
gates / gates (push) Successful in 1m56s
night fixes 2026-10-26: live proofs (R-1220, R-1232, R-1187, R-1298), register states, design lines; R-1300 filed; R-528 design
Evidence audits/night-fixes-2026-10-26/ (scanned: 0 secret hits).
2026-10-26 22:17:36 +01:00

126 lines
6.4 KiB
Python

#!/usr/bin/env python3
"""r1187_r1220_live.py — R-1220 and R-1187 (a)(b) live on scratch guest 9202, through the endpoints the UI invokes.
A THROWAWAY Grimmory (drive appdata binds). Steps:
1. install; a marker folder in its drive appdata: an owned file (1000:1001, 0640), a link inside, a link outside;
2. R-1220: its app.yaml made unreadable (a byte copy saved first) → after a stack rescan the „Megőrzött adatok" page does
NOT list its folder; the app.yaml put back byte-identical;
3. R-1187 (a): a FIFO in the folder → the export FAILS naming the FIFO's path; the FIFO removed;
4. R-1187 (a)(b): export → import over the running app → the owned file keeps owner and mode, the inside link comes
back, the outside link is refused (listed in the controller WARN);
5. teardown: the app removed through the product (data and backups), the bundles this run made deleted.
usage: SC=<0600 dir with .ctlpw> SWEEP_EV=<evidence dir> TARGET=9202 DRIVE_ROOT=/mnt/felhom-drives/scratch_hdd python3 -u r1187_r1220_live.py
"""
import os
import signal
import sys
import time
signal.signal(signal.SIGTERM, lambda *a: (_ for _ in ()).throw(SystemExit("SIGTERM — cleaning up")))
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../catalog-sweep-2026-10-24/tools"))
import sweep as sw # noqa: E402
w = sw.w
APP, SUB, DRIVE = "grimmory", "grimd53", os.environ.get("DRIVE_ROOT", "/mnt/felhom-drives/scratch_hdd")
say = print
def g(cmd):
return sw.g(cmd).strip()
def job(kind):
path = "/api/export/status" if kind == "export" else "/api/export/import/status"
for _ in range(180):
time.sleep(5)
code, d = w.ctl("GET", path)
j = d.get("data", d) if isinstance(d, dict) else {}
if j.get("done") or j.get("Done"):
return j
return {"timeout": True}
def scan_after(since):
"""Wait for a stack rescan that started after `since` (the controller rescans on its own every ~2 min)."""
for _ in range(40):
time.sleep(10)
if g(f"docker logs --since {since} felhom-controller 2>&1 | grep -c 'ScanStacks complete'") not in ("", "0"):
return True
return False
def main():
w.say = say
w.login()
try:
ok = w.deploy(APP, SUB, {"HDD_PATH": DRIVE}) and w.wait_app(SUB, "/", tries=90, delay=5)
say("1. grimmory installed and answering:", ok)
appdata = g(f"ls -d {DRIVE}/appdata/grimmory 2>/dev/null | head -1")
say(" its appdata folder:", appdata or "(not found)")
if not appdata:
raise SystemExit("INSTRUMENT: no appdata folder — no verdict")
m = f"{appdata}/data/cc-r1187" # INSIDE the bound folder (appdata/grimmory/data) — the export packs only binds
say(" markers:", g(f"mkdir -p {m} && printf hi > {m}/owned.txt && chown 1000:1001 {m}/owned.txt && chmod 0640 {m}/owned.txt"
f" && ln -sfn owned.txt {m}/link-in && ln -sfn /etc/hostname {m}/link-out && ls -ln {m} | tail -n +2"))
# 2. R-1220 (SKIP_R1220=1: already proven in the first run, `r1187-r1220-9202-run1.txt`)
if os.environ.get("SKIP_R1220"):
say("2. R-1220 skipped (proven in the first run)")
else:
stack = f"/opt/docker/stacks/{APP}"
g(f"cp -p {stack}/app.yaml /root/r1220-app.yaml.save && printf 'deployed: true\\nenv: [broken: yaml: here\\n' > {stack}/app.yaml")
t = int(time.time())
say("2. app.yaml made unreadable; rescan seen:", scan_after(t))
page = w.page("/kept-data")
say(" R-1220: the kept-data page lists grimmory's folder:", appdata in page, "(want False) | WARN lines:",
g(f"docker logs --since {t} felhom-controller 2>&1 | grep -c 'R-1220'"))
g(f"cp -p /root/r1220-app.yaml.save {stack}/app.yaml && rm -f /root/r1220-app.yaml.save")
say(" app.yaml put back, byte check:", g(f"sha256sum {stack}/app.yaml | cut -c1-16"))
scan_after(int(time.time()))
# 3. R-1187 (a) FIFO
g(f"mkfifo {m}/pipe")
code, d = w.ctl("POST", "/api/export/start", {"stack_name": APP, "dest_drive": DRIVE, "stop_app": False})
say("3. export with a FIFO ->", code, str(d)[:160])
if code != "200":
raise SystemExit("INSTRUMENT: the export start was refused — no verdict")
j = job("export")
say(" job:", str(j.get("error") or j.get("Error") or j)[:300])
g(f"rm -f {m}/pipe")
if j.get("timeout"):
raise SystemExit("INSTRUMENT: the export job never ended — no verdict")
# 4. R-1187 (a)(b) round trip
code, d = w.ctl("POST", "/api/export/start", {"stack_name": APP, "dest_drive": DRIVE, "stop_app": False})
say("4. export ->", code, str(d)[:120])
if code != "200":
raise SystemExit("INSTRUMENT: the export start was refused — no verdict")
j = job("export")
say(" job:", str(j)[:300])
fab = g(f"ls -t {DRIVE}/exports/grimmory*.fab 2>/dev/null | head -1")
say(" bundle:", fab)
say(" tar listing of the drive part:", g(f"mkdir -p /root/r1187x && cd /root/r1187x && tar -xzf {fab} data/hdd 2>/dev/null; "
f"for f in data/hdd/*.tar; do tar -tvf $f | grep cc-r1187; done; rm -rf /root/r1187x"))
g(f"rm -rf {m}") # the import must bring it back
ti = int(time.time())
code, d = w.ctl("POST", "/api/export/import", {"path": fab})
say(" import ->", code, str(d)[:160])
if code != "200":
raise SystemExit("INSTRUMENT: the import start was refused — no verdict")
j = job("import")
say(" import job:", str(j)[:300])
say(" after import:", g(f"ls -ln {m} | tail -n +2; stat -c '%n %u:%g %a' {m}/owned.txt; readlink {m}/link-in; "
f"ls -l {m}/link-out 2>&1 | head -1"))
say(" the import's WARN:", g(f"docker logs --since {ti} felhom-controller 2>&1 | grep 'R-1187 b' | cut -c1-220"))
say(" the app answers:", w.wait_app(SUB, "/", tries=60, delay=5))
finally:
sw.remove(APP, keep=False, say=say)
say("5. leftovers:", sw.leftovers(APP, SUB))
say(" bundles of this run removed:", g(f"ls {DRIVE}/exports/grimmory*.fab 2>/dev/null; rm -f {DRIVE}/exports/grimmory*.fab; ls {DRIVE}/exports/ 2>/dev/null | grep -c grimmory"))
if __name__ == "__main__":
main()