Size: 7247
Comment: undid revs 45-48, which make the first example (which isn't even supposed to be used) more complicated and worse
|
Size: 7278
Comment: Simplified. Remove superfluous '--', use simple echo where applicable, use uppercase Die() to prevent clash with any existing command.
|
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). 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. | There does not appear to be any single command that simply ''works'' everywhere. Comnand `tempfile` is not portable and mentions on manual that it is "(...) deprecated; you should use mktemp". Command `mktemp` is included in POSIX (in few cases, it may not exists in all systems). It may require a `-c` option 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 (old Solaris). |
Line 5: | Line 5: |
The traditional answer has usually been something like this: | In POSIX systems, you could possibly also use `m4`, which has the ability to create a temporary file. However, the command may not be installed by default. In very old, legacy systems, the `m4` implementation may be missing this feature. The traditional approach: |
Line 8: | Line 10: |
# Do not use! Race condition! | # Problems: race condition, security |
Line 10: | Line 12: |
trap 'rm -f -- "$tempfile"; exit 1' 1 2 3 15 rm -f -- "$tempfile" touch -- "$tempfile" |
trap 'rm -f "$tempfile"; exit 1' 1 2 3 15 rm -f "$tempfile" touch "$tempfile" |
Line 36: | Line 38: |
i=0 tempdir= trap '[[ $tempdir ]] && rm -rf -- "$tempdir"' EXIT |
i=0 tempdir="" trap '[ "$tempdir" ] && rm -rf "$tempdir"' EXIT |
Line 41: | Line 44: |
mkdir -m 700 -- "$tempdir" 2>/dev/null && break | mkdir -m 700 "$tempdir" 2>/dev/null && break |
Line 45: | Line 48: |
printf 'Could not create temporary directory\n' >&2 | echo 'Could not create temporary directory' >&2 |
Line 55: | Line 58: |
i=0 tempdir= cleanup() { [ "$tempdir" ] && rm -rf -- "$tempdir" |
i=0 tempdir="" Cleanup() { [ "$tempdir" ] && rm -rf "$tempdir" |
Line 64: | Line 68: |
trap "cleanup $sig" "$sig" | trap "Cleanup $sig" "$sig" |
Line 68: | Line 72: |
tempdir=${TMPDIR:-/tmp}//$(awk 'BEGIN { srand (); print rand() }')-$$ mkdir -m 700 -- "$tempdir" 2>/dev/null && break |
tempdir=${TMPDIR:-/tmp}/$(awk 'BEGIN { srand (); print rand() }')-$$ mkdir -m 700 "$tempdir" 2>/dev/null && break |
Line 75: | Line 79: |
printf 'Could not create temporary directory\n' >&2 | echo 'Could not create temporary directory' >&2 |
Line 92: | Line 96: |
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; } |
tempdir="" trap '[ "$tempdir" ] && rm -rf "$tempdir"' EXIT tempdir=$(mktemp -d "${TMPDIR:-/tmp}//XXXXXXXXXXXXXXXXXXXXXXXXXXXXX") || { echo 'ERROR creating a temporary file' >&2; exit 1; } |
Line 108: | Line 112: |
# Bash on Linux | # POSIX |
Line 110: | Line 114: |
unset -v tmpfile trap '[[ $tmpfile ]] && rm -f -- "$tmpfile"' EXIT |
tmpfile="" trap '[ "$tmpfile" ] && rm -f "$tmpfile"' EXIT |
Line 123: | Line 127: |
die() { printf >&2 '%s\n' "$*"; exit 1; } | Die() { echo "$*" >&2; exit 1; } |
Line 126: | Line 130: |
trap '[[ $tmpfile ]] && rm -f -- "$tmpfile"' EXIT | trap '[ "$tmpfile" ] && rm -f "$tmpfile"' EXIT |
Line 128: | Line 132: |
die "couldn't create temporary file" | Die "couldn't create temporary file" |
Line 136: | Line 140: |
die() { printf >&2 '%s\n' "$*"; exit 1; } | Die() { echo "$*" >&2; exit 1; } |
Line 138: | Line 142: |
unset -v tmpfile trap '[[ $tmpfile ]] && rm -f -- "$tmpfile"' EXIT |
tmpfile="" trap '[ "$tmpfile" ] && rm -f "$tmpfile"' EXIT |
Line 143: | Line 147: |
die "couldn't create temporary file" | Die "couldn't create temporary file" |
How do I create a temporary file in a secure manner?
There does not appear to be any single command that simply works everywhere. Comnand tempfile is not portable and mentions on manual that it is "(...) deprecated; you should use mktemp". Command mktemp is included in POSIX (in few cases, it may not exists in all systems). It may require a -c option 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 (old Solaris).
In POSIX systems, you could possibly also use m4, which has the ability to create a temporary file. However, the command may not be installed by default. In very old, legacy systems, the m4 implementation may be missing this feature.
The traditional approach:
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:
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.
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:
Using m4
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.
Or, alternatively:
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.