old-www/HOWTO/Secure-Programs-HOWTO/avoid-race.html

1018 lines
37 KiB
HTML

<HTML
><HEAD
><TITLE
>Avoid Race Conditions</TITLE
><META
NAME="GENERATOR"
CONTENT="Modular DocBook HTML Stylesheet Version 1.7"><LINK
REL="HOME"
TITLE="Secure Programming for Linux and Unix HOWTO"
HREF="index.html"><LINK
REL="UP"
TITLE="Structure Program Internals and Approach"
HREF="internals.html"><LINK
REL="PREVIOUS"
TITLE="Fail Safe"
HREF="fail-safe.html"><LINK
REL="NEXT"
TITLE="Trust Only Trustworthy Channels"
HREF="trustworthy-channels.html"></HEAD
><BODY
CLASS="SECT1"
BGCOLOR="#FFFFFF"
TEXT="#000000"
LINK="#0000FF"
VLINK="#840084"
ALINK="#0000FF"
><DIV
CLASS="NAVHEADER"
><TABLE
SUMMARY="Header navigation table"
WIDTH="100%"
BORDER="0"
CELLPADDING="0"
CELLSPACING="0"
><TR
><TH
COLSPAN="3"
ALIGN="center"
>Secure Programming for Linux and Unix HOWTO</TH
></TR
><TR
><TD
WIDTH="10%"
ALIGN="left"
VALIGN="bottom"
><A
HREF="fail-safe.html"
ACCESSKEY="P"
>Prev</A
></TD
><TD
WIDTH="80%"
ALIGN="center"
VALIGN="bottom"
>Chapter 7. Structure Program Internals and Approach</TD
><TD
WIDTH="10%"
ALIGN="right"
VALIGN="bottom"
><A
HREF="trustworthy-channels.html"
ACCESSKEY="N"
>Next</A
></TD
></TR
></TABLE
><HR
ALIGN="LEFT"
WIDTH="100%"></DIV
><DIV
CLASS="SECT1"
><H1
CLASS="SECT1"
><A
NAME="AVOID-RACE"
></A
>7.10. Avoid Race Conditions</H1
><P
>A ``race condition'' can be defined as
``Anomalous behavior due to unexpected critical dependence
on the relative timing of events''
[FOLDOC].
Race conditions generally involve one or more processes
accessing a shared resource (such a file or variable), where this
multiple access has not been properly controlled.</P
><P
>In general, processes do not execute atomically;
another process may interrupt it between essentially any two instructions.
If a secure program's process is not prepared for these interruptions,
another process may be able to interfere with the secure program's process.
Any pair of operations in a secure program must still work correctly
if arbitrary amounts of another process's code is executed between them. </P
><P
>Race condition problems can be notionally divided into two categories:
<P
></P
><UL
><LI
><P
>Interference caused by untrusted processes.
Some security taxonomies call this problem a
``sequence'' or ``non-atomic'' condition.
These are conditions caused by processes running other, different programs,
which ``slip in'' other actions between steps of the secure program.
These other programs might be invoked by an attacker specifically
to cause the problem.
This book will call these sequencing problems.</P
></LI
><LI
><P
>Interference caused by trusted processes (from the secure program's
point of view).
Some taxonomies call these deadlock, livelock, or locking failure conditions.
These are conditions caused by processes running the ``same'' program.
Since these different processes may have the ``same'' privileges, if
not properly controlled they may be able to interfere with each other in
a way other programs can't.
Sometimes this kind of interference can be exploited.
This book will call these locking problems.</P
></LI
></UL
></P
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="NON-ATOMIC"
></A
>7.10.1. Sequencing (Non-Atomic) Problems</H2
><P
>In general,
you must check your code for any pair of operations that might fail if
arbitrary code is executed between them. </P
><P
>Note that loading and saving a shared variable are usually implemented
as separate operations and are not atomic.
This means that an ``increment variable'' operation is usually converted into
loading, incrementing, and saving operation, so if the variable memory
is shared the other process may interfere with the incrementing.</P
><P
>Secure programs must determine if a request should be granted, and if
so, act on that request.
There must be no way for an untrusted user to change anything used in
this determination before the program acts on it.
This kind of race condition is sometimes termed a
``time of check - time of use'' (TOCTOU) race condition.</P
><DIV
CLASS="SECT3"
><H3
CLASS="SECT3"
><A
NAME="ATOMIC-FILESYSTEM"
></A
>7.10.1.1. Atomic Actions in the Filesystem</H3
><P
>The problem of failing to perform atomic actions
repeatedly comes up in the filesystem.
In general, the filesystem is a shared resource used by many programs,
and some programs may interfere with its use by other programs.
Secure programs should generally avoid using access(2) to determine
if a request should be granted, followed later by open(2), because users
may be able to move files around between these calls, possibly creating
symbolic links or files of their own choosing instead.
A secure program should instead set its effective id or filesystem id,
then make the open call directly.
It's possible to use access(2) securely, but only when a user cannot affect
the file or any directory along its path from the filesystem root.</P
><P
>When creating a file, you should
open it using the modes O_CREAT | O_EXCL and grant only
very narrow permissions (only to the current user);
you'll also need to prepare for having the open fail.
If you need to be able to open the file (e.g,. to prevent a
denial-of-service), you'll need to repetitively
(1) create a ``random'' filename, (2) open the file as noted,
and (3) stop repeating when the open succeeds.</P
><P
>Ordinary programs can become security weaknesses if they
don't create files properly.
For example, the ``joe'' text editor had a weakness called the
``DEADJOE'' symlink vulnerability.
When joe was exited in a nonstandard way (such as a system crash, closing an
xterm, or a network connection going down), joe would unconditionally append
its open buffers to the file "DEADJOE".
This could be exploited by the
creation of DEADJOE symlinks in directories where root would normally use joe.
In this way, joe could be used to append garbage to
potentially-sensitive files, resulting in a denial of service and/or
unintentional access.</P
><P
>As another example, when performing a series of operations on a file's
meta-information (such as changing its owner, stat-ing the file, or
changing its permission bits), first open the file and then use the
operations on open files.
This means use the fchown( ), fstat( ), or fchmod( ) system calls,
instead of the functions taking filenames
such as chown(), chgrp(), and chmod().
Doing so will prevent the file from being
replaced while your program is running (a possible race condition).
For example, if you close a file and then use chmod()
to change its permissions,
an attacker may be able to move or remove the file between those
two steps and create a symbolic link to another file
(say /etc/passwd).
Other interesting files include /dev/zero, which can
provide an infinitely-long data stream of input to a program; if an
attacker can ``switch'' the file midstream, the results can be dangerous.</P
><P
>But even this gets complicated - when creating files, you must give
them as a minimal set of rights as possible, and then change the
rights to be more expansive if you desire.
Generally, this means you need to use umask and/or open's parameters to
limit initial access to just the user and user group.
For example, if you create a file that is initially world-readable, then
try to turn off the ``world readable'' bit, an attacker could try to
open the file while the permission bits said this was okay.
On most Unix-like systems, permissions are only checked on open, so
this would result in an attacker having more privileges than intended.</P
><P
>In general, if multiple users can write to a directory in a Unix-like
system, you'd better have the ``sticky'' bit set on that directory,
and sticky directories had better be implemented.
It's much better to completely avoid the problem, however, and create
directories that only a trusted special process can access
(and then implement that carefully).
The traditional Unix temporary directories (/tmp and /var/tmp) are usually
implemented as ``sticky'' directories, and all sorts of security problems
can still surface, as we'll see next.</P
></DIV
><DIV
CLASS="SECT3"
><H3
CLASS="SECT3"
><A
NAME="TEMPORARY-FILES"
></A
>7.10.1.2. Temporary Files</H3
><P
>This issue of correctly performing atomic operations
particularly comes up when creating temporary files.
Temporary files in Unix-like systems are traditionally
created in the /tmp or /var/tmp directories,
which are shared by all users.
A common trick by attackers is to create symbolic links in the
temporary directory to some other file (e.g., /etc/passwd)
while your secure program is running.
The attacker's goal is to create
a situation where the secure program determines that
a given filename doesn't exist, the attacker then creates the symbolic
link to another file, and then the secure program performs some operation
(but now it actually opened an unintended file).
Often important files can be clobbered or modified this way.
There are many variations to this attack, such as creating normal files,
all based on the
idea that the attacker can create (or sometimes
otherwise access) file system objects
in the same directory used by the secure program for temporary files.</P
><P
>Michal Zalewski exposed in 2002 another serious problem with
temporary directories involving automatic cleaning of temporary directories.
For more information, see his
posting to Bugtraq dated December 20, 2002,
(subject "[RAZOR] Problems with mkstemp()").
Basically, Zalewski notes that
it's a common practice to have a program automatically sweep
temporary directories like /tmp and /var/tmp and remove "old" files
that have not been accessed for a while (e.g., several days).
Such programs are sometimes called "tmp cleaners" (pronounced "temp cleaners").
Possibly the most common tmp cleaner is "tmpwatch" by
Erik Troan and Preston Brown of Red Hat Software;
another common one is 'stmpclean' by Stanislav Shalunov;
many administrators roll their own as well.
Unfortunately, the existance of tmp cleaners creates an opportunity
for new security-critical race conditions;
an attacker may be able to arrange things so that the tmp cleaner
interferes with the secure program.
For example, an attacker could create an "old" file, arrange for
the tmp cleaner to plan to delete the file, delete the file himself,
and run a secure program that creates the same file - now the tmp cleaner
will delete the secure program's file!
Or, imagine that a secure program can have long delays after using the file
(e.g., a setuid program stopped with SIGSTOP and
resumed after many days with SIGCONT, or simply intentionally creating
a lot of work).
If the temporary file isn't used for long enough,
its temporary files are likely to be
removed by the tmp cleaner.</P
><P
>The general problem when creating files in these shared directories is that
you must guarantee that the filename you plan to use doesn't already
exist at time of creation, and atomically create the file.
Checking ``before'' you create the file doesn't work, because after the check
occurs, but before creation, another process can create that file with
that filename.
Using an ``unpredictable'' or ``unique'' filename doesn't work in
general, because another process can often repeatedly guess until it succeeds.
Once you create the file atomically, you must alway use the returned
file descriptor
(or file stream, if created from the file descriptor using routines
like fdopen()).
You must never re-open the file, or use any operations that use the
filename as a parameter - always use the file descriptor or
associated stream.
Otherwise, the tmpwatch race issues noted above will cause problems.
You can't even create the file, close it, and re-open it, even if the
permissions limit who can open it.
Note that comparing the descriptor and a reopened file to verify inode
numbers, creation times or file ownership is not sufficient - please refer
to "Symlinks and Cryogenic Sleep" by Olaf Kirch.</P
><P
>Fundamentally, to create a temporary file in a shared (sticky) directory,
you must repetitively: (1) create a ``random'' filename, (2) open it using
O_CREAT | O_EXCL and very narrow permissions (which atomically creates the
file and fails if it's not created),
and (3) stop repeating when the open succeeds.</P
><P
>According to the 1997 ``Single Unix Specification'', the preferred
method for creating an arbitrary temporary file
(using the C interface) is tmpfile(3).
The tmpfile(3) function creates a temporary file
and opens a corresponding stream, returning that stream (or NULL if it didn't).
Unfortunately, the specification doesn't make any
guarantees that the file will be created securely.
In earlier versions of this book, I stated that I was concerned because
I could not assure myself that all implementations do this securely.
I've since found that older System V systems
have an insecure implementation of tmpfile(3) (as well as insecure
implementations of tmpnam(3) and tempnam(3)), so on at least some systems
it's absolutely useless.
Library implementations of tmpfile(3) should securely create such files,
of course, but users don't always realize that their system libraries
have this security flaw, and sometimes they can't do anything about it.</P
><P
>Kris Kennaway recommends using mkstemp(3) for making temporary files
in general.
His rationale is that you should use well-known library functions to perform
this task instead of rolling your own functions, and that this function
has well-known semantics.
This is certainly a reasonable position.
I would add that, if you use mkstemp(3), be sure to use umask(2) to limit
the resulting temporary file permissions to only the owner.
This is because
some implementations of mkstemp(3) (basically older ones) make such
files readable and writable by all,
creating a condition in which an attacker can read or
write private data in this directory.
A minor nuisance is that mkstemp(3) doesn't directly support the
environment variables TMP or TMPDIR (as discussed below), so
if you want to support them you have to add code to do so.
Here's a program in C that demonstrates how to use mkstemp(3)
for this purpose, both directly and when adding support for TMP and TMPDIR:
<TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><FONT
COLOR="#000000"
><PRE
CLASS="PROGRAMLISTING"
>#include &#60;stdio.h&#62;
#include &#60;stdlib.h&#62;
#include &#60;sys/types.h&#62;
#include &#60;sys/stat.h&#62;
void failure(msg) {
fprintf(stderr, "%s\n", msg);
exit(1);
}
/*
* Given a "pattern" for a temporary filename
* (starting with the directory location and ending in XXXXXX),
* create the file and return it.
* This routines unlinks the file, so normally it won't appear in
* a directory listing.
* The pattern will be changed to show the final filename.
*/
FILE *create_tempfile(char *temp_filename_pattern)
{
int temp_fd;
mode_t old_mode;
FILE *temp_file;
old_mode = umask(077); /* Create file with restrictive permissions */
temp_fd = mkstemp(temp_filename_pattern);
(void) umask(old_mode);
if (temp_fd == -1) {
failure("Couldn't open temporary file");
}
if (!(temp_file = fdopen(temp_fd, "w+b"))) {
failure("Couldn't create temporary file's file descriptor");
}
if (unlink(temp_filename_pattern) == -1) {
failure("Couldn't unlink temporary file");
}
return temp_file;
}
/*
* Given a "tag" (a relative filename ending in XXXXXX),
* create a temporary file using the tag. The file will be created
* in the directory specified in the environment variables
* TMPDIR or TMP, if defined and we aren't setuid/setgid, otherwise
* it will be created in /tmp. Note that root (and su'd to root)
* _will_ use TMPDIR or TMP, if defined.
*
*/
FILE *smart_create_tempfile(char *tag)
{
char *tmpdir = NULL;
char *pattern;
FILE *result;
if ((getuid()==geteuid()) &#38;&#38; (getgid()==getegid())) {
if (! ((tmpdir=getenv("TMPDIR")))) {
tmpdir=getenv("TMP");
}
}
if (!tmpdir) {tmpdir = "/tmp";}
pattern = malloc(strlen(tmpdir)+strlen(tag)+2);
if (!pattern) {
failure("Could not malloc tempfile pattern");
}
strcpy(pattern, tmpdir);
strcat(pattern, "/");
strcat(pattern, tag);
result = create_tempfile(pattern);
free(pattern);
return result;
}
main() {
int c;
FILE *demo_temp_file1;
FILE *demo_temp_file2;
char demo_temp_filename1[] = "/tmp/demoXXXXXX";
char demo_temp_filename2[] = "second-demoXXXXXX";
demo_temp_file1 = create_tempfile(demo_temp_filename1);
demo_temp_file2 = smart_create_tempfile(demo_temp_filename2);
fprintf(demo_temp_file2, "This is a test.\n");
printf("Printing temporary file contents:\n");
rewind(demo_temp_file2);
while ( (c=fgetc(demo_temp_file2)) != EOF) {
putchar(c);
}
putchar('\n');
printf("Exiting; you'll notice that there are no temporary files on exit.\n");
}</PRE
></FONT
></TD
></TR
></TABLE
></P
><P
>Kennaway states that if you can't use mkstemp(3),
then make yourself a directory using mkdtemp(3), which is protected
from the outside world.
However, as Michal Zalewski notes, this is a bad idea if there are
tmp cleaners in use; instead, use a directory inside the user's HOME.
Finally, if you really have to use the insecure mktemp(3), use lots of
X's - he suggests 10 (if your libc allows it) so that the filename can't
easily be guessed (using only 6 X's means that 5 are taken up by the
PID, leaving only one random character and allowing an attacker to
mount an easy race condition).
Note that this is fundamentally insecure, so you should normally not do this.
I add that you should avoid tmpnam(3) as well -
some of its uses aren't reliable when threads are present, and
it doesn't guarantee that it will work correctly after
TMP_MAX uses (yet most practical uses must be inside a loop).</P
><P
>In general, you should avoid using the insecure functions
such as mktemp(3) or tmpnam(3), unless you take specific measures to
counter their insecurities or test for a secure library implementation
as part of your installation routines.
If you ever want to make a file in /tmp or a world-writable directory
(or group-writable, if you don't trust the group) and don't want to
use mk*temp() (e.g. you intend for the file to be predictably named),
then <EM
>always</EM
> use the O_CREAT and O_EXCL flags to
open() and <EM
>check the return value</EM
>.
If you fail the open() call, then recover gracefully (e.g. exit).</P
><P
>The GNOME programming guidelines recommend the following C code when
creating filesystem objects in shared (temporary) directories
to securely open temporary files [Quintero 2000]:
<TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><FONT
COLOR="#000000"
><PRE
CLASS="PROGRAMLISTING"
> char *filename;
int fd;
do {
filename = tempnam (NULL, "foo");
fd = open (filename, O_CREAT | O_EXCL | O_TRUNC | O_RDWR, 0600);
free (filename);
} while (fd == -1);</PRE
></FONT
></TD
></TR
></TABLE
>
Note that, although the insecure function tempnam(3) is being used, it
is wrapped inside a loop using O_CREAT and O_EXCL to counteract its
security weaknesses, so this use is okay.
Note that you need to free() the filename.
You should close() and unlink() the file after you are done.
If you want to use the Standard C I/O library,
you can use fdopen() with mode "w+b"
to transform the file descriptor into a FILE *.
Note that this approach won't work over
NFS version 2 (v2) systems, because older
NFS doesn't correctly support O_EXCL.
Note that one minor disadvantage to this approach is that, since
tempnam can be used insecurely, various compilers and security scanners
may give you spurious warnings about its use.
This isn't a problem with mkstemp(3).</P
><P
>If you need a temporary file in a shell script, you're probably
best off using pipes, using a local directory (e.g., something inside the
user's home directory), or in some cases using the current directory.
That way, there's no sharing unless the user permits it.
If you really want/need the temporary file
to be in a shared directory like /tmp, do
<EM
>not</EM
> use the traditional shell
technique of using the process id in a template and just creating the file
using normal operations like "&#62;".
Shell scripts can use "$$" to indicate the PID, but the
PID can be easily determined or guessed by an attacker,
who can then pre-create files or links with the same name.
Thus the following "typical" shell script is <EM
>unsafe</EM
>:
<TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><FONT
COLOR="#000000"
><PRE
CLASS="PROGRAMLISTING"
> echo "This is a test" &#62; /tmp/test$$ # DON'T DO THIS.</PRE
></FONT
></TD
></TR
></TABLE
></P
><P
>If you need a temporary file or directory
in a shell script, and you want it in /tmp,
a solution sometimes suggested is to use
mktemp(1), which is intended for use in shell scripts
(note that mktemp(1) and mktemp(3) are different things).
However, as Michal Zalewski notes, this is insecure in many environments
that run tmp cleaners;
the problem is that when a privileged program sweeps through a temporary
directory, it will probably expose a race condition.
Even if this weren't true, I do not recommend using shell scripts that
create temporary files in shared directories;
creating such files in private directories or using pipes instead is
generally preferable, even if you're sure your tmpwatch program is okay
(or that you have no local users).
If you must use mktemp(1), note that
mktemp(1) takes a template, then
creates a file or directory using O_EXCL and returns the resulting name;
thus, mktemp(1) won't work on NFS version 2 filesystems.
Here are some examples of correct use of mktemp(1) in Bourne shell scripts;
these examples are straight from the mktemp(1) man page:
<TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><FONT
COLOR="#000000"
><PRE
CLASS="PROGRAMLISTING"
> # Simple use of mktemp(1), where the script should quit
# if it can't get a safe temporary file.
# Note that this will be INSECURE on many systems, since they use
# tmpwatch-like programs that will erase "old" files and expose race
# conditions.
TMPFILE=`mktemp /tmp/$0.XXXXXX` || exit 1
echo "program output" &#62;&#62; $TMPFILE
# Simple example, if you want to catch the error:
TMPFILE=`mktemp -q /tmp/$0.XXXXXX`
if [ $? -ne 0 ]; then
echo "$0: Can't create temp file, exiting..."
exit 1
fi</PRE
></FONT
></TD
></TR
></TABLE
></P
><P
>Perl programmers should use File::Temp, which tries to
provide a cross-platform means of securely creating temporary files.
However, read the documentation carefully on how to use it properly first;
it includes interfaces to unsafe functions as well.
I suggest explicitly setting its safe_level to HIGH; this will invoke
additional security checks.
<A
HREF="http://search.cpan.org/author/JHI/perl-5.8.0/lib/File/Temp.pm"
TARGET="_top"
>The Perl 5.8 documentation of File::Temp is available on-line</A
>.</P
><P
>Don't reuse a temporary filename (i.e. remove and recreate it),
no matter how you obtained the ``secure'' temporary filename in the
first place.
An attacker can observe the original filename
and hijack it before you recreate it the second time.
And of course, always use appropriate file permissions.
For example, only allow world/group access
if you need the world or a group to access the file, otherwise
keep it mode 0600 (i.e., only the owner can read or write it).</P
><P
>Clean up after yourself, either by using an exit handler, or making
use of UNIX filesystem semantics and unlink()ing the file immediately
after creation so the directory entry goes away but the file itself
remains accessible until the last file descriptor pointing to it is
closed. You can then continue to access it within your program by
passing around the file descriptor.
Unlinking the file has a lot of advantages for code maintenance:
the file is automatically deleted, no matter how your program crashes.
It also decreases the likelihood that a maintainer will insecurely
use the filename (they need to use the file descriptor instead).
The one minor problem with immediate unlinking is that it makes it slightly
harder for administrators to see how disk space is being used, since
they can't simply look at the file system by name.</P
><P
>You might consider ensuring that your code for Unix-like systems
respects the environment variables TMP or TMPDIR
if the provider of these variable values is trusted.
By doing so, you make it possible for users to move their temporary
files into an unshared directory (and eliminating the problems discussed here),
such as a subdirectory inside their home directory.
Recent versions of Bastille can set these variables to reduce the sharing
between users.
Unfortunately, many users set TMP or TMPDIR to a shared directory
(say /tmp), so your secure program must still
correctly create temporary files even if these environment variables
are set.
This is one advantage of the GNOME approach, since at least on some
systems tempnam(3) automatically uses TMPDIR, while
the mkstemp(3) approach requires more code to do this.
Please don't create yet more environment variables for temporary directories
(such as TEMP), and in particular don't create a different environment
name for each application (e.g., don't use "MYAPP_TEMP").
Doing so greatly complicates managing systems,
and users wanting a special temporary directory for a specific
application can just set the environment variable specially
when running that particular application.
Of course, if these environment variables might have been set by an
untrusted source, you should ignore them - which you'll do anyway
if you follow the advice in
<A
HREF="environment-variables.html#ENV-VAR-SOLUTION"
>Section 5.2.3</A
>.</P
><P
>These techniques don't work if the temporary directory is remotely
mounted using NFS version 2 (NFSv2), because NFSv2 doesn't properly
support O_EXCL.
See <A
HREF="avoid-race.html#LOCKING-USING-FILES"
>Section 7.10.2.1</A
> for more information.
NFS version 3 and later properly support O_EXCL; the simple solution
is to ensure that temporary directories are either local or, if mounted
using NFS, mounted using NFS version 3 or later.
There is a technique for safely creating temporary files on NFS v2,
involving the use of link(2) and stat(2), but it's complex; see
<A
HREF="avoid-race.html#LOCKING-USING-FILES"
>Section 7.10.2.1</A
> which has more information about this.</P
><P
>As an aside, it's worth noting that
FreeBSD has recently changed the mk*temp() family to get rid of
the PID component of the filename and replace the entire thing with
base-62 encoded randomness. This drastically raises the number of
possible temporary files for the "default" usage of 6 X's, meaning
that even mktemp(3) with 6 X's is reasonably (probabilistically) secure
against guessing, except under very frequent usage.
However, if you also follow the guidance here, you'll eliminate the
problem they're addressing.</P
><P
>Much of this information on temporary files was derived from
<A
HREF="http://lwn.net/2000/1221/a/sec-tmp.php3"
TARGET="_top"
>Kris Kennaway's
posting to Bugtraq about temporary files on December 15, 2000</A
>.</P
><P
>I should note that the Openwall Linux patch from
<A
HREF="http://www.openwall.com/linux/"
TARGET="_top"
>http://www.openwall.com/linux/</A
>
includes an optional ``temporary file directory'' policy that counters
many temporary file based attacks.
The Linux Security Module (LSM) project includes an "owlsm" module
that implements some of the OpenWall ideas, so
Linux Kernels with LSM can quickly insert these rules into a running system.
When enabled, it has two protections:
<P
></P
><UL
><LI
><P
>Hard links: Processes may not make hard links to files in certain cases.
The OpenWall documentation states that
"Processes may not make hard links to files they do not have write access to."
In the LSM version, the rules are as follows:
if both the process' uid and fsuid (usually the same as the euid) is
is different from the linked-to-file's uid, the
process uid is not root, and the process lacks the FOWNER capability, then
the hard link is forbidden.
The check against the process uid may be dropped someday
(they are work-arounds for the atd(8) program), at which point the rules
would be:
if both the process' fsuid (usually the same as the euid) is
is different from the linked-to-file's uid and
and the process lacks the FOWNER capability, then the hard link is forbidden.
In other words, you can only create hard links to files you own,
unless you have the FOWNER capability.&#13;</P
></LI
><LI
><P
>Symbolic links (symlinks): Certain symlinks are not followed.
The original OpenWall documentation states that
"root processes may not follow symlinks that
are not owned by root", but the actual rules (from looking at the code)
are more complicated.
In the LSM version, if the directory is sticky ("+t" mode, used in shared
directories like /tmp), symlinks are not followed if the symlink was
created by anyone other than either the owner of the directory or
the current process' fsuid (which is usually the effective uid).</P
></LI
></UL
>
Many systems do not implement this openwall policy, so you can't depend on
this in general protecting your system.
However, I encourage using this policy on your own system, and
please make sure that your application will work when this policy is in place.</P
></DIV
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="LOCKING"
></A
>7.10.2. Locking</H2
><P
>There are often situations in which a program must ensure that it has
exclusive rights to something (e.g., a file, a device, and/or
existence of a particular server process).
Any system which locks resources must deal with the standard problems
of locks, namely, deadlocks (``deadly embraces''), livelocks,
and releasing ``stuck'' locks if a program doesn't clean up its locks.
A deadlock can occur if programs are stuck waiting for each other to
release resources.
For example, a deadlock would occur if
process 1 locks resources A and waits for resource B,
while process 2 locks resource B and waits for resource A.
Many deadlocks can be prevented by simply requiring all processes
that lock multiple resources to lock them
in the same order (e.g., alphabetically by lock name).</P
><DIV
CLASS="SECT3"
><H3
CLASS="SECT3"
><A
NAME="LOCKING-USING-FILES"
></A
>7.10.2.1. Using Files as Locks</H3
><P
>On Unix-like systems resource locking has traditionally been done by creating
a file to indicate a lock, because this is very portable.
It also makes it easy to ``fix'' stuck locks, because an administrator
can just look at the filesystem to see what locks have been set.
Stuck locks can occur because the program failed to clean up after
itself (e.g., it crashed or malfunctioned) or because the whole system crashed.
Note that these are ``advisory'' (not ``mandatory'') locks - all processes
needed the resource must cooperate to use these locks.</P
><P
>However, there are several traps to avoid.
First, don't use the technique used by
very old Unix C programs,
which is calling creat() or its open() equivalent, the open() mode
O_WRONLY | O_CREAT | O_TRUNC, with the file mode set to 0 (no permissions).
For normal users on normal file systems, this works, but
this approach fails to lock the file when the user has root privileges.
Root can always perform this operation, even when the file
already exists.
In fact, old versions of Unix had this particular problem in the
old editor ``ed'' -- the symptom was that
occasionally portions of the password file would be placed in user's files
[Rochkind 1985, 22]!
Instead, if you're creating a lock for processes that are on the local
filesystem, you should use open() with the flags
O_WRONLY | O_CREAT | O_EXCL (and again, no permissions, so that other
processes with the same owner won't get the lock).
Note the use of O_EXCL, which is the official way to
create ``exclusive'' files; this even works for root on a local filesystem.
[Rochkind 1985, 27].</P
><P
>Second, if the lock file may be on an NFS-mounted filesystem, then you have
the problem that NFS version 2 doesn't completely support normal file semantics.
This can even be a problem for work that's supposed to be ``local'' to a
client, since some clients don't have local disks and may have <EM
>all</EM
>
files remotely mounted via NFS.
The manual for <EM
>open(2)</EM
> explains how to handle things in this case
(which also handles the case of root programs):</P
><P
><SPAN
CLASS="QUOTE"
>"... programs which rely on
[the O_CREAT and O_EXCL flags of open(2) to work on
filesystems accessed via NFS version 2]
for performing locking tasks will contain a race condition. The solution
for performing atomic file locking using a lockfile is to create
a unique file on the same filesystem (e.g., incorporating
hostname and pid), use link(2) to make a link to
the lockfile and use stat(2) on the unique file to
check if its link count has increased to 2. Do
not use the return value of the link(2) call."</SPAN
></P
><P
>Obviously, this solution only works if all programs doing the locking
are cooperating, and if all non-cooperating programs aren't allowed to
interfere.
In particular, the directories you're using for file locking
must not have permissive file permissions for creating and removing files.</P
><P
>NFS version 3 added support for O_EXCL mode in open(2);
see IETF RFC 1813,
in particular the "EXCLUSIVE" value to the "mode" argument of "CREATE".
Sadly, not everyone has switched to NFS version 3 or higher at the time of this
writing, so you can't depend on this yet in portable programs.
Still, in the long run there's hope that this issue will go away.</P
><P
>If you're locking a device or the existence of a process on a local
machine, try to use standard conventions.
I recommend using the Filesystem Hierarchy Standard (FHS);
it is widely referenced by Linux systems, but it also tries to incorporate
the ideas of other Unix-like systems.
The FHS describes
standard conventions for such locking files, including naming, placement,
and standard contents of these files [FHS 1997].
If you just want to be sure that your server doesn't execute more than once
on a given machine, you should usually create a process identifier as
/var/run/NAME.pid with the pid as its contents.
In a similar vein, you should place lock files for things
like device lock files in /var/lock.
This approach has the minor disadvantage of leaving files hanging around
if the program suddenly halts,
but it's standard practice and that problem is
easily handled by other system tools.</P
><P
>It's important that the programs which are cooperating using files to
represent the locks use the same
directory, not just the same directory name.
This is an issue with networked systems: the FHS explicitly notes that
/var/run and /var/lock are unshareable, while /var/mail is shareable.
Thus, if you want the lock to work on a single machine, but not interfere
with other machines, use unshareable directories like /var/run
(e.g., you want to permit each machine to run its own server).
However, if you want all machines sharing files in a network to obey the
lock, you need to use a directory that they're sharing; /var/mail is
one such location. See FHS section 2 for more information on this subject.</P
></DIV
><DIV
CLASS="SECT3"
><H3
CLASS="SECT3"
><A
NAME="OTHER-LOCKING"
></A
>7.10.2.2. Other Approaches to Locking</H3
><P
>Of course, you need not use files to represent locks.
Network servers often need not bother; the mere act of binding to a port
acts as a kind of lock, since if there's an existing server bound to a given
port, no other server will be able to bind to that port.</P
><P
>Another approach to locking
is to use POSIX record locks, implemented through fcntl(2) as a
``discretionary lock''.
These are discretionary, that is, using them requires the cooperation of the
programs needing the locks (just as the approach to using files to
represent locks does).
There's a lot to recommend POSIX record locks:
POSIX record locking is supported on nearly all Unix-like platforms
(it's mandated by POSIX.1), it
can lock portions of a file (not just a whole file), and it can handle the
difference between read locks and write locks.
Even more usefully, if a process dies, its locks are automatically removed,
which is usually what is desired.</P
><P
>You can also use mandatory locks, which are based on System V's
mandatory locking scheme.
These only apply to files where the locked file's setgid bit is set, but
the group execute bit is not set.
Also, you must mount the filesystem to permit mandatory file locks.
In this case, every read(2) and write(2) is checked for locking;
while this is more thorough than advisory locks, it's also slower.
Also, mandatory locks don't port as widely to other Unix-like systems
(they're available on Linux and System V-based systems, but not necessarily
on others).
Note that processes with root privileges
can be held up by a mandatory lock, too, making it possible that
this could be the basis of a denial-of-service attack.</P
></DIV
></DIV
></DIV
><DIV
CLASS="NAVFOOTER"
><HR
ALIGN="LEFT"
WIDTH="100%"><TABLE
SUMMARY="Footer navigation table"
WIDTH="100%"
BORDER="0"
CELLPADDING="0"
CELLSPACING="0"
><TR
><TD
WIDTH="33%"
ALIGN="left"
VALIGN="top"
><A
HREF="fail-safe.html"
ACCESSKEY="P"
>Prev</A
></TD
><TD
WIDTH="34%"
ALIGN="center"
VALIGN="top"
><A
HREF="index.html"
ACCESSKEY="H"
>Home</A
></TD
><TD
WIDTH="33%"
ALIGN="right"
VALIGN="top"
><A
HREF="trustworthy-channels.html"
ACCESSKEY="N"
>Next</A
></TD
></TR
><TR
><TD
WIDTH="33%"
ALIGN="left"
VALIGN="top"
>Fail Safe</TD
><TD
WIDTH="34%"
ALIGN="center"
VALIGN="top"
><A
HREF="internals.html"
ACCESSKEY="U"
>Up</A
></TD
><TD
WIDTH="33%"
ALIGN="right"
VALIGN="top"
>Trust Only Trustworthy Channels</TD
></TR
></TABLE
></DIV
></BODY
></HTML
>