By Jake Edge
September 19, 2007
"Insecure tmpfile creation" and "arbitrary file overwrite using symlinks"
(and other similar names)
are commonly seen vulnerabilities listed in the LWN daily security update.
The problems are related in many ways and can be very serious, with damage
ranging from corrupted files to full takeover of a vulnerable system. By
and large, they are easy to avoid, so it is disheartening to see them
crop up time and time again.
Typically, these kinds of attacks exploit race conditions, where
correct functioning depends, inappropriately, on the order of operations
between two or more processes in the system. The classic example is a
program that checks for the existence of a file, in a directory writable by
others, before opening it, to avoid
overwriting an existing file. An attacker can arrange, usually through
repeated attempts, to create the file just after the existence check and
before the open. The vulnerable program's author made an incorrect
assumption about what else could be going on in the system, which allows
the attacker's program to race with it.
At first blush, it doesn't seem particularly harmful for a program to
mistakenly overwrite the attacker's carefully inserted file. After all,
the victimized program will probably just truncate the file before writing
whatever data it had planned. This is where symbolic links (symlinks) come
into play.
Symlinks are just an alias for an entry in the filesystem which can be
created by anyone with write access to the directory where the symlink will
reside. The target of the symlink can be most any string, normally they
are the path to the target of the alias, but there is no requirement that
the target exist. More importantly, there is no check that the process
which creates the symlink has access rights to the target. When operations
are performed on a symlink, the filesystem layer follows the pointer to the
actual file, checking the permissions on the inode of that file.
What that means is that any random Linux user can create a symbolic link
from /tmp/foo to /etc/passwd, though they will not be
able to write to the former, because the permissions on the latter do not
allow it. But, privileged programs, either setuid or those
run as root, do have the proper permission. If they open and write
to /tmp/foo, they have just corrupted the password file.
Vulnerable programs aren't usually quite that simple, but they do use
predictable filenames or patterns. If an attacker knows that the
administrator often runs a vulnerable program or script, which writes to
/tmp/fooNNNNN where NNNNN is a random number, they can run a
program which continuously makes links from those filenames to some file
they wish to corrupt. If their program happens to generate the right link
at the right time, the corruption succeeds. Normally, a program that
creates a temporary file will delete it when it is done executing, but for
symlinks that just removes the link, leaving the file that was pointed to
with the whatever contents were written.
A setuid program provides even more opportunities for exploitation
as the attacker can run it many times, under his control, while running
other programs that create the symlinks. If the attacker can control, via
input to the program, what gets written, the problem becomes worse
still, quite possibly leading to complete compromise of the system. The
scenarios for abusing this kind of hole are endless.
It doesn't necessarily have to be a temporary file that gets exploited, any
file that gets opened in a directory that is writable by others can
potentially be symlinked elsewhere. This can lead to unexpected results
for reads, or corruption of unexpected files for writes. These types of
vulnerabilities can be used when a regular user login (or system user like
'apache') is compromised, by an exploit or password disclosure, to further
compromise the system. Some may be difficult to exploit reliably, but the
consequences are such that it may be worth the effort.
As always, David Wheeler's Secure
Programming for Linux and Unix HOWTO is an excellent resource for
avoiding these kinds of problems. The basic idea is to avoid the race by
using atomic filesystem operations or, for tmpfiles, mkstemp().
When creating files, ensure that the open() call uses O_CREAT
| O_EXCL which will fail if the file already exists. Another
important note is that a program should not close and reopen files that
live in shared directories, instead they should be left open until the
program is done with them.
These kind of problems have been around for twenty years or more, but still
keep cropping up, which is a good indication that many programmers aren't
following secure coding practices. Whenever one is writing code that is
opening files, which is, after all, a very common operation, some
consideration should be given to symlink/tmpfile vulnerabilities. With
some perseverance, these kinds of vulnerabilities could become a thing of
the past.
Comments (11 posted)