skip to content

In a bash script, what does `tmpfile=$(mktemp)` give you that `tmpfile=/tmp/myscript.$$` does not?

level: juniorimportance: should knowfreq 55%

answer

  1. who actually creates the file
  2. PIDs are small and reused
  3. exclusive create, mode 0600
  4. a shared directory anyone can write
  5. mktemp prints the path it just made

basics

~20 s

mktemp creates the file itself, atomically, under a random name with owner-only permissions, and prints the path. A $$-based name is merely a predictable string that nothing has created yet, so another user can pre-create or symlink it first.

solid answer

~50 s

`/tmp/myscript.$$` is just a string — nothing exists on disk until some later redirection creates it, and the name is guessable, because `$$` is the shell's PID: a small, sequentially allocated, reusable number that any local user can watch. On a shared host an attacker can pre-create that exact path as a symlink to a file they want clobbered, and your `>` redirection will happily follow it — which matters a lot when the script runs as root from cron. `mktemp` instead *creates* the file before printing its name: it opens a randomly named candidate with `O_CREAT|O_EXCL` and mode 0600, retries on collision, and prints the path that won. So you get atomicity, an unguessable name, and private permissions in one step. What it does not give you is cleanup or quoting — those stay your job.

code

bash · 8 lines
bash
#!/usr/bin/env bash
set -euo pipefail

tmpfile=$(mktemp) || exit 1
trap 'rm -f -- "$tmpfile"' EXIT

printf '%s\n' "scratch data" > "$tmpfile"
wc -l < "$tmpfile"

go deeper

for a junior

Know that mktemp creates the file and prints its path, while a hand-built /tmp name is only a string. Say plainly that you use mktemp, quote the variable, and delete the file when you are done.

for a middle

Explain the mechanics: exclusive create so an existing name never gets reused, mode 0600, TMPDIR support, and mktemp -d for a whole scratch directory. Be ready to name mktemp -u as the unsafe option and to check mktemp's exit status.

for a senior

Show why a predictable path in a world-writable directory is a genuine vulnerability for a root cron job — symlink redirection of a write, and content swapping between write and read — and why you refuse to depend on kernel hardening settings for that.

for a principal

Own the standard: state where scratch space lives across your fleet and containers via TMPDIR, how it is sized and reaped, and how the rule is enforced in review or lint rather than left to each script's author to remember.

## The real difference is who creates the file `tmpfile=/tmp/myscript.$$` performs no I/O at all. It builds a string. The file only comes into existence later, when some command redirects into it, and at that moment the shell opens the path with `O_CREAT|O_TRUNC` — creating it if absent, truncating whatever is already there if present, and following a symlink if the path is one. `tmpfile=$(mktemp)` runs an external program that does the creating. GNU coreutils `mktemp` builds a candidate path from a template (by default `tmp.XXXXXXXXXX` inside `$TMPDIR`, falling back to `/tmp`), replaces the trailing `X`s with random characters, and opens it with `O_CREAT|O_EXCL` and mode 0600. `O_EXCL` means the open fails if the name already exists, so `mktemp` retries with a fresh name. Only the path that was successfully created is printed on stdout, which command substitution captures. By the time your variable holds a value, the file exists, is empty, and is readable and writable by you alone. ## Why the predictable name is a security bug, not untidiness `/tmp` is shared by every user on the machine. `$$` expands to the PID of the script's own shell — a small integer, handed out roughly sequentially and reused as the system runs, and visible in the process table to anyone. Guessing it, or simply pre-creating a few thousand candidates, is cheap. The classic attack is a symlink: a local user creates `/tmp/myscript.4711` as a symbolic link to a file they cannot write themselves. Your script — commonly running as root out of cron — then does `echo ... > "$tmpfile"`, the kernel resolves the symlink, and the redirection truncates and overwrites the attacker's chosen target. The same predictability supports the reverse trick: swapping the file's contents between the moment your script writes it and the moment it reads it back, a time-of-check-to-time-of-use race. Linux does ship a hardening knob that refuses to follow symlinks in world-writable sticky directories in some cases, but it is a host-tuning setting; a script that only behaves safely on a hardened kernel is not a safe script. `$RANDOM` is no substitute either — it is a seeded pseudo-random value in bash, not a security primitive. ## What mktemp guarantees and what it does not It guarantees: create-or-fail atomicity, a large random name space, mode 0600 for files and 0700 for directories, and respect for `TMPDIR` so a container or CI runner can redirect scratch space. Useful options, all real: ```bash tmp=$(mktemp) # file in $TMPDIR or /tmp, mode 0600 dir=$(mktemp -d) # directory, mode 0700 tmp=$(mktemp -p /var/tmp) # choose the parent directory tmp=$(mktemp /var/tmp/job.XXXXXX) # explicit template, at least three X's ``` `mktemp -u` prints a name *without* creating it — that is exactly the unsafe mode you are trying to escape, so treat it as a red flag in review. Check the exit status too: `tmp=$(mktemp) || exit 1`, because a full or read-only `TMPDIR` makes it fail and you do not want to carry on with an empty variable. It does **not** clean up after itself, does not quote anything for you, and is not perfectly uniform across platforms: the BSD version shipped on macOS handles template arguments differently, so a script that must run on both should pass an explicit template such as `mktemp -d /tmp/job.XXXXXX`. ## Everything after the creation is still your job Quote the variable everywhere (`"$tmpfile"`) — the name is random, but the directory it sits in may not be. Remove the file on *every* exit path, not just the happy one, which is what a cleanup registered with `trap ... EXIT` is for. And if the script only wants a scratch area rather than a single file, `mktemp -d` plus one `rm -rf` of the directory is easier to get right than tracking six individual files. A related but weaker safety net is `set -o noclobber`, which makes plain `>` refuse to overwrite an existing regular file (`>|` overrides it). It catches accidental clobbering of your own files; it is not a replacement for creating temp files with `mktemp`.

  • Your script sets TMPDIR to a project directory before calling mktemp. What changes, and what should you check?
    `mktemp` honours `TMPDIR`, so the file is created there instead of `/tmp` — useful in containers and CI where `/tmp` may be tiny or shared. Check that the directory exists and is writable, and that it is not world-writable, or you have recreated the original exposure in a new location. Also check `mktemp`'s exit status, since a missing `TMPDIR` makes it fail.
  • Why is `mktemp -u` considered unsafe?
    `-u` prints a candidate name but does not create it, so there is a window between the name being chosen and your script using it in which anyone can create that path — including as a symlink. It reintroduces exactly the race that `mktemp` exists to close. Use it only when a tool insists on a non-existent path, and then only in a directory you control.
  • Does mktemp make the temp file's contents private?
    Only against other users' processes: the file is mode 0600, so nobody else can open it. It is still plaintext on a shared filesystem, readable by root and by anything that later gains your identity, and it may survive a crash. Secrets in a temp file should be short-lived and removed on every exit path; better still, pass them through a pipe, a here-string or an environment variable and never land them on disk.

A $$ name is calling ahead to reserve a table by shouting a number anyone can hear; mktemp is walking in and sitting down before announcing where you are.

saying these in an interview costs you the question

  • Says $$ is random enough to be unique
  • Uses mktemp -u and then redirects into that name
  • Thinks mktemp deletes the file automatically
  • Assumes /tmp is private to the script's own user
  • Leaves $tmpfile unquoted when passing it to commands

context