I commented on Lobsters that /tmp
is usually a bad idea,
which caused some surprise. I suppose /tmp
security bugs were common
in the 1990s when I was learning Unix, but they are pretty rare now so
I can see why less grizzled hackers might not be familiar with the
problems.
I guess that’s some kind of success, but sadly the fixes have left
behind a lot of scar tissue because they didn’t address the underlying
problem: /tmp
should not exist.
It’s a bad idea because it’s shared global mutable state that crosses security boundaries. There’s a ton of complexity at all levels of unix (filesystems, kernel APIs, libc, shell, admin scripts) that only exists as a workaround for the dangers caused by making
/tmp
shared.
sticky bit
I think the earliest and lowest-level workaround is the sticky bit.
The sticky bit is mode bit 01000 in unix file permissions. It
is printed as the t
instead of x
in rwt
.
drwxrwxrwt 5 root wheel 160 Oct 22 10:22 /tmp/
Originally in the 1970s the sticky bit was invented to speed up frequently-used programs such as the shell: the sticky bit indicated to the kernel that the executable file should stick in core. This functionality was made obsolete in the 1980s by filesystem page caches.
The sticky bit was re-used to mean something else on directories, to
fix a security problem with /tmp
.
On unix, permission to delete a file depends on write access to the directory containing the file, independent of the file’s own permissions.
So if a directory (such as /tmp
) is world-writable, anyone can delete
any file in it.
This means /tmp
was vulnerable to all sorts of accidental or malicious
trouble caused by users deleting each others’ files.
tmp stupidity in C
POSIX used to provide 5 (five!) ways to create a temporary file, three
of which (most of them!) you must never use and which have
subsequently been deprecated – except the do-not-use footgun tmpnam
which is still part of the C standard. Two more safe functions have
subsequently been added to support more complicated situations.
There are so many dangerous API design problems in those old functions! I’m not going to waste time roasting them in detail because I can cover the important points by discussing …
mkstemp and mkdtemp
The purpose of all these functions is to create a temporary file (or directory) that doesn’t collide with other concurrent activity.
When you are creating a file in /tmp
the risk is that another
malicious user can make you open a file under their control. To avoid
this vulnerability, you (or rather mkstemp
) must:
-
Generate an unpredictable filename.
It isn’t enough for the filename to be unique, it must contain sufficient secure randomness that an adversary can’t win a race and interfere.
-
Open the file with
O_CREAT
|O_EXCL
.This ensures the file has not already been created by another friendly or malicious process, without any time-of-check / time-of-use vulnerabilities. And it ensures that the file isn’t a symlink pointing somewhere an attacker wants you to accidentally corrupt.
-
Retry if open fails with
EEXIST
.Together with the randomness in the name, this avoids denial-of-service vulnerabilites.
This is intricate and not entirely obvious, as you can tell from all the previous failed attempts at functions that didn’t create temporary files safely.
mktemp in shell
The mktemp(1) command is a wrapper around mkstemp(3). (Slightly confusingly it isn’t a wrapper around mktemp(3) because that would be unsafe.) It was introduced by OpenBSD and it’s widely supported though it isn’t yet in POSIX. Its manual page includes a nice rationale:
The
mktemp
utility is provided to allow shell scripts to safely use temporary files. Traditionally, many shell scripts take the name of the program with the pid as a suffix and use that as a temporary file name. This kind of naming scheme is predictable and the race condition it creates is easy for an attacker to win. A safer, though still inferior, approach is to make a temporary directory using the same naming scheme. While this does allow one to guarantee that a temporary file will not be subverted, it still allows a simple denial of service attack. For these reasons it is suggested thatmktemp
be used instead.
Although shell scripts can’t avoid using the temporary file by name, mktemp(1) is safe because it creates the file securely and an attacker can’t interfere with it after that point. It’s OK to re-open a file that you know is yours.
tmp cleanup
The last item on my list of regrets is “admin scripts”, an oblique
reference to /tmp
cleanup jobs.
By its nature /tmp
tends to accumulate junk, so it was common to have
cron jobs that would delete old files. (Less common now that computers
are much bigger.)
These scripts tended to have problems with time-of-check / time-of-use
vulnerabilities, careless handling of symlinks, and pulling the rug
out from under long-running programs that foolishly used /tmp
. (Lots
more reasons these scripts are now less common!)
tmp remedy
So I’ve spent dozens of paragraphs outlining bugs and complications
related to /tmp
. All of them could have been avoided if /tmp
did not
exist, and everything would have been simpler as a result.
So where should temporary files have gone, if not in /tmp
?
There should have been per-user temporary directories in different per-user locations. In fact, on some modern systems there are per-user temporary directories! But this solution came several decades too late.
If you have per-user $TMPDIR
then temporary filenames can safely be
created using the simple mechanisms described in the mktemp(1)
rationale or used by the old deprecated C functions. There’s no need
to defend against an attacker who doesn’t have sufficient access to
mount an attack! There’s no need for sticky directories because there
aren’t any world-writable directories.
There’s a minor wrinkle that setuid programs would have to be more careful about how they create temporary files, but setuid programs have to be more careful about everything.
tmp rationale
So why wasn’t per-user $TMPDIR
a thing back in the day?
Probably the main reason was path-dependence: /tmp
was created and in
wide use before its problems became apparent, at which point it was
difficult to deprecate.
There are reasons 1990-ish-you didn’t want $TMPDIR
to be in your
home directory:
-
$HOME
might be on NFS so a local$TMPDIR
might be faster -
$TMPDIR
can be a way to get workspace beyond your disk quota -
$HOME
might be on a filesystem that doesn’t support named pipes, so your X11 and SSH agent sockets live in$TMPDIR
instead
So some finesse is necessary when choosing where to put $TMPDIR
, but
that should not have been an insurmountable problem.
more tmp
edited to add…
Of course, we can’t go back in time to get rid of /tmp
, so efforts
continue to make it safer by making things more complicated.
For example, on Linux there are a couple of features that use
filesystem namespaces to hide the global /tmp
with a user-owned
/tmp
overlay: pam_namespace and systemd PrivateTmp
.
less tmp
I don’t think there’s any way to get rid of the complications I’ve
described in this post, as long as a /tmp
directory exists – which
will be forever. Unix programs have to assume /tmp
is unsafe, so the
security mechanisms in mkstemp()
have to stay. We can’t remove
sticky bit support from the kernel as long as there’s a global /tmp
,
even if it is sometimes hidden by an overlay.
Personally, when writing software I avoid using /tmp
. I create a
working directory somewhere that isn’t world writable and use that
instead.