Subverting the execute flag on Linux systems. Why is this possible?

Whilst reading this, I found the following exploit:

% cp /usr/bin/id ~
% chmod -x ~/id
% ls -al ~/id
-rw-r--r-- 1 edd edd 22020 2012-08-01 15:06 /home/edd/id
% ~/id
zsh: permission denied: /home/edd/id
% /lib/ld-linux.so.2 ~/id
uid=1001(edd) gid=1001(edd) groups=1001(edd),1002(wheel)

This snippet shows that we can trivially sidestep the filesystem's execute permissions as a normal unprivileged user. I ran this on an Ubuntu 12.04.

Whilst the Linux loader is a shared object according to file(1), it also has an entry point which allows it to be executed directly. When executed in this way the Linux loader acts as an interpreter for ELF binaries.

On my OpenBSD machine, however, this exploit is not effective, because you may not execute the loader as a program. The OpenBSD manual page says: "ld.so is itself a shared object that is initially loaded by the kernel.".

Try this on Solaris 9, and you will get a segfault. I am not sure what happens elsewhere.

My questions are therefore:

  • Why does the Linux loader (when executed directly) not check the filesystem attributes before interpreting an ELF binary?
  • Why implement a mechanism which is designed disallow execution of files, if it is so trivially sidestepped? Have I missed something?

The goal of the execute permission is not to prevent execution in general. It is (1) to tell progams which files are meant to be executed, and (2) to prevent execution as a privileged user, when the setuid bit (etc.) is specified.

The linker hack is less of an exploit than it seems. You could execute any non-executable file you have permissions to read even more easily:

$ cp unexecutable_file ~/runme
$ chmod +x ~/runme
$ ~/runme

See this discussion on the Arch Linux forum.

In summary:

Marking files that should be executed

When you write a shell script, you can mark it as executable with chmod +x. This hints to your shell that you intend it to be executable (otherwise for all the shell knows, it's just another plain text file). The shell can then show it in the tab-completion when you type ./Tab.

Similarly: something.d directories (e.g. init.d) contain startup or control shell scripts that are typically executed automatically by daemons. You might want to put a comment or README file in the directory as a plain text file. Or you might want to temporarily disable one of the scripts. You can do so by clearing the execute bit for that particular file. This tells the daemon to skip over it.

Preventing privileged execution

The setuid bit means that when you execute the file, it is executed as a specified user (e.g. root).

The forum post explains it well:

You want an executable to be setuid for some user, but you only want people from a specific group to be able to execute it as setuid. They can still execute it by copying, but the setuid flag gets lost, so they'll be executing it as themselves, rather than the user who owned the original file.


If you have read access to a file, you can always make a copy of it.

If you can make a personal copy, you can always mark that copy executable.

This doesn't explain the bahaviour of ld-linux but does indicate that it may not be a very useful security loophole.

If you want tighter security, consider SELinux


Looking at the question a little differently: as Mechanical Snail says, the execute permission on a file is not intended to prevent execution. However, the filesystem option "noexec" does prevent execution, and is not so easily circumvented (not supported by all filesystems, but certainly by the most popular linux ones). If the administrator wanted to prevent users running programs of their own, they could specify the noexec option on home and tmp directories, and any others where the users may be able to create files.

$ mount -o noexec /dev/sdd1 /test
$ cd /test
$ cp /usr/bin/id .
$ ./id
-bash: ./id: Permission denied

Apparently it used to be possible to circumvent the noexec option using the loader trick mentioned in the question, but that was fixed in the kernel a few versions back.

From http://linux.die.net/man/8/mount:

noexec

Do not allow direct execution of any binaries on the mounted filesystem. (Until recently it was possible to run binaries anyway using a command like /lib/ld*.so /mnt/binary. This trick fails since Linux 2.4.25 / 2.6.0.)