views:

72

answers:

1

As a follow-up question to this one, I thought of another approach which builds off of @caf's answer for the case where I want to append to file name and create it if it does not exist.

Here is what I came up with:

  1. Create a temporary directory with mode 0700 in a system temporary directory on the same filesystem as file name.
  2. Open file name for reading only and O_CREAT. The OS may follow name if it is a symbolic link.

    Use mkstemp to create a temporary file in the temporary directory and attempt to rename the temporary file that was created by mkstemp to file name.

    Open file name for reading only and O_CREAT | O_EXCL.
  3. Iteratively attempt to make a hard link to name at a temporary name within the temporary directory. If ever the link call fails due to an error other than "the link target exists" (errno EEXIST), then exit. (Maybe someone has come along and removed the file at name, who knows?)
  4. Use lstat on temp_name (the hard link). If S_ISLNK(lst.st_mode), then exit.
  5. open temp_name for writing & append (O_WRONLY | O_APPEND).
  6. Write everything out. Close the file descriptor.
  7. unlink the hard link.
  8. Remove the temporary directory.

(All of this, by the way, is for an open source project that I am working on. You can view the source of my implementation of this approach here.)

Is this procedure safe against symbolic link attacks? For example, is it possible for a malicious process to ensure that the inode for name represents a regular file for the duration of the lstat check, then make the inode a symbolic link with the temp_name hard link now pointing to the new, symbolic link?

I am assuming that a malicious process cannot affect temp_name.

EDIT: link does not overwrite the target so creating a "placeholder" temporary file is not what I wanted to do. I have since updated my code and have updated the steps above.

EDIT2: I am now using an alternate procedure for step 2 to create file name if it does not exist which I do not think is susceptible to this problem.

EDIT3: Even better than renaming a temporary, empty, regular file to name, which also has the effect of unlinking name, then renaming, I can open the file O_RDONLY | O_CREAT | O_EXCL.

The POSIX standard for open states:

If O_EXCL and O_CREAT are set, and path names a symbolic link, open() shall fail and set errno to EEXIST, regardless of the contents of the symbolic link.

A: 

Well, there is one problem with step 2 ("Open file name for reading only and O_CREAT. The OS may follow name if it is a symbolic link.") which, if fully exploitable, could allow an unprivileged process to essentially touch any path in the filesystem. The consequences of this include forcing a disk check on the next reboot (by touching /forcefsck), as well as other, more destructive things. For example, in upgrading from Debian Lenny to Squeeze, both dbus and the kernel image have to be upgraded at the same time because each of the corresponding packages depends on the other (the new dbus doesn't work with the old kernel and the old dbus doesn't work with the new kernel). The way that the admin gets around this circular dependency is by touching a specific path, which informs the new dbus package that the kernel image is going to be upgraded before the next reboot. If, however, a malicious process manages to touch that path before a first upgrade & reboot during the upgrade from Lenny to Squeeze, then the system may not be bootable. The first upgrade, if I recall correctly, installs the new dbus, but you have to explicitly upgrade again for the new kernel image to install. One upgrade followed by a reboot might brick the system.

Looking through the source of GNU Coreutils' touch, it appears that they only set the file timestamps, but do not create the file if it does not exist, when touch is passed the --no-dereference option. They do this with a utility function, lutimens from gnulib, which wraps Linux's utimensat, allowing the timestamps of the symbolic link file itself, if that is the case, to be updated when Linux >= 2.6.22.

Daniel Trebbien