Exploiting symlinks and tmpfiles
"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.
| Index entries for this article | |
|---|---|
| Security | Race conditions |
| Security | Vulnerabilities/Temporary files |