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, 2019 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. Mar 14, 2019 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. Mar 14, 2019 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. Mar 14, 2019 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, 2019 at 15:56

3 Answers 3


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, 2019 at 16:11
  • @Serverfrog nothing.
    – sourcejedi
    Mar 14, 2019 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). Mar 18, 2019 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, 2019 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? Mar 14, 2019 at 15:37
  • how did you ban it from executing arbitrary commands e.g. shell commands that read /etc/passwd ?
    – sourcejedi
    Mar 14, 2019 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. Mar 14, 2019 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, 2019 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. Mar 14, 2019 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.