// toctou.c: la condición de carrera time-of-check to time-of-use. La versión
// vulnerable comprueba el permiso con access() y luego abre con open(): son dos
// resoluciones distintas de la misma ruta. Si entre una y otra cambia un enlace
// simbólico, se termina usando un archivo distinto del que se autorizó.
//
// No hace falta setuid real para verlo: el programa compara el inodo que vio el
// chequeo con el que abrió el uso. Si no coinciden, la carrera se ganó.
//
//   ./toctou vuln   RUTA   -> con chequeo y uso separados (vulnerable)
//   ./toctou seguro RUTA   -> baja el privilegio y abre una sola vez (correcto)
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>

static int vulnerable(const char *path) {
    if (access(path, R_OK) != 0)             // CHEQUEO: ¿puede leerlo el usuario real?
        return -1;
    struct stat cs;
    if (stat(path, &cs) != 0)
        return -1;                           // inodo que vio el chequeo
    usleep(200);                             // ventana: el programa "trabaja" entremedio
    int fd = open(path, O_RDONLY);           // USO: vuelve a resolver la ruta
    if (fd < 0)
        return -1;
    struct stat us;
    fstat(fd, &us);                          // inodo que de verdad se abrió
    int raced = (cs.st_ino != us.st_ino);
    if (raced)
        printf("  chequeo inodo=%lu  uso inodo=%lu  <-- TOCTOU\n",
               (unsigned long)cs.st_ino, (unsigned long)us.st_ino);
    close(fd);
    return raced;
}

// Segura: en vez de chequear con access() y luego abrir (dos resoluciones), se
// baja el euid al uid real y se deja que open() haga el chequeo con la identidad
// del usuario, en una sola syscall: no hay ventana. O_NOFOLLOW evita además que
// un enlace simbólico en el último componente cambie el destino.
static int seguro(const char *path) {
    uid_t e = geteuid();
    if (seteuid(getuid()) != 0)              // actuar como el usuario real
        return -1;
    int fd = open(path, O_RDONLY | O_NOFOLLOW);  // el kernel chequea con el uid real
    int err = errno;
    if (seteuid(e) != 0) {                   // recuperar el privilegio
        if (fd >= 0) close(fd);
        return -1;
    }
    if (fd < 0) {
        errno = err;
        printf("  open rechazado (sin permiso del usuario real o enlace): no hay carrera\n");
        return -1;
    }
    struct stat us;
    fstat(fd, &us);                          // todo lo demás se decide sobre este fd
    usleep(200);
    close(fd);
    return 0;
}

int main(int argc, char **argv) {
    if (argc != 3) {
        fprintf(stderr, "uso: %s {vuln|seguro} RUTA\n", argv[0]);
        return 2;
    }
    return strcmp(argv[1], "seguro") == 0 ? seguro(argv[2]) : vulnerable(argv[2]);
}
