Linux-based systems are vulnerable to symlink race attacks from unprivileged UID processes. For example, a PHP process on a shared hosting can create a symlink to /etc/passwd in a directory where Apache httpd will serve it to anybody on the Internet. For other examples of symlink-related issues, see


I'd like some way to protect all Linux systems (of various versions) from those suspect processes creating malicious symlinks.

There are some partial solutions out there, but usually they involve custom patching of the Linux kernel (which is a lot of work and a drag on update process) or they are not sufficiently restrictive. There is Kees Cook's patch to Linux in versions 3.6 and higher


but it is not present in all earlier Linux versions and it protects only from some symlink issues, not necessarily all of them (such as creating symlinks in non-sticky bit directories, which is still a problem).

What are some universal solutions to symlink race vulnerabilities applicable to all common Linux systems, from version 2.6.26 on?

One thing that comes to mind is setting up a global rule that prevents high UID users from ever creating a symlink. For some deployments (shared hosting), such restriction may be acceptable.

What is the easiest way to ban users from creating symlinks on systems with Linux kernel >=2.6.26? (symlinks however need to be supported for more privileged users, including root)

Which of these: SELinux, Seccomp-bpf, Apparmor is best suited for setting up such a restriction?

  • 1
    chroot jail?... – Attie Mar 14 '19 at 13:08
  • @Fox Symlinks have to be supported to have a functioning Linux system, I only want to restrict a subset of unix users. – Ján Lalinský Mar 14 '19 at 14:16
  • 1
    @domen Yes, it even is present on Debian 7 with 3.2.0-6, but as stated in the question, I'm looking for a more robust and more universal solution. – Ján Lalinský Mar 14 '19 at 14:56
  • 1
    Yes, so put apache in a chroot... then no matter what symlink it is given, it can't serve a system file. Also configure apache properly. You should configure it to refuse to serve all files and then make specific exceptions for specific directories. Apache is aware of symlinks and will apply its configuration accordingly. – Philip Couling Mar 14 '19 at 15:44
  • 1
    "httpd outside the chroot will serve the global /etc/passwd" my point was really to put httpd in a jail as well... that way /etc/passwd doesn't / shouldn't exist from its point of view. – Attie Mar 14 '19 at 15:56

Preventing Apache from creating symlinks probably doesn't break it.

On current Linux, there are two system calls that can create new symlinks: symlink and symlinkat.

man systemd.exec tells us that systemd can install a seccomp filter, to deny certain system calls: SystemCallFilter=~symlink symlinkat. And according to man systemd.unit, existing systemd service files can be amended using "drop-in" files e.g. /etc/systemd/system/httpd.service.d/nosymlink.conf.

EDIT: of course if you were running PHP as FastCGI in a separate systemd service, then that is the service you need to amend instead of httpd.

There is a massive caveat with this "blocklist" approach:

Upgrading your OS to a new kernel may add support for new system calls. Unfortunately even the newer symlinkat() did not add a flags argument. Therefore a new system call might need to be added in future, if the behaviour of symlinkat needs to be modified in any way. Ironically, the most likely reason for this is in order to add AT_BENEATH support for symlinkat. This could re-open the "vulnerability", e.g. if newer versions of GNU libc funnel all symlink creation through a new symlinkat2() system call.

  • And what if a file is created that is in it's binary form a symlink (like a compress TAR with symlinks inside of it)? – Serverfrog Mar 14 '19 at 16:11
  • @Serverfrog nothing. – sourcejedi Mar 14 '19 at 16:30
  • Thank you, the seccomp way via systemd setting is interesting. Unfortunately, it only works on systems with very recent systemd (it does not work on Centos 7 with systemd 219). – Ján Lalinský Mar 18 '19 at 16:25

You can use immutable bit option in Linux.

A file with an immutable attribute can not be:

  1. Modified
  2. Deleted
  3. Renamed
  4. No soft or hard link created by anyone including root user

Use chattr(check if your OS supports) command to set the attribute.

chattr +i file
chattr +i /path/to/filename

To list the properties of file wrt attribute bit

lsattr /etc/shadow

To remove immutable bit:

chattr -i file
chattr -i /path/to/filename
  • 4
    it does not prevent soft links being created to it. it prevents hard links being created to it. – sourcejedi Mar 14 '19 at 14:13

If you want to ban PHP reading /etc/passwd, then you must already ban it from executing arbitrary commands.

Then you already know how to ban it from calling symlink().

I note that PHP has deprecated and removed "safe mode", and there is still no official usage guidance for disable_functions.

  • How would I ban PHP from executing arbitrary syscalls? Via seccomp-bpf, or is there something more user-friendly? – Ján Lalinský Mar 14 '19 at 15:37
  • how did you ban it from executing arbitrary commands e.g. shell commands that read /etc/passwd ? – sourcejedi Mar 14 '19 at 15:38
  • 1
    I didn't, but the PHP process is restricted from reading /etc/passwd because it runs in chrooted environment where there is no global /etc/passwd. This does not solve the symlink problem though, because Apache isn't running in that chrooted environment and has to be able to read global /etc/passwd. – Ján Lalinský Mar 14 '19 at 15:46
  • @JánLalinský I see, then if there is no command you want PHP to be able to execute, you could use one of the recipes for disable_functions that disables executing programs, and just add symlink and eio_symlink to the list of functions. – sourcejedi Mar 14 '19 at 16:36
  • 1
    I know, symlink function in PHP and running /bin/ls is already disabled, I worry more about hacked PHP process which won't respect the PHP setting disable_functions. I do not want to rely on PHP doing my security checks. – Ján Lalinský Mar 14 '19 at 16:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.