If the user has no read permission on an executable script, then trying to run it will fail, unless she has the CAP_DAC_OVERRIDE
capability (eg. she's root):
$ cat > yup; chmod 100 yup
#! /bin/sh
echo yup
^D
$ ./yup
/bin/sh: 0: Can't open ./yup
The interpreter (whether failing or successful) will always run as the current user, ignoring any setuid bits or setcap extended attributes of the script.
Executable scripts are different from binaries in the fact that the interpreter should be able to open and read in order to run them. However, notice that they're simply passed as an argument to the interpreter, which may not try to read them at all, but do something completely different:
$ cat > interp; chmod 755 interp
#! /bin/sh
printf 'you said %s\n' "$1"
^D
$ cat > script; chmod 100 script
#! ./interp
nothing to see here
^D
$ ./script
you said ./script
Of course, the interpreter itself may be a setuid or cap_dac_override=ep
-setcap binary (or pass down the script's path as an argument to such a binary), in which case it will run with elevated privileges and could ignore any file permissions.
Unreadable setuid scripts on Linux via binfmt_misc
On Linux you can bypass all the restrictions on executable scripts (and wreck your system ;-)) by using the binfmt_misc
module:
As root:
# echo ':interp-test:M::#! ./interp::./interp:C' \
> /proc/sys/fs/binfmt_misc/register
# cat > /tmp/script <<'EOT'; chmod 4001 /tmp/script # just exec + setuid
#! ./interp
id -u
EOT
As an ordinary user:
$ echo 'int main(void){ dup2(getauxval(AT_EXECFD), 0); execl("/bin/sh", "sh", "-p", (void*)0); }' |
cc -include sys/auxv.h -include unistd.h -x c - -o ./interp
$ /tmp/script
0
Yuppie!
More information in Documentation/admin-guide/binfmt-misc.rst
in the kernel source.
The -p
option may cause an error with some shells (where it could be simply dropped), but is needed with newer versions of dash
and bash
in order to prevent them from dropping privileges even if not asked for.
execve
system call (which does not care about its reading perms), and only if that fails withENOEXEC
it will try to read it itself and run it as a script. Your assumptions from theEdit:
part are wrong.