Bash · Shell-Sicherheit · mktemp · Cleanup
Temporäre Dateien sicher nutzen
und zuverlässig aufräumen mit mktemp und trap

Ein fester Pfad wie /tmp/skript.tmp in einem Shell-Skript ist eine Einladung für Race Conditions, Datenlecks und Symlink-Angriffe. mktemp erstellt garantiert einmalige, sichere temporäre Dateien – und trap EXIT stellt sicher, dass sie in jedem Beendigungsszenario aufgeräumt werden.

12 Min. Lesezeit mktemp · trap EXIT · /tmp · /var/tmp · Symlink-Schutz · Cleanup Bash 4.x · 5.x · Linux · POSIX

1. Das Problem mit festen /tmp-Pfaden

Temporäre Dateien mit festen Pfaden wie /tmp/meinskript.tmp in Bash-Skripten sind ein klassisches Sicherheitsproblem. Wenn zwei Instanzen desselben Skripts gleichzeitig laufen – durch parallele Cron-Jobs, manuelle Ausführungen oder verteilte Systeme – überschreiben sie sich gegenseitig die temporären Dateien. Das Ergebnis ist ein Race Condition, der sich manchmal als Datenverlust, manchmal als stillem Fehler und manchmal als Sicherheitslücke äußert.

Das Gefährlichste an festen /tmp-Pfaden für temporäre Dateien ist die Vorhersagbarkeit. Ein Angreifer, der auf demselben System unprivilegierte Prozesse ausführen kann, kennt den exakten Pfad, der vom Skript verwendet wird. Er kann vor dem Skript eine Datei oder einen Symlink unter diesem Pfad anlegen. Wenn das Skript dann privilegiert (als root) ausgeführt wird und die temporäre Datei schreibt, schreibt es tatsächlich in das Ziel des Symlinks – das kann eine Systemdatei sein. Diese Klasse von Angriffen heißt "TOCTOU" (Time of Check to Time of Use) und ist in CVE-Datenbanken gut dokumentiert.

Auch das Nicht-Aufräumen von temporären Dateien ist ein Problem, das unterschätzt wird. Wenn ein Skript durch set -e, durch ein Signal oder durch einen expliziten exit-Aufruf in einem Fehlerpfad endet, ohne aufzuräumen, akkumulieren sich temporäre Dateien in /tmp. Auf Systemen mit wenig Platz oder bei häufig fehlschlagenden Skripten kann das /tmp füllen und das gesamte System in einen unlesbaren Zustand bringen.

2. mktemp: sichere temporäre Dateien und Verzeichnisse

mktemp löst das Sicherheitsproblem mit festen Pfaden durch atomares Erstellen von temporären Dateien mit garantiert einmaligem Namen. Der Aufruf tmpfile=$(mktemp) erstellt eine leere Datei mit einem zufälligen, einmaligen Namen wie /tmp/tmp.Xz4K8m und gibt den Pfad auf stdout aus. Der Dateierstellungsvorgang ist atomar: Zwischen dem Prüfen der Existenz und dem Erstellen der Datei gibt es kein Fenster für Angreifer.

Das Template-Argument von mktemp ermöglicht sprechende Namen für temporäre Dateien: mktemp /tmp/deploy-XXXXXXXX erstellt eine Datei wie /tmp/deploy-Kf73nPqR. Die X-Zeichen werden durch zufällige Zeichen ersetzt – mindestens drei sollten es sein, sechs sind Standard. Für temporäre Verzeichnisse statt Dateien verwendet man mktemp -d. Das erstellte Verzeichnis hat standardmäßig Permissions 700, die Datei 600 – beide nur für den erstellenden User lesbar.


#!/usr/bin/env bash
# safe-tmpfile.sh — Secure temporary file usage with mktemp and trap
set -euo pipefail

# Declare all temp resources at the top for visibility
TMPFILE=""
TMPDIR=""

cleanup() {
  # Remove only if paths are set and exist — safe even if mktemp failed
  [[ -n "$TMPFILE" ]] && rm -f "$TMPFILE"
  [[ -n "$TMPDIR"  ]] && rm -rf "$TMPDIR"
}

# Register cleanup BEFORE creating temp files — no orphan risk
trap cleanup EXIT
trap 'echo "[ABORT] Signal received"; exit 130' INT TERM HUP

# Create temp file and temp directory with descriptive templates
TMPFILE="$(mktemp /tmp/deploy-XXXXXXXX)"
TMPDIR="$(mktemp -d /tmp/deploy-work-XXXXXXXX)"

