Executing a bash script or a c binary on a file system with noexec option
What's happening in both cases is the same: to execute a file directly, the execute bit needs to be set, and the filesystem can't be mounted noexec. But these things don't stop anything from reading those files.
When the bash script is run as ./hello_world
and the file isn't executable (either no exec permission bit, or noexec on the filesystem), the #!
line isn't even checked, because the system doesn't even load the file. The script is never "executed" in the relevant sense.
In the case of bash ./hello_world
, well, The noexec filesystem option just plain isn't as smart as you'd like it to be. The bash
command that's run is /bin/bash
, and /bin
isn't on a filesystem with noexec
. So, it runs no problem. The system doesn't care that bash (or python or perl or whatever) is an interpreter. It just runs the command you gave (/bin/bash
) with the argument which happens to be a file. In the case of bash or another shell, that file contains a list of commands to execute, but now we're "past" anything that's going to check file execute bits. That check isn't responsible for what happens later.
Consider this case:
$ cat hello_world | /bin/bash
… or for those who do not like Pointless Use of Cat:
$ /bin/bash < hello_world
The "shbang" #!
sequence at the beginning of a file is just some nice magic for doing effectively the same thing when you try to execute the file as a command. You might find this LWN.net article helpful: How programs get run.
Previous answers explain why the noexec
setting doesn't prevent a script from being run when the interpreter (in your case /bin/bash
) is explicitly called from the command line. But if that was all there was to it, this command would have worked as well:
/lib64/ld-linux-x86-64.so.2 hello_world
And as you noted that doesn't work. That's because noexec
has another effect as well. The kernel will not allow memory mapped files from that file system with PROT_EXEC
enabled.
Memory mapped files are used in multiple scenarios. The two most common scenarios are for executables and libraries. When a program is started using the execve
system call, the kernel will internally create memory mappings for the linker and executable. Any other libraries needed are memory mapped by the linker through the mmap
system call with PROT_EXEC
enabled. If you tried to use a library from a filesystem with noexec
the kernel would refuse to do the mmap
call.
When you invoked /lib64/ld-linux-x86-64.so.2 hello_world
the execve
system call will only create a memory mapping for the linker and the linker will open the hello_world
executable and attempt to create a memory mapping in pretty much the same way it would have done for a library. And this is the point at which the kernel refuse to perform the mmap
call and you get the error:
./hello_world: error while loading shared libraries: ./hello_world: failed to map segment from shared object: Operation not permitted
The noexec
setting still allows memory mappings without execute permission (as is sometimes used for data files) and it also allows normal reading of files which is why bash hello_world
worked for you.
Executing command on this way:
bash hello_world
you make bash
read from file hello_world
(which is not forbidden).
In other cases OS try to run this file hello_world
and fail because of noexec
flag