Differences between revisions 15 and 16
Revision 15 as of 2013-01-09 12:56:02
Size: 5751
Editor: 82
Comment: use $$ and awk (strip leading 0.) -- igli
Revision 16 as of 2013-01-10 23:13:04
Size: 5906
Editor: 82
Comment: igli: use printf and awk -v to allow 32-bit pid on 14-char fs
Deletions are marked like this. Additions are marked like this.
Line 76: Line 76:
----
Line 78: Line 79:
   temp_dir=/tmp/$$_"$(awk 'BEGIN { srand (); s = rand(); sub(/^0./, "", s); print s }')"    temp_dir=/tmp/"$(awk -v p=$$ 'BEGIN { srand(); s = rand(); sub(/^0./, "", s); printf("%X_%X", p, s) }')'"
Line 81: Line 82:
Since the default awk numeric format is %g which equates to 6 digits after the decimal point, we have a 6 digit trail, one character for the {{{_}}} and 7 available for a prefixed pid on a 14-char filesystem: if the filesystem is so restricted, one might hope that it still uses a 16-bit pid_t which would of course be 5 digits maximum. Since the default awk numeric format is %g which equates to 6 digits after the decimal point, we have a 6 digit trail. 999,999 is F423F which gives us one character back, another for the {{{_}}} and 8 available for a prefixed pid on a 14-char filesystem. An unsigned 32-bit pid will be a maximum of 8 hex digits, so the above should stand a chance in most places one might encounter such an fs. And of course we bail out as greycat mentioned when the creation fails.

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

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.)

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.


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 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.

   # 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

   # Creates a particular temporary directory inside $TMPDIR
   temporary_dir=$(mktemp -d "$TMPDIR/XXXXXXXXXXXXXXXXXXXXXXXXXXXXX") || { echo "ERROR creating a temporary file" >&2; exit 1; }

   # Remove the temporary directory when the script finishes, or when it receives a signal
   trap 'rm -rf "$temporary_dir"' 0       # remove directory when script finishes
   trap 'exit 2' 1 2 3 15       # terminate script when receiving signal

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:

   # 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"


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:

   temp_dir=/tmp/$RANDOM
   mkdir "$temp_dir"

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:

   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 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

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.


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

   temp_dir=/tmp/$(awk 'BEGIN { srand (); print rand() }')
   mkdir -m 700 "$temp_dir"

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.


Combining the above we can use:

   temp_dir=/tmp/"$(awk -v p=$$ 'BEGIN { srand(); s = rand(); sub(/^0./, "", s); printf("%X_%X", p, s) }')'"
   mkdir -m 700 "$temp_dir" || { echo '!! unable to create a tempdir' >&2; exit 1; }

Since the default awk numeric format is %g which equates to 6 digits after the decimal point, we have a 6 digit trail. 999,999 is F423F which gives us one character back, another for the _ and 8 available for a prefixed pid on a 14-char filesystem. An unsigned 32-bit pid will be a maximum of 8 hex digits, so the above should stand a chance in most places one might encounter such an fs. And of course we bail out as greycat mentioned when the creation fails.

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