In an /etc/fstab entry, what do the mount options nosuid, nodev and noexec each enforce on Linux, and why is noexec on /tmp weaker protection than people assume?
answer
- restrictions live on the mount
- one blocks the privilege transition
- one makes device nodes inert
- the exec one has an interpreter hole
basics
~20 snosuid makes the kernel ignore setuid and setgid bits (and file capabilities) on that mount; nodev makes device special files there non-functional; noexec makes execve fail for files under it. noexec is weak because an interpreter can still read and run a script as data.
solid answer
~50 sThese are per-mount restrictions the kernel applies at the moment of use. `nosuid` means a setuid or setgid binary on that filesystem executes with the caller's own identity — the bits are ignored, and so are file capabilities. `nodev` means the kernel refuses to treat device nodes on that mount as devices, which stops someone from smuggling in a writable `/dev/sda` node on removable media or an unpacked archive. `noexec` makes `execve` on files under the mount fail. The point about `noexec` is that it constrains one path, not the content: `bash /tmp/x.sh` or `python3 /tmp/x.py` still works, because the interpreter opens the file as data and the kernel never executes it. Copying the payload somewhere executable also works. So I treat these as defence in depth on world-writable and data mounts — `/tmp`, `/dev/shm`, `/var/tmp`, removable media, data disks — while remembering they are attributes of the mount, so a second mount of the same filesystem without them re-enables everything.
go deeper
Know that fstab options can restrict a mount: no setuid programs, no working device nodes, no direct execution, and that read-only is the strictest of all.
Explain what the kernel checks for each option and when, note that the bits on disk are unchanged, and give the interpreter path that gets around the execution restriction.
Show that options are per-mount, so a second mount or a bind without them undoes the hardening, and be able to say which mounts you would apply which set to without breaking package-supplied binaries.
Position these as cheap defence in depth inside a larger control set — mandatory access control, immutable images, and allowlisting do the real work; mount options reduce the easy paths and must not be sold to auditors as the boundary.
## Per-mount restrictions, checked by the kernel These three options are stored in the mount, not in the filesystem, and the kernel consults them when a file on that mount is used. That framing explains both their strength — no cooperation from the filesystem or the application is required — and their main weakness, which is that they constrain a *path*, not the data. **`nosuid`.** When a program on this mount is executed, the setuid and setgid bits in its mode are ignored: the new process keeps the caller's user and group. File capabilities attached to the binary are ignored for the same reason. The setgid bit's other role — making new files in a directory inherit its group — is unaffected, because that is a filesystem behaviour rather than a privilege transition. This is the option that makes it safe to let an unprivileged user bring in their own binaries: a setuid-root shell copied onto a `nosuid` mount is just a shell. **`nodev`.** Character and block device special files on this mount do not function as devices. Opening them does not reach the driver. The attack this defeats is old and still valid: a device node is just an inode carrying a major/minor number, so anyone who can create one on a filesystem you later mount — an image file, a USB stick, a tarball unpacked as root — could hand you a readable `/dev/mem` or a writable disk node. With `nodev` the node is inert. **`noexec`.** `execve` on a file under this mount fails with `EACCES`, and the kernel likewise refuses to map a file from the mount with execute permission. The idea is to stop dropped payloads in world-writable directories from being run. ```bash # a hardened entry for a scratch data mount UUID=6f3c1e6a-06a1-4c39-9ad3-2f9f2b7f8a11 /srv/upload ext4 defaults,nosuid,nodev,noexec,nofail 0 2 ``` ## Why noexec is the weakest of the three `noexec` blocks the kernel from executing the file. It does not stop anything from *reading* it. An interpreter reads its script as ordinary data and then does the work itself: ```bash bash /tmp/payload.sh # bash reads the file; execve is never called on it python3 /tmp/payload.py # same, for any interpreted language present ``` So on a machine with a shell, Python, Perl or any language runtime — which is every machine — `noexec` on `/tmp` stops naive `chmod +x && ./payload` and nothing more determined. An attacker can also simply write the payload somewhere that is executable, or hold it in memory. Treat it as raising the cost, not as a control you can rely on; the value is real but modest, and claiming otherwise in an interview is a red flag in itself. `nosuid` and `nodev`, by contrast, are genuinely hard: there is no interpreter trick that recovers a privilege transition the kernel declined to perform. ## They belong to the mount, not the filesystem Because the restriction is an attribute of the mount, the same filesystem mounted twice can be restricted at one path and unrestricted at the other — that is exactly what makes read-only or `nosuid` bind mounts useful. It is also the hole: if a filesystem carrying `nosuid` at `/srv/upload` is also bind-mounted somewhere without the option, the setuid bits are honoured through the second path. When you harden a mount, check that no other mount of the same filesystem undoes it, and remember that a bind mount does not inherit stricter options unless you set them on the bind. ## The other options that share the field The same options field carries a handful of non-security settings worth knowing. `ro` mounts read-only, which is the strongest of all — it is checked for every write regardless of ownership or capability. `noatime` stops the kernel updating each file's access time on read, removing a write from what should be a pure read path; `relatime`, the kernel default since 2.6.30, is the compromise that updates the access time only when it is older than the modification time or more than a day old, so explicit `noatime` matters much less than it did a decade ago. `defaults` expands to `rw,suid,dev,exec,auto,nouser,async` — note that it explicitly enables the three things above, so writing `defaults,nosuid` works because later options win, but writing `nosuid,defaults` does not. ## Where to apply them The standard hardening set is `nosuid,nodev,noexec` on `/tmp`, `/dev/shm` and `/var/tmp`; `nosuid,nodev` on `/home` (users legitimately run their own code, but should never gain privilege from it); `nosuid,nodev,noexec` on pure data mounts and on removable media. Applying `noexec` to `/var` or `/usr` breaks packages that ship helper binaries there, so it is not a blanket setting — verify against what actually runs on the machine before rolling it out.
- If nosuid ignores the setuid bit, does it also stop a setgid directory from setting group ownership on new files?No. `nosuid` suppresses privilege transitions at execution time — the setuid and setgid bits on executables, and file capabilities. The setgid bit on a *directory* is a filesystem behaviour: new entries inherit the directory's group rather than the creator's primary group. That is not a privilege escalation and the option does not affect it.
- Why can noexec on /home be more disruptive than noexec on /tmp?Because users legitimately execute code from their home directories — build outputs, virtualenv and node tool shims, language version managers, and locally installed binaries all run from there. On /tmp the only things executing are usually installers and payloads. The usual compromise is nosuid and nodev on /home, keeping the hard privilege restrictions, without noexec breaking normal developer workflows.
saying these in an interview costs you the question
- Claims noexec on /tmp prevents attackers running code
- Thinks nosuid removes the setuid bit from the file
- Believes the options are stored in the filesystem, not the mount
- Writes nosuid before defaults and expects it to apply
- Confuses nodev with disabling the /dev filesystem itself