echo "[INFO] Using TMPFILE=$TMPFILE TMPDIR=$TMPDIR"

# Write data — file has mode 600 (owner-only) by default
echo "sensitive config data" > "$TMPFILE"
cp /etc/myapp/template.conf "$TMPDIR/config.conf"

# Use temp resources in a subshell — cleanup still fires on EXIT
(
  cd "$TMPDIR"
  process-config.sh config.conf > "$TMPFILE"
)

# Read result
cat "$TMPFILE"
# cleanup() fires automatically on script exit (normal or error)

Ein häufiger Fehler beim Umgang mit temporären Dateien: trap wird nach dem ersten mktemp-Aufruf registriert. Wenn mktemp fehlschlägt (voller /tmp, keine Rechte), läuft kein Cleanup. Das korrekte Muster: Variablen mit leerem Wert initialisieren, trap sofort registrieren, dann mktemp aufrufen. Die Cleanup-Funktion prüft mit [[ -n "$TMPFILE" ]], ob die Variable gesetzt ist, bevor sie löscht.

3. trap EXIT: zuverlässige Cleanup-Registrierung

trap cleanup EXIT ist der Eckpfeiler des zuverlässigen Umgangs mit temporären Dateien. Der EXIT-Trap wird in jedem Beendigungsszenario ausgeführt: normales Skriptende, vorzeitiger Abbruch durch set -e, expliziter exit-Aufruf und – in Kombination mit Signal-Traps – auch bei SIGINT (Ctrl+C) und SIGTERM. Der EXIT-Trap selbst feuert nicht bei SIGKILL (9) oder SIGHUP – diese Signale können in Bash nicht abgefangen werden.

Mehrere Cleanup-Registrierungen für temporäre Dateien können mit einem Array von Cleanup-Aktionen implementiert werden. Statt eine monolithische Cleanup-Funktion zu haben, die alles kennen muss, registriert jeder Codeabschnitt seine eigene Cleanup-Aktion in einem globalen Array. Die Haupt-Cleanup-Funktion iteriert das Array rückwärts und führt jede Aktion aus. Das ermöglicht modulare Skripte, in denen Funktionen ihre eigenen Ressourcen verwalten, ohne globale Variablen zu verschmutzen.


#!/usr/bin/env bash
# modular-cleanup.sh — Stack-based cleanup for multiple temporary resources
set -euo pipefail

declare -a CLEANUP_STACK=()

# Push a cleanup action onto the stack
push_cleanup() {
  CLEANUP_STACK+=("$*")
}

