[go: up one dir, main page]

|
|
Log in / Subscribe / Register

The trouble with symbolic links

The trouble with symbolic links

Posted Jul 7, 2022 22:51 UTC (Thu) by zaitseff (subscriber, #851)
Parent article: The trouble with symbolic links

As Chris points out, the problem is not so much symlinks as Time Of Check to Time of Use (TOCTOU). Take, for example, a problem I've faced writing a simple C program:

  1. I want my program to read and store user data/configuration.
  2. The traditional place for that data (over the thirty years of the program so far) has been ~/.PROGRAM.
  3. Modern XDG systems use ~/.local/share for such purposes (actually, possibly using XDG_DATA_HOME from the environment).
  4. I'd like my program to continue to use ~/.PROGRAM if it already exists, otherwise use ${XDG_DATA_HOME}/PROGRAM if $XDG_DATA_HOME is set (creating the directory, and any parents as required, if it doesn't exist), otherwise use ~/.local/share/PROGRAM (creating the directory, and any parents as required, if it doesn't exist).

The naïve way would be to code up something like the following pseudo-C for determining which directory to use:

    const char *traditional_path = "/home/john/.PROGRAM";    /* Just for testing */
    const char *xdg_default_path = "/home/john/.local/share/PROGRAM";
    const char *xdg_data_home = getenv("XDG_DATA_HOME");
    char *xdg_path;

    if (stat(traditional_path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) {
        return traditional_path;
    } else if (xdg_data_home != NULL && *xdg_data_home != '\0' && *xdg_data_home == '/') {
        /* Use xdg_data_home + "/PROGRAM"; */
        xdg_path = strcat_malloc(xdg_data_home, "/PROGRAM");
        return xdg_path;
    } else {
        return xdg_default_path;
    }

And there you have it: a TOCTOU error (check for /home/john/.trader before creating the final target path using mkdir() later). No symlinks need be involved.

Now you can merrily post your solution to the above problem! :-)


to post comments

The trouble with symbolic links

Posted Jul 7, 2022 23:04 UTC (Thu) by Paf (subscriber, #91811) [Link]

For a preferences file, I wouldn’t solve it. ;)

Now for something else which was security critical …………….. eek

The trouble with symbolic links

Posted Jul 7, 2022 23:06 UTC (Thu) by zaitseff (subscriber, #851) [Link] (4 responses)

And I should mention that, as an additional wrinkle/challenge, any solution must be portable to the latest versions of FreeBSD, OpenBSD, NetBSD, macOS, Solaris, ... — any POSIX environment.

The trouble with symbolic links

Posted Jul 7, 2022 23:57 UTC (Thu) by mpr22 (subscriber, #60784) [Link] (3 responses)

It's worth noting that openat(), mkdirat(), and fdopendir() were adopted in POSIX.1-2008.

The trouble with symbolic links

Posted Jul 8, 2022 0:46 UTC (Fri) by zaitseff (subscriber, #851) [Link] (2 responses)

Indeed. However, I can't quite see how to use these: something I didn't mention is that mkdir() is only run if writing a data file: if no data file is saved (a user-initiated operation), no directory is created. So I'm not sure how to avoid the TOCTOU problem in such a situation.

The trouble with symbolic links

Posted Jul 8, 2022 1:20 UTC (Fri) by mpr22 (subscriber, #60784) [Link] (1 responses)

  1. Any time you access a file without knowing which data directory to use, search for your preferred data directory with open() and fstat().
  2. If you find one of your desired directories, retain the file descriptor as well as the pathname, and use openat() with the file descriptor for all further activities relating to your preferred data directory.
  3. If you don't find any of the desired data directories, then don't retain the result at all; next time you try to access a file, start fresh from step 1.

Safely creating the directory if it doesn't exist is still kind of a pain in the neck, because you can't combine the operations of "make a directory" and "open a file descriptor pointing to the directory" into a single indivisible syscall.

The trouble with symbolic links

Posted Jul 8, 2022 4:16 UTC (Fri) by magfr (subscriber, #16052) [Link]

And neither the cd nor the mkdir shell builtin provides a flag to create a directory and chdir to it in one operation.
Probably for that exact reason but I have wished for it at times.

The trouble with symbolic links

Posted Jul 8, 2022 2:39 UTC (Fri) by k8to (guest, #15413) [Link]

I mean the usual solution for something along these lines is not to pass a computed path for later opening but rather open the file and pass that.

But if you want to have a problem have a stable dir it settles on and uses consistently, you've kind of designed a TOCTOU problem into the concept of your program or library. At least with classic system calls.

Of course *at calls allow one to do precisely what I implied. Open the dir, and keep it around for use. With pipe magic you can even eliminate TOCTOU across multiple processes.


Copyright © 2026, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds