Differences between revisions 7 and 35 (spanning 28 versions)
Revision 7 as of 2009-04-11 00:45:22
Size: 4721
Editor: 102
Comment: Excuse my English :-)
Revision 35 as of 2022-02-24 00:19:57
Size: 7102
Editor: emanuele6
Comment: add a bunch of --
Deletions are marked like this. Additions are marked like this.
Line 2: Line 2:
Line 4: Line 3:
Good question. To be filled in later. (Interim hints: {{{tempfile}}} is not portable. {{{mktemp}}} exists more widely, but it may require a {{{-c}}} switch to create the file in advance; or it may create the file by default and barf if {{{-c}}} is supplied. Some systems don't have either command (Solaris, POSIX). There does not appear to be any single command that simply ''works'' everywhere.) There does not appear to be any single command that simply ''works'' everywhere. `tempfile` is not portable. `mktemp` exists more widely (but still not ubiquitously), but it may require a `-c` switch to create the file in advance; or it may create the file by default and barf if `-c` is supplied. Some systems don't have either command (Solaris, POSIX). POSIX systems are supposed to have `m4` which has the ability to create a temporary file, but some systems may not install `m4` by default, or their implementation of `m4` may be missing this feature.
Line 9: Line 8:
   # Do not use! Race condition!
   TEMPFILE=/tmp/myname.$$
   trap 'rm -f $TEMPFILE; exit 1' 1 2 3 15
   rm -f $TEMPFILE
   touch $TEMPFILE
# Do not use! Race condition!
tempfile=/tmp/myname.$$
trap 'rm -f -- "$tempfile"; exit 1' 1 2 3 15
rm -f -- "$tempfile"
touch -- "$tempfile"
Line 15: Line 14:
Line 16: Line 16:

=== Use your $HOME ===
The best ''portable'' answer is to put your temporary files ''in your home directory'' (or some other private directory, e.g. `$XDG_RUNTIME_DIR`) where nobody else has write access. Then at least you don't have to worry about malicious users. Simplistic PID-based schemes (or hostname + PID for shared file systems) should be enough to prevent conflicts with your own scripts.

If you're implementing a daemon which runs under a user account with no home directory, why not simply ''make a private directory for your daemon'' at the same time you're installing the code?

Unfortunately, people don't seem to like that answer. They demand that their temporary files should be in `/tmp` or `/var/tmp`. For those people, there is no clean answer, so they must choose a hack they can live with.

=== Make a temporary directory ===
If you can't use $HOME, the next best answer is to create a private '''directory''' to hold your temp file(s), instead of creating the files directly inside a world-writable sandbox like `/tmp` or `/var/tmp`. The `mkdir` command is atomic, and only reports success if it actually created the directory. So long as we ''do not'' use the `-p` option, we can be assured that it actually created a brand new directory, rather than following a symlink to danger.

Here is one example of this approach:

{{{#!highlight bash
# Bash

i=0 tempdir=
trap '[[ $tempdir ]] && rm -rf -- "$tempdir"' EXIT

while ((++i <= 10)); do
  tempdir=${TMPDIR:-/tmp}/$RANDOM-$$
  mkdir -m 700 -- "$tempdir" 2>/dev/null && break
done

if ((i > 10)); then
  printf 'Could not create temporary directory\n' >&2
  exit 1
fi
}}}

Instead of `RANDOM`, `awk` can be used to generate a random number in a POSIX compatible way:

{{{#!highlight bash
# POSIX

i=0 tempdir=
cleanup() {
  [ "$tempdir" ] && rm -rf -- "$tempdir"
  if [ "$1" != EXIT ]; then
    trap - "$1" # reset trap, and
    kill "-$1" "$$" # resend signal to self
  fi
}
for sig in EXIT HUP INT TERM; do
  trap "cleanup $sig" "$sig"
done

while [ "$i" -lt 10 ]; do
  tempdir=${TMPDIR:-/tmp}/$(awk 'BEGIN { srand (); print rand() }')-$$
  mkdir -m 700 -- "$tempdir" 2>/dev/null && break
  sleep 1
  i=$((i+1))
done

if [ "$i" -ge 10 ]; then
  printf 'Could not create temporary directory\n' >&2
  exit 1
fi
}}}

Note however that `srand()` seeds the random number generator using seconds since the epoch which is fairly easy for an adversary to predict and perform a denial of service attack. (Historical awk implementations that predate POSIX may not even use the time of day for `srand()`, so don't count on this if you're on an ancient system.)

Some systems have a 14-character filename limit, so avoid the temptation to string `$RANDOM` together more than twice. You're relying on the atomicity of `mkdir` for your security, ''not'' the obscurity of your random name. If someone fills up `/tmp` with hundreds of thousands of random-number files to thwart you, you've got bigger issues.
Line 19: Line 82:
 * you have available the  {{{mktemp}}} command and you can use its {{{-d}}} option so that it creates temporary directores only accessible for you, with random characters inside their names to make it almost impossible for an attacker to guess the directories names beforehand.
 * you can create filenames longer than 14 characters in'' ''{{{/tmp.}}}
 * you have available Bash so you can use special features of it.
 * You have available the {{{mktemp}}} command and you can use its {{{-d}}} option so that it creates temporary directores only accessible for you, with random characters inside their names to make it almost impossible for an attacker to guess the directory name beforehand.
 * You can create filenames longer than 14 characters in {{{/tmp}}}.
Line 23: Line 85:
{{{
# Sets $TMPDIR to "/tmp" only if it didn't have a value previously
TMPDIR=${TMPDIR:-/tmp}
# We can find about $TMPDIR in http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html
{{{#!highlight bash
# Bash on Linux
Line 28: Line 88:
# Creates a particular temporary directory inside $TMPDIR
TEMPORARY_DIR=$(mktemp -d "$TMPDIR/XXXXXXXXXXXXXXXXXXXXXXXXXXXXX") || { echo "ERROR creating a temporary file"; exit 1; }
unset -v tempdir
trap '[ "$tempdir" ] && rm -rf -- "$tempdir"' EXIT
tempdir=$(mktemp -d -- "${TMPDIR:-/tmp}/XXXXXXXXXXXXXXXXXXXXXXXXXXXXX") ||
  { printf 'ERROR creating a temporary file\n' >&2; exit 1; }
}}}
Line 31: Line 94:
# When the program exits, it tries to remove the temporary folder
trap 'rm -rf "$TEMPORARY_DIR"' 0
# Note: this code is executed even if the process receives a signal 1,2,3 or 15 (not if it receives a 9 signal, of course)
And then you can create your particular files inside the temporary directory.

=== Use platform-specific tools ===
If you're writing a script for only a specific set of systems, and if those systems have a `mktemp` or `tempfile` tool which works correctly, you can use that. Make sure that your tool actually ''creates'' the temporary file, and does not simply give you an unused name. The options will vary from system to system, so you will have to write your script with that in mind. Since some platforms have ''none'' of these tools, this is not a portable solution, but it's often good enough, especially if your script is only targeting a specific operating system.

Here's an example using Linux's `mktemp`:

{{{#!highlight bash
# Bash on Linux

unset -v tmpfile
trap '[[ $tmpfile ]] && rm -f -- "$tmpfile"' EXIT
tmpfile=$(mktemp)
Line 35: Line 108:
And then you can create your particular files inside the temporary folder. If you want to make life more difficult to an adversary and you are using Bash, you can use random numbers in the names of your temporary files to prevent strange cases like when you are using a shared system, your program is paused for a long time, its process ID is known, it uses a temporary file, root runs Tmpwatch (or similar) to delete temporary files that are not used for a long time and an adversary creates a replica of your temporary folder (if you use random names the adversary will be able to know the name of your temporary folder but not the name of your files inside it). For example:
Line 37: Line 109:
{{{
# Prepares the name of the future temporary file
TEMPORARY_FILE="$TEMPORARY_DIR/strings-$RANDOM-$RANDOM-$RANDOM"
# Then you can use the temporary file, like in
grep string file > "$TEMPORARY_FILE"
The `m4` approach is ''theoretically'' POSIX-standard, but not in practice -- it may not work on MacOS, as its version of `m4` is too old (as of July, 2021). Nevertheless, here's an example. Note that `mkstemp` requires a template, including a path prefix, or else it creates the temporary file in the current directory.

{{{#!highlight bash
# Bash

die() { printf >&2 '%s\n' "$*"; exit 1; }

unset -v tmpfile
trap '[[ $tmpfile ]] && rm -f -- "$tmpfile"' EXIT
tmpfile=$(<<< 'mkstemp(/tmp/foo-XXXXXX)' m4 -) ||
   die "couldn't create temporary file"
Line 43: Line 121:
A different suggestion (remove if not universal): A temporary directory can be created that is unlikely to match an existing directory using the {{{RANDOM}}} variable as follows:
Line 45: Line 122:
{{{
   TEMP_DIR=/tmp/$RANDOM
   mkdir $TEMP_DIR
Or, alternatively:

{{{#!highlight bash
# Bash

die() { printf >&2 '%s\n' "$*"; exit 1; }

unset -v tmpfile
trap '[[ $tmpfile ]] && rm -f -- "$tmpfile"' EXIT
: "${TMPDIR:=/tmp}"
tmpfile=$TMPDIR/$(CDPATH= cd -- "$TMPDIR" && <<< 'mkstemp(foo-XXXXXX)' m4 -) ||
   die "couldn't create temporary file"
Line 49: Line 135:
This will make a directory of the form: {{{/tmp/20445/}}}. To decrease the chance of collision with an existing directory, the {{{RANDOM}}} variable can be used a number of times:
Line 51: Line 136:
{{{
   TEMP_DIR=/tmp/$RANDOM-$RANDOM-$RANDOM
   mkdir $TEMP_DIR
}}}
This will make a directory of the form: {{{/tmp/24953-2875-2182/}}} . This avoids a race condition because the `mkdir` is atomic, as we see in [[BashFAQ/045|FAQ #45]].

 . ''Hmmm... this has potential, if you check the exit status of `mkdir` to be sure it actually created the directory. And set `umask` to something fairly restrictive as well. It could use some more peer review, though. -- GreyCat''
  . ''Oh, also, you shouldn't assume you can create filenames longer than 14 characters in `/tmp`. There are still some systems out there with 14-character filename limits. --GreyCat''
   . ''Also also, {{{RANDOM}}} is not available in the Bourne shell, so it still fails on Solaris. This is why most legacy Bourne shell scripts use {{{$$}}} for that purpose.'' --GreyCat
=== Other approaches ===

How do I create a temporary file in a secure manner?

There does not appear to be any single command that simply works everywhere. tempfile is not portable. mktemp exists more widely (but still not ubiquitously), but it may require a -c switch to create the file in advance; or it may create the file by default and barf if -c is supplied. Some systems don't have either command (Solaris, POSIX). POSIX systems are supposed to have m4 which has the ability to create a temporary file, but some systems may not install m4 by default, or their implementation of m4 may be missing this feature.

The traditional answer has usually been something like this:

# Do not use!  Race condition!
tempfile=/tmp/myname.$$
trap 'rm -f -- "$tempfile"; exit 1' 1 2 3 15
rm -f -- "$tempfile"
touch -- "$tempfile"

The problem with this is: if the file already exists (for example, as a symlink to /etc/passwd), then the script may write things in places they should not be written. Even if you remove the file immediately before using it, you still have a RaceCondition: someone could re-create a malicious symlink in the interval between your shell commands.

Use your $HOME

The best portable answer is to put your temporary files in your home directory (or some other private directory, e.g. $XDG_RUNTIME_DIR) where nobody else has write access. Then at least you don't have to worry about malicious users. Simplistic PID-based schemes (or hostname + PID for shared file systems) should be enough to prevent conflicts with your own scripts.

If you're implementing a daemon which runs under a user account with no home directory, why not simply make a private directory for your daemon at the same time you're installing the code?

Unfortunately, people don't seem to like that answer. They demand that their temporary files should be in /tmp or /var/tmp. For those people, there is no clean answer, so they must choose a hack they can live with.

Make a temporary directory

If you can't use $HOME, the next best answer is to create a private directory to hold your temp file(s), instead of creating the files directly inside a world-writable sandbox like /tmp or /var/tmp. The mkdir command is atomic, and only reports success if it actually created the directory. So long as we do not use the -p option, we can be assured that it actually created a brand new directory, rather than following a symlink to danger.

Here is one example of this approach:

   1 # Bash
   2 
   3 i=0 tempdir=
   4 trap '[[ $tempdir ]] && rm -rf -- "$tempdir"' EXIT
   5 
   6 while ((++i <= 10)); do
   7   tempdir=${TMPDIR:-/tmp}/$RANDOM-$$
   8   mkdir -m 700 -- "$tempdir" 2>/dev/null && break
   9 done
  10 
  11 if ((i > 10)); then
  12   printf 'Could not create temporary directory\n' >&2
  13   exit 1
  14 fi

Instead of RANDOM, awk can be used to generate a random number in a POSIX compatible way:

   1 # POSIX
   2 
   3 i=0 tempdir=
   4 cleanup() {
   5   [ "$tempdir" ] && rm -rf -- "$tempdir"
   6   if [ "$1" != EXIT ]; then
   7     trap - "$1"         # reset trap, and
   8     kill "-$1" "$$"     # resend signal to self
   9   fi
  10 }
  11 for sig in EXIT HUP INT TERM; do
  12   trap "cleanup $sig" "$sig"
  13 done
  14 
  15 while [ "$i" -lt 10 ]; do
  16   tempdir=${TMPDIR:-/tmp}/$(awk 'BEGIN { srand (); print rand() }')-$$
  17   mkdir -m 700 -- "$tempdir" 2>/dev/null && break
  18   sleep 1
  19   i=$((i+1))
  20 done
  21 
  22 if [ "$i" -ge 10 ]; then
  23   printf 'Could not create temporary directory\n' >&2
  24   exit 1
  25 fi

Note however that srand() seeds the random number generator using seconds since the epoch which is fairly easy for an adversary to predict and perform a denial of service attack. (Historical awk implementations that predate POSIX may not even use the time of day for srand(), so don't count on this if you're on an ancient system.)

Some systems have a 14-character filename limit, so avoid the temptation to string $RANDOM together more than twice. You're relying on the atomicity of mkdir for your security, not the obscurity of your random name. If someone fills up /tmp with hundreds of thousands of random-number files to thwart you, you've got bigger issues.

In some systems (like Linux):

  • You have available the mktemp command and you can use its -d option so that it creates temporary directores only accessible for you, with random characters inside their names to make it almost impossible for an attacker to guess the directory name beforehand.

  • You can create filenames longer than 14 characters in /tmp.

   1 # Bash on Linux
   2 
   3 unset -v tempdir
   4 trap '[ "$tempdir" ] && rm -rf -- "$tempdir"' EXIT
   5 tempdir=$(mktemp -d -- "${TMPDIR:-/tmp}/XXXXXXXXXXXXXXXXXXXXXXXXXXXXX") ||
   6   { printf 'ERROR creating a temporary file\n' >&2; exit 1; }

And then you can create your particular files inside the temporary directory.

Use platform-specific tools

If you're writing a script for only a specific set of systems, and if those systems have a mktemp or tempfile tool which works correctly, you can use that. Make sure that your tool actually creates the temporary file, and does not simply give you an unused name. The options will vary from system to system, so you will have to write your script with that in mind. Since some platforms have none of these tools, this is not a portable solution, but it's often good enough, especially if your script is only targeting a specific operating system.

Here's an example using Linux's mktemp:

   1 # Bash on Linux
   2 
   3 unset -v tmpfile
   4 trap '[[ $tmpfile ]] && rm -f -- "$tmpfile"' EXIT
   5 tmpfile=$(mktemp)

The m4 approach is theoretically POSIX-standard, but not in practice -- it may not work on MacOS, as its version of m4 is too old (as of July, 2021). Nevertheless, here's an example. Note that mkstemp requires a template, including a path prefix, or else it creates the temporary file in the current directory.

   1 # Bash
   2 
   3 die() { printf >&2 '%s\n' "$*"; exit 1; }
   4 
   5 unset -v tmpfile
   6 trap '[[ $tmpfile ]] && rm -f -- "$tmpfile"' EXIT
   7 tmpfile=$(<<< 'mkstemp(/tmp/foo-XXXXXX)' m4 -) ||
   8    die "couldn't create temporary file"

Or, alternatively:

   1 # Bash
   2 
   3 die() { printf >&2 '%s\n' "$*"; exit 1; }
   4 
   5 unset -v tmpfile
   6 trap '[[ $tmpfile ]] && rm -f -- "$tmpfile"' EXIT
   7 : "${TMPDIR:=/tmp}"
   8 tmpfile=$TMPDIR/$(CDPATH= cd -- "$TMPDIR" && <<< 'mkstemp(foo-XXXXXX)' m4 -) ||
   9    die "couldn't create temporary file"

Other approaches

Another not-quite-serious suggestion is to include C code in the script that implements a mktemp(1) command based on the mktemp(3) library function, compile it, and use that in the script. But this has a couple problems:

  • The useless Solaris systems where we would need this probably don't have a C compiler either.
  • Chicken and egg problem: we need a temporary file name to hold the compiler's output.

BashFAQ/062 (last edited 2023-04-28 01:07:11 by larryv)