# Execute all cleanup actions in LIFO order (last in, first out)
run_cleanup() {
  local exit_code=$?
  for (( i=${#CLEANUP_STACK[@]}-1; i>=0; i-- )); do
    eval "${CLEANUP_STACK[$i]}" || true
  done
  return $exit_code
}

trap run_cleanup EXIT

# Each section registers its own cleanup — no global knowledge needed
setup_database_dump() {
  local dump_file
  dump_file="$(mktemp /tmp/db-dump-XXXXXXXX.sql)"
  push_cleanup "rm -f '$dump_file'"
  echo "$dump_file"  # return path via stdout
}

setup_work_dir() {
  local work_dir
  work_dir="$(mktemp -d /tmp/migration-XXXXXXXX)"
  push_cleanup "rm -rf '$work_dir'"
  echo "$work_dir"
}

DUMP_FILE="$(setup_database_dump)"
WORK_DIR="$(setup_work_dir)"

echo "Dump: $DUMP_FILE"
echo "Work: $WORK_DIR"

# Simulate work — cleanup fires in reverse order on any exit
mysqldump mydb > "$DUMP_FILE"
cp "$DUMP_FILE" "$WORK_DIR/"

4. /tmp vs. /var/tmp: Lebensdauer und Wahl

Die Wahl zwischen /tmp und /var/tmp für temporäre Dateien hängt von der gewünschten Lebensdauer ab. /tmp ist für kurzlebige temporäre Dateien gedacht: Systemd-tmpfiles bereinigt /tmp typischerweise beim Systemstart, und viele Distributionen mounten /tmp als tmpfs direkt im RAM – Dateien dort belegen keinen Festplattenplatz, aber Arbeitsspeicher, und verschwinden beim Reboot. Auf Systemen mit wenig RAM kann ein großes tmpfs den Arbeitsspeicher erschöpfen.

/var/tmp überlebt Neustarts: systemd-tmpfiles löscht Dateien dort erst nach 30 Tagen (configuerbar in /etc/tmpfiles.d/). Es eignet sich für temporäre Dateien, die zwischen Sitzungen oder über mehrere Skriptausführungen hinweg erhalten bleiben sollen – zum Beispiel Download-Caches, Partial-Uploads oder Checkpointing-Daten. Wer mktemp -p /var/tmp verwendet, erstellt temporäre Dateien mit derselben Sicherheit wie in /tmp, aber mit persistenter Lebensdauer.

Symlink-Angriffe gegen temporäre Dateien funktionieren so: Ein Angreifer beobachtet, dass ein privilegiertes Skript mit einem vorhersagbaren Pfad wie /tmp/backup.tar arbeitet. Bevor das Skript die Datei erstellt, legt der Angreifer einen Symlink /tmp/backup.tar -> /etc/passwd an. Das privilegierte Skript folgt dem Symlink und überschreibt /etc/passwd. Selbst wenn das Skript nur liest, kann es sensible Daten in eine vom Angreifer kontrollierte Datei umleiten.

mktemp verhindert diesen Angriff durch atomares Erstellen: Es prüft und erstellt die Datei in einem einzigen Systemaufruf (open(O_CREAT|O_EXCL)), der fehlschlägt, wenn die Datei bereits existiert. Ein Angreifer kann den Zeitraum zwischen Prüfung und Erstellung nicht ausnutzen, weil es keinen gibt. Zusätzlichen Schutz bietet das Setzen von TMPDIR auf ein nur für den ausführenden User zugängliches Verzeichnis: export TMPDIR="$(mktemp -d ~/tmp.XXXXXXXX)". In einem privaten TMPDIR können andere User keine Symlinks anlegen.


#!/usr/bin/env bash
# private-tmpdir.sh — Use a private TMPDIR to eliminate symlink attack surface
set -euo pipefail

# Create a private temp directory under $HOME — no other users can write here
PRIVATE_TMPDIR="$(mktemp -d "${HOME}/tmp.XXXXXXXX")"

# Set TMPDIR so that all mktemp calls in this script (and subprocesses) use it
export TMPDIR="$PRIVATE_TMPDIR"

cleanup() {
  rm -rf "$PRIVATE_TMPDIR"
}
trap cleanup EXIT

# All subsequent mktemp calls create files in the private directory
config_file="$(mktemp)"        # e.g. ~/tmp.K3mPqR/tmp.Xf9kLm
staging_dir="$(mktemp -d)"    # e.g. ~/tmp.K3mPqR/tmp.dir.8NqPx

echo "Private tmp: $PRIVATE_TMPDIR"
echo "Config: $config_file"
echo "Staging: $staging_dir"

# Verify: files are inside private dir (not world-writable /tmp)
if [[ "$config_file" != "${PRIVATE_TMPDIR}"/* ]]; then
  echo "[ERROR] mktemp did not honor TMPDIR" >&2
  exit 1
fi

# Sensitive operations without symlink risk
generate-config.sh > "$config_file"
deploy.sh --config "$config_file" --staging-dir "$staging_dir"

6. Temporäre Verzeichnisse und Arbeitsumgebungen

Temporäre Verzeichnisse mit mktemp -d sind die sichere Alternative zu mkdir /tmp/mydir. Sie haben Modus 700 und sind damit für andere User nicht lesbar oder schreibbar. Als Arbeitsverzeichnis für Build-Prozesse, Entpackoperationen oder Multi-Step-Pipelines, die viele Zwischendateien erzeugen, sind temporäre Verzeichnisse einer einzelnen temporären Datei vorzuziehen: Alle Zwischendateien werden automatisch beim Löschen des Verzeichnisses mit entfernt, ohne dass jede einzelne Datei explizit in der Cleanup-Funktion registriert werden muss.

Das Muster cd "$TMPDIR" in einem Build-Skript hat einen Fallstrick: Wenn das Skript mit set -e endet, während das aktuelle Verzeichnis das temporäre Verzeichnis ist, und die Cleanup-Funktion rm -rf "$TMPDIR" ausführt, kann ein anschließendes cd scheitern. Das sicherere Muster: eine Subshell für den Arbeitsverzeichniswechsel verwenden und niemals das globale cd auf ein temporäres Verzeichnis setzen, das später gelöscht wird.

7. Temporäre Dateien in parallelen Prozessen

In parallelisierten Skripten, die mehrere Hintergrundprozesse starten, muss jeder Prozess seine eigenen temporären Dateien verwalten. Das korrekte Muster: Jeder Hintergrundprozess ruft mktemp selbst auf und registriert seinen eigenen Cleanup über trap in der Subshell. Der Hauptprozess hat keinen Zugriff auf die temporären Dateien der Kindprozesse und sollte nicht versuchen, sie zentral zu verwalten. Sobald ein Kindprozess beendet wird – normal oder durch den Hauptprozess – feuert sein EXIT-Trap und räumt auf.

Eine Alternative für temporäre Dateien in parallelen Prozessen: Alle Prozesse arbeiten in Unterverzeichnissen eines gemeinsamen temporären Verzeichnisses, das der Hauptprozess verwaltet. Jeder Kindprozess erhält einen eigenen Unterverzeichnis-Pfad übergeben: workdir=$(mktemp -d "${SHARED_TMPDIR}/worker-XXXXXXXX"). Der Hauptprozess räumt nach wait das gesamte temporäre Verzeichnis auf. Das vereinfacht das Cleanup auf Kosten der Isolation – tritt ein Kindprozess ab, ohne aufzuräumen, hat der Hauptprozess noch Zugriff auf seine Daten.

8. Cleanup-Strategien für komplexe Skripte

In komplexen Skripten, die viele temporäre Dateien erstellen, wird das manuelle Tracking von Pfaden in einzelnen Variablen unübersichtlich. Das Stack-basierte Cleanup-Muster aus Abschnitt 3 ist eine Lösung. Eine weitere ist das Verzeichnis-basierte Cleanup: Alle temporären Dateien des Skripts werden in einem einzigen mktemp -d-Verzeichnis erstellt. Die Cleanup-Funktion löscht nur dieses Verzeichnis mit rm -rf – unabhängig davon, wie viele Dateien darin entstanden sind. Kein Tracking, keine Liste, kein Vergessen.

Für langlebige Skripte oder Daemon-ähnliche Prozesse, die temporäre Dateien über Stunden halten, empfiehlt sich ein explizites Bestandsverzeichnis: Alle aktiven temporären Dateien werden in einer Indexdatei (selbst eine sichere temporäre Datei) registriert. Ein periodischer Cleanup-Lauf prüft, ob die zugehörigen Prozesse noch laufen, und löscht verwaiste temporäre Dateien von beendeten Prozessen. Das verhindert Anhäufung bei langen Laufzeiten ohne vollständige Kontrolle über alle Cleanup-Pfade.

9. Vergleich: sichere vs. unsichere temporäre Dateien

Die folgende Tabelle zeigt die häufigsten Muster für temporäre Dateien in Bash-Skripten und ihre Sicherheits- und Robustheitseigenschaften.

Muster Race-sicher Symlink-sicher Cleanup
/tmp/skript.tmp Nein Nein Manuell, oft vergessen
/tmp/skript-$$.tmp Teilweise Nein Nur wenn Trap vorhanden
mktemp Ja (O_EXCL) Ja Nur mit trap EXIT
mktemp + trap EXIT Ja Ja Alle Szenarien
mktemp + privates TMPDIR Ja Maximal Alle Szenarien

Die Verwendung der PID ($$) im Dateinamen schützt nur gegen gleichzeitige Ausführungen mit verschiedenen PIDs. Es schützt nicht gegen Symlink-Angriffe, weil die PID des nächsten Skripts vorhersagbar ist: /proc/sys/kernel/pid_max begrenzt PIDs, und auf einem ruhigen System folgen sie vorhersagbaren Mustern. Nur mktemp mit kryptografisch zufälligen Suffixen ist wirklich sicher. Das vollständige Muster mktemp + trap EXIT + privates TMPDIR ist die einzige Lösung, die alle Bedrohungsklassen für temporäre Dateien gleichzeitig abdeckt.

Mironsoft

Shell-Sicherheit, Bash-Auditing und DevOps-Infrastruktur

Shell-Skripte auf Sicherheitslücken prüfen?

Wir analysieren bestehende Bash-Skripte auf unsichere temporäre Dateien, fehlende trap-Registrierungen und Symlink-Verwundbarkeiten – und beheben sie mit dem vollständigen mktemp + trap-Muster.

Security-Audit

Skripte auf TOCTOU, Symlink-Angriffe und vorhersagbare Pfade analysieren

Refactoring

Feste /tmp-Pfade durch mktemp + trap EXIT + privates TMPDIR ersetzen

ShellCheck-CI

Automatische Prüfung in der Pipeline – kein unsicheres Muster gelangt in die Produktion

10. Zusammenfassung

Temporäre Dateien in Bash sicher einzusetzen erfordert drei Komponenten: mktemp für atomares, sicheres Erstellen mit zufälligem Namen; trap cleanup EXIT für zuverlässiges Aufräumen in jedem Beendigungsszenario; und optional ein privates TMPDIR für maximalen Schutz vor Symlink-Angriffen. Feste Pfade wie /tmp/skript.tmp sind in keinem Produktionsskript akzeptabel – sie sind Race-anfällig, Symlink-verwundbar und werden oft nicht aufgeräumt.

Die Cleanup-Funktion muss vor dem ersten mktemp-Aufruf registriert werden und darf nie den Fehlerfall ignorieren. Stack-basierte Cleanup-Muster ermöglichen modulare Skripte, bei denen jede Funktion ihre eigenen temporären Dateien verwaltet. Das Verzeichnis-basierte Cleanup (mktemp -d + rm -rf) ist für viele Zwischendateien eleganter als das Tracking einzelner Pfade. ShellCheck erkennt typische Fehler bei temporären Dateien statisch und sollte in jede CI-Pipeline integriert werden.

Temporäre Dateien sicher nutzen — Das Wichtigste auf einen Blick

Immer mktemp

Niemals feste /tmp-Pfade. mktemp erstellt atomar, einmalig und mit Permissions 600/700 — Grundvoraussetzung für sichere temporäre Dateien.

trap vor mktemp

trap cleanup EXIT muss vor dem ersten mktemp-Aufruf stehen. Cleanup prüft mit [[ -n "$TMPFILE" ]] vor dem Löschen.

/tmp vs. /var/tmp

/tmp: kurzlebig, oft tmpfs im RAM, wird bei Reboot gelöscht. /var/tmp: persistiert Neustarts, wird erst nach 30 Tagen bereinigt.

Privates TMPDIR

export TMPDIR="$(mktemp -d ~/tmp.XXXXXXXX)" eliminiert Symlink-Angriffsfläche vollständig – kein anderer User kann in das Verzeichnis schreiben.

11. FAQ: Temporäre Dateien sicher nutzen und zuverlässig aufräumen

1Was ist ein Symlink-Angriff auf temporäre Dateien?
Angreifer legt vor dem Erstellen einen Symlink unter dem erwarteten Pfad an. Privilegiertes Skript schreibt ins Symlink-Ziel. mktemp verhindert das durch atomares O_EXCL-Erstellen.
2Warum /tmp/skript-$$.tmp nicht sicher?
PID ist vorhersagbar. Kein Schutz gegen Symlink-Angriffe. Nur mktemp mit kryptografisch zufälligem Suffix ist wirklich sicher.
3SIGKILL und temporäre Dateien?
SIGKILL ist nicht abfangbar — trap EXIT feuert nicht. Lösung: mktemp -d und systemd-tmpfiles oder Cleanup-Cronjob für den Verzeichnis-Präfix.
4/var/tmp statt /tmp?
Wenn die Datei Neustarts überleben soll: /var/tmp. Für einmalige, sitzungsgebundene Nutzung: /tmp (oft tmpfs im RAM).
5mktemp mit Datei-Suffix?
mktemp --suffix=.sql (GNU/Linux). Für Portabilität: mktemp /tmp/PREFIX.XXXXXXXX.sql — funktioniert auf beiden Plattformen.
6Vertrauliche Daten in temporären Dateien?
mktemp erstellt mit Modus 600. Privates TMPDIR unter $HOME: kein anderer User kann lesen. Für maximale Sicherheit: RAM-backed tmpfs in eigenem Namespace.
7Temporäre Datei sofort nach Öffnen löschen?
Ja: exec 3<>"$tmpfile" && rm -f "$tmpfile". Dateideskriptor bleibt gültig, Platz wird erst beim Schließen freigegeben. Kein trap nötig.
8Stack-basiertes Cleanup-Muster?
Globales Array mit Cleanup-Befehlen. Jede Funktion pusht ihren Löschbefehl. EXIT-Trap führt das Array rückwärts aus (LIFO). Modulares Ressourcenmanagement ohne globale Variablen.
9mktemp macOS vs. Linux?
GNU (Linux): --suffix, -p. BSD (macOS): -t PREFIX. Portabel: Template im Argument: mktemp /tmp/PREFIX.XXXXXXXX.
10Ctrl+C und Cleanup?
trap 'exit 130' INT TERM zusätzlich registrieren. exit im Signal-Trap löst den EXIT-Trap aus, der aufräumt. exit 130 = Unix-Konvention für SIGINT.