如果当前用户对文件仅具有执行(--x)权限,则解释器(由#!/path/to/interpreter
文件开头指定)在哪个用户下运行?
它不可能是当前用户,因为他没有读取该文件的权限。它不可能是 root,因为解释器中包含的任意代码都将获得 root 访问权限。
那么,解释器进程以哪个用户身份运行呢?
编辑:我认为我的问题假设该文件已经被充分读取以了解它指定的解释器,而实际上它不会走那么远。当前 shell(通常是 b/a/sh)解释执行目标文件的命令将尝试读取它,但会失败。
答案1
如果用户对可执行脚本没有读取权限,则尝试运行它将失败,除非她有CAP_DAC_OVERRIDE
能力(例如她是root):
$ cat > yup; chmod 100 yup
#! /bin/sh
echo yup
^D
$ ./yup
/bin/sh: 0: Can't open ./yup
解释器(无论失败还是成功)将始终以当前用户身份运行,忽略脚本的任何 setuid 位或 setcap 扩展属性。
可执行脚本与二进制文件的不同之处在于,解释器应该能够打开和读取它们才能运行它们。但是,请注意,它们只是作为参数传递给解释器,解释器可能根本不会尝试读取它们,而是执行完全不同的操作:
$ 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
当然,翻译员本身可能是 setuid 或cap_dac_override=ep
-setcap 二进制文件(或将脚本的路径作为参数传递给此类二进制文件),在这种情况下,它将以提升的权限运行,并且可以忽略任何文件权限。
Linux 上通过 binfmt_misc 无法读取的 setuid 脚本
在 Linux 上,您可以使用以下模块绕过可执行脚本的所有限制(并破坏您的系统;-))binfmt_misc
:
以 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
作为普通用户:
$ 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
雅皮士!
更多信息请参见Documentation/admin-guide/binfmt-misc.rst
在内核源代码中。
该-p
选项可能会导致某些 shell 出现错误(可以简单地删除该选项),但新版本的dash
和需要该选项,bash
以防止它们放弃特权即使没有要求。
答案2
执行脚本分两个阶段。首先,内核读取文件的开头并发现它以 开头#!
,因此它读取舍邦行并确定要调用哪个解释器。然后,内核将原始命令行转换为解释器的路径、shebang 行上的选项(如果有)、文件的路径和原始选项。然后它执行此命令行,就像这一直是命令行一样,但不进行任何进一步的 shebang 处理。
到目前为止,内核已检查调用者是否具有对该脚本文件的执行权限。读取权限尚未发挥作用。内核在执行文件的过程中读取文件的开头,因此这是由执行权限控制的,而不是由读取权限控制。
在第二阶段,解释器被执行。由于它在命令行上看到脚本文件名,因此它可能会尝试打开它。此时需要读取权限。如果解释器没有读取该文件的权限,则其open
调用将失败,然后解释器可能会打印一条错误消息并放弃。我说“大概”是因为这是每个明智的解释者所做的,但没有技术义务要求事情以这种方式发生。如果您在 shebang 行上使用的程序不是解释器,则在第一阶段不会有任何区别,但该程序将在第二阶段执行其操作。例如,以 开头的“脚本”#!/bin/echo
将仅打印脚本文件名和任何其他命令行参数,然后退出,并且该脚本只需为此可执行。
1许多内核,包括 Linux,只允许一种选择。
答案3
一般来说,即使您拥有该文件,没有“r”权限也无法执行脚本
$ ls -l tst
---x--x--x 1 sweh sweh 24 May 4 21:22 tst*
$ ./tst
/bin/bash: ./tst: Permission denied
$ sudo cat tst
#!/bin/bash
echo hello
根据您编辑的问题。
程序的这一#!
部分被内核解释为exec()
系统调用的一部分。因此,要达到这一点,脚本不需要可读。
在我的示例中,实际上发生的是内核将 my 转换./tst
为/bin/bash ./tst
调用。
这种转换解释了为什么脚本需要r
访问才能被处理,但内核只需要x
确定要使用的解释器。