Differences between revisions 28 and 62 (spanning 34 versions)
Revision 28 as of 2015-12-23 12:27:15
Size: 5503
Editor: geirha
Comment: more quotes
Revision 62 as of 2025-02-03 20:20:30
Size: 7292
Editor: 81
Comment: Platform soecific: Add mink to macos 2019 Darwin mktemp discussion
Deletions are marked like this. Additions are marked like this.
Line 3: Line 3:
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). The recommended solution is to use the `mktemp` command. It is widely available (GNU coreutils since 2007), and there has been discussion about its status in POSIX (see
[[https://unix.stackexchange.com/q/614808|StackExchange 2020]]). Old versions may require a `-c` option to create the file in advance, or they may create the file by default and fail if `-c` is supplied.
Line 5: Line 6:
The traditional answer has usually been something like this: Still, for a definitive solution, there does not appear to be any single command that simply "works" everywhere. The much older command `tempfile` is not portable, and its use is discouraged according to the manual page: "(...) deprecated; you should use `mktemp`."
Line 7: 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"
Some legacy systems, like Solaris, may not have either command.

In POSIX systems, you could possibly revert to `m4`, which has the ability to create a temporary file. However, the command may not be installed by default, and old `m4` versions may be missing this feature.

The traditional approach:

{{{#!highlight bash
# Problems: race condition, security
tempfile=/tmp/myname.$$
trap 'rm -f "$tempfile"; exit 1' 1 2 3 15
rm -f "$tempfile"
touch "$tempfile"
Line 17: Line 24:

<<Anchor(HOME)>>
Line 18: Line 27:
The best ''portable'' answer is to put your temporary files ''in your home directory'' (or some other private directory) 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. 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.
Line 24: Line 33:

<<Anchor(tempdir)>>
Line 25: Line 36:
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 `mktemp` 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. 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.
Line 29: Line 40:
{{{ {{{#!highlight bash
Line 32: Line 43:
i=0 tempdir=
trap '[[ $tempdir ]] && rm -rf "$tempdir"' EXIT
i=0
tempdir=""
trap '[ "$tempdir" ] && rm -rf "$tempdir"' EXIT
Line 36: Line 48:
  tempdir=${TMPDIR:-/tmp}/$RANDOM-$$   tempdir=${TMPDIR:-/tmp}//$RANDOM-$$
Line 41: Line 53:
  printf 'Could not create temporary directory\n' >&2   echo 'Could not create temporary directory' >&2
Line 48: Line 60:
{{{ {{{#!highlight bash
Line 51: Line 63:
i=0 tempdir=
cleanup() {
i=0
tempdir=""
Cleanup() {
Line 60: Line 73:
  trap "cleanup $sig" "$sig"   trap "Cleanup $sig" "$sig"
Line 71: Line 84:
  printf 'Could not create temporary directory\n' >&2   echo 'Could not create temporary directory' >&2
Line 85: Line 98:
{{{
# Linux
{{{#!highlight bash
# Bash on Linux
Line 88: Line 101:
unset tempdir tempdir=""
Line 90: Line 103:
tempdir=$(mktemp -d "${TMPDIR:-/tmp}/XXXXXXXXXXXXXXXXXXXXXXXXXXXXX") ||
  { printf 'ERROR creating a temporary file\n' >&2; exit 1; }
tempdir=$(mktemp -d  "${TMPDIR:-/tmp}//XXXXXXXXXXXXXXXXXXXXXXXXXXXXX") ||
  { echo 'ERROR creating a temporary file' >&2; exit 1; }
Line 96: Line 109:

<<Anchor(nonportable-tools)>>
Line 97: Line 112:
If you're writing a script for only a specific set of systems, and if those systems have a `mktemp` or `tempfile` tool, you can use that. 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. 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 legacy systems may not have these tools, this is not guaranteed to be portable solution. But it's often good enough for most of the operating system (see e.g. MacOS Darwin 2019 notes in [[https://unix.stackexchange.com/questions/555058/mktemp-on-macos-not-honouring-tmpdir|StackExchange]]).
Line 99: Line 114:
Here's an example using `mktemp`:

{{{#!highlight bash
# POSIX

tmpfile=""
trap '[ "$tmpfile" ] && rm -f "$tmpfile"' EXIT
tmpfile=$(mktemp)
}}}


<<Anchor(m4)>>
=== Using m4 ===
The `m4` approach, although in POSIX, may not be an ideal solution in practice. For example, the `m4` in MacOS (July 2021) was too old to have the `mkstemp` macro included. 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() { echo "$*" >&2; exit 1; }

unset -v tmpfile
trap '[ "$tmpfile" ] && rm -f "$tmpfile"' EXIT
tmpfile=$(m4 - <<< 'mkstemp(/tmp/foo-XXXXXX)') ||
  Die "couldn't create temporary file"
}}}


<<Anchor(other)>>

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

The recommended solution is to use the mktemp command. It is widely available (GNU coreutils since 2007), and there has been discussion about its status in POSIX (see StackExchange 2020). Old versions may require a -c option to create the file in advance, or they may create the file by default and fail if -c is supplied.

Still, for a definitive solution, there does not appear to be any single command that simply "works" everywhere. The much older command tempfile is not portable, and its use is discouraged according to the manual page: "(...) deprecated; you should use mktemp."

Some legacy systems, like Solaris, may not have either command.

In POSIX systems, you could possibly revert to m4, which has the ability to create a temporary file. However, the command may not be installed by default, and old m4 versions may be missing this feature.

The traditional approach:

   1 # Problems: race condition, security
   2 tempfile=/tmp/myname.$$
   3 trap 'rm -f "$tempfile"; exit 1' 1 2 3 15
   4 rm -f "$tempfile"
   5 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 
   4 tempdir=""
   5 trap '[ "$tempdir" ] && rm -rf "$tempdir"' EXIT
   6 
   7 while ((++i <= 10)); do
   8   tempdir=${TMPDIR:-/tmp}//$RANDOM-$$
   9   mkdir -m 700 "$tempdir" 2>/dev/null && break
  10 done
  11 
  12 if ((i > 10)); then
  13   echo 'Could not create temporary directory' >&2
  14   exit 1
  15 fi

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

   1 # POSIX
   2 
   3 i=0 
   4 tempdir=""
   5 Cleanup() {
   6   [ "$tempdir" ] && rm -rf "$tempdir"
   7   if [ "$1" != EXIT ]; then
   8     trap - "$1"         # reset trap, and
   9     kill "-$1" "$$"     # resend signal to self
  10   fi
  11 }
  12 for sig in EXIT HUP INT TERM; do
  13   trap "Cleanup $sig" "$sig"
  14 done
  15 
  16 while [ "$i" -lt 10 ]; do
  17   tempdir=${TMPDIR:-/tmp}/$(awk 'BEGIN { srand (); print rand() }')-$$
  18   mkdir -m 700 "$tempdir" 2>/dev/null && break
  19   sleep 1
  20   i=$((i+1))
  21 done
  22 
  23 if [ "$i" -ge 10 ]; then
  24   echo 'Could not create temporary directory' >&2
  25   exit 1
  26 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 tempdir=""
   4 trap '[ "$tempdir" ] && rm -rf "$tempdir"' EXIT
   5 tempdir=$(mktemp -d  "${TMPDIR:-/tmp}//XXXXXXXXXXXXXXXXXXXXXXXXXXXXX") ||
   6   { echo 'ERROR creating a temporary file' >&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 legacy systems may not have these tools, this is not guaranteed to be portable solution. But it's often good enough for most of the operating system (see e.g. MacOS Darwin 2019 notes in StackExchange).

Here's an example using mktemp:

   1 # POSIX
   2 
   3 tmpfile=""
   4 trap '[ "$tmpfile" ] && rm -f "$tmpfile"' EXIT
   5 tmpfile=$(mktemp)

Using m4

The m4 approach, although in POSIX, may not be an ideal solution in practice. For example, the m4 in MacOS (July 2021) was too old to have the mkstemp macro included. 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() { echo "$*" >&2; exit 1; }
   4 
   5 unset -v tmpfile
   6 trap '[ "$tmpfile" ] && rm -f "$tmpfile"' EXIT
   7 tmpfile=$(m4 - <<< 'mkstemp(/tmp/foo-XXXXXX)') ||
   8   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 2025-02-09 13:06:43 by emanuele6)