Skip to content

CL-0022: tmpfs exec — “Permission denied” running from tmpfs

Severity: LOW

Derivation (see severity model):

  • Baseline: A — the attacker already has code execution in this container, as the workload uid
  • Precondition: Removes a mitigation — exec, suid and dev on a tmpfs grant no privilege. They remove three defaults Docker applies to every tmpfs
  • Impact: Single container
  • Qualifier/modifier: none
  • Derived: Removes a mitigation × Single container = LOW
  • Shipped: LOW
  • Evidence: _cl0022 and _t22_exec_tmpfs — a tmpfs is noexec by default and :exec removes it, re-proven against a live container on every CI run

References: - OWASP Docker Security Rule #8 - Docker docs — tmpfs mounts

What it detects

A tmpfs: entry that explicitly passes exec, suid, or dev — each of which removes a security default Docker applies to every tmpfs:

tmpfs:
  - /tmp:exec            # flagged: re-enables execution (default is noexec)
  - /tmp:suid            # flagged: re-enables setuid (default nosuid)
  - /scratch:exec,suid   # flagged: names both
  - /tmp                 # clean: gets noexec,nosuid,nodev by default
  - /run:size=64m        # clean: size doesn't remove the defaults
  - /tmp:noexec          # clean: that's the secure value

Matching is on whole comma-separated tokens, so the secure noexec is never mistaken for exec. The long volumes: [{type: tmpfs}] form is out of scope — it keeps the secure defaults and can't express these tokens.

Why it matters

Docker mounts every tmpfs with noexec,nosuid,nodev by default — verified across the short, list, and long mount forms, and the defaults survive even when other options like size= are set. The only way to lose them is to pass the opposite option explicitly:

Option Removes Effect
exec noexec binaries can be executed from the mount
suid nosuid setuid/setgid bits are honored
dev nodev device nodes on the mount are usable

A tmpfs is always writable and in-memory. Re-enabling execution turns it into a place to stage and run a dropped payload — the classic foothold an attacker wants, and the more so under read_only: true (CL-0007), where tmpfs is often the only writable path. suid additionally opens a local privilege-escalation vector. Because the secure posture is the default, an explicit exec/suid/dev is a deliberate weakening that should be justified.

This is why the rule flags the presence of these options rather than the absence of noexec/nosuid/nodev: a plain tmpfs: [/tmp] is already hardened by Docker, so flagging it would be noise.

Fix

Remove the offending option to fall back to Docker's secure default:

# Before
tmpfs:
  - /tmp:exec

# After
tmpfs:
  - /tmp

There is no auto-fix: the option is set deliberately when present, so reverting it (which would break a workload that genuinely executes from the mount) is left to manual review.

Reading the failure

If you arrived here from an error, it is probably this one — a binary or script refusing to run from a tmpfs. The busybox wording is re-proven against a live container on every CI run (scripts/validate_rule_premises.py); the exact prefix varies by shell.

Symptom in logs (verbatim) What it means Action — in this order
sh: line 0: /scratch/busybox: Permission denied executing a file that is executable (x bit set) from a tmpfs the mount's default noexec blocked the exec — not a file-permission problem 1. relocate the executable into the image (bake it in at build time) or onto a named volume; 2. only if the workload genuinely must write-then-execute on that path, add :exec — which is exactly what this rule flags, so pair it with a suppression carrying a documented reason

The trap is that the obvious fix (:exec) is the finding: a writable, executable, in-memory mount is the classic staging ground for a dropped payload, and doubly so under read_only: true (CL-0007), where tmpfs is often the only writable path. Treat :exec as a justified exception, not a fix. A related red herring: chmod +x appears to succeed on the tmpfs file and changes nothing — noexec is a property of the mount, not the file.

ATT&CK coverage

No adversary technique maps to this rule. That is a finding about the rule, not an omission — see below.