Handbook sections

Mount & filesystem hardening

Last reviewed

Split high-risk directories onto their own mounts and strip them with nodev, nosuid, noexec so a writable corner of the disk can't become a launch pad.

The threat: write a payload, then run it

Attackers almost always need a place to write a payload and then run it. World-writable directories like /tmp and /dev/shm, or churn-heavy trees like /var, are the obvious candidates: a dropped malware binary, an exploit stage, a downloaded toolkit, or a fileless payload in shared memory. Two mount-level weaknesses make this trivial:

  • Execution from writable storage: if any unprivileged user can drop a file under /tmp and execute it, a single compromised service becomes arbitrary code execution.
  • setuid/device abuse: a setuid binary or a device node placed on a user-writable mount can be leveraged for privilege escalation or to access raw devices and bypass filesystem permissions.

When everything lives on a single / partition, none of these directories can carry independent restrictions: the whole disk shares one set of mount options, and there is no boundary to enforce.

Why harden it

Mount options are a cheap, kernel-enforced control: the kernel, not an application, decides what is allowed. Three flags neutralise the core abuse cases:

  • noexec: the kernel refuses to execute any program stored on the mount, removing the attacker's staging ground.
  • nosuid: the setuid/setgid bits are ignored, so a planted setuid-root binary runs with the caller's privileges, not root's.
  • nodev: device nodes on the mount are not honoured, blocking a rogue /dev/sda-style node used to read raw disk.

But a flag can only be applied to a mount that exists. That is why hardening pairs the options with separate partitions (or tmpfs/systemd mount units) for the sensitive trees: /tmp, /var, /var/tmp, /var/log, /var/log/audit, /home, /dev/shm, /boot, /srv, /opt, /usr. A separate /var/log also stops a log flood from filling /, and a dedicated audit partition protects the evidence trail.

Defense principles applied

  • Attack-surface reduction: disable unused filesystem drivers (cramfs, freevxfs, jffs2, hfs, udf, usb-storage) so an attacker can't auto-mount an exotic or removable filesystem to sidestep your options.
  • Least privilege: nosuid and nodev deny the two classic escalation primitives on data partitions, which never legitimately need them.
  • Defense in depth: mount options layer on top of file permissions and MAC; even if permissions are wrong, noexec still blocks the execute step.
  • Containment: separate partitions stop one directory from exhausting space everywhere and isolate the audit trail from tampering and overflow.

The dpkg/noexec caveat

noexec on /var and /tmp has a real cost on Debian/Ubuntu. APT and dpkg stage and run maintainer scripts (preinst, postinst) from temporary locations, and some operations execute helpers from under /var or /tmp: under a strict noexec these can fail mid-upgrade, leaving packages half-configured. The same applies to build tools, certain pip/dnf plugins, app self-updaters, and sandboxed browsers that execute from /dev/shm. This is not a reason to skip the control: redirect tooling with $TMPDIR/APT::ExtractTemplates::TempDir to an exec-capable scratch area, grant a scoped temporary exception during maintenance, or use dpkg hooks that remount briefly, never silently drop noexec from /tmp system-wide. Test upgrades in staging before enforcing.

What Pavois audits: the effective mount table

Pavois reads the effective mount table, the kernel's live view via /proc/self/mountinfo exposed through InSpec's mount('<path>') resource, not /etc/fstab. That is the whole point: it sees a /tmp provided at runtime by the tmp.mount systemd unit, a /dev/shm absent from fstab, a partition silently remounted without a flag, or an fstab line that never took effect, cases a file-based scanner (OVAL/oscap) would wrongly pass. Concretely, the Mounts rules check:

  • noexec, nosuid, nodev on /tmp, /var, /var/tmp, /var/log, /var/log/audit, /home, /dev/shm, /boot, /boot/efi, /srv, /opt, asserting each resolved option set contains the flag.
  • Separate-partition rules (be_mounted) confirming /tmp, /var, /var/tmp, /var/log, /var/log/audit, /home, /dev/shm, /boot, /srv, /opt, /usr ride on their own filesystem rather than on /.

Each rule carries its norm mappings (CIS, ANSSI BP-028 R28, NIST) and a findmnt-based verification step.

How do you verify and operate?

  • See the effective options: findmnt /tmp or findmnt -o TARGET,OPTIONS shows what the kernel actually enforces; /proc/self/mountinfo is the raw live truth. Do not trust /etc/fstab alone.
  • Apply without a reboot: mount -o remount,noexec,nosuid,nodev /tmp once the fstab line (or systemd unit) is in place, then confirm with findmnt.
  • /tmp is often a systemd unit, not fstab: systemctl enable --now tmp.mount (or a tmp.mount drop-in) provides /tmp as tmpfs; edit the unit, not just fstab.
  • Persist it: the nodev,nosuid,noexec options go in the /etc/fstab entry (or the systemd mount unit) so they survive a reboot; Pavois reading the live mount catches the gap when they don't.

Pitfalls

  • noexec can break package upgrades and build tools (see the dpkg caveat): scope an exec-capable $TMPDIR rather than dropping the flag.
  • A flag in fstab that was never remounted is not effective: only the live mount counts.
  • /tmp as a systemd tmp.mount ignores the fstab line: harden the unit.
  • Some apps execute from /dev/shm (Chrome, some runtimes): test before enforcing noexec there.

FAQ

Why does Pavois read the live mount instead of /etc/fstab? Because the kernel enforces the live options; an fstab entry not yet remounted, or a /tmp provided by tmp.mount, makes fstab a false source. Pavois reads /proc/self/mountinfo.

noexec broke an apt upgrade. What now? Redirect APT/dpkg temp execution to an exec-capable area ($TMPDIR, APT::ExtractTemplates::TempDir) or remount briefly during maintenance; never drop noexec from /tmp permanently.

Do I need separate partitions, or just options? Both: a flag can only apply to a mount that exists, so isolate the sensitive trees and then strip them with nodev,nosuid,noexec.