任何人都可以详细解释以下发生了什么。假设我正在安装一个目录,其noexec
选项如下:
mount -o noexec /dev/mapper/fedora-data /data
因此,为了验证这一点,我运行了mount | grep data
:
/dev/mapper/fedora-data on /data type ext4 (rw,noexec,relatime,seclabel,data=ordered)
现在,/data
我正在创建一个简单的脚本,hello_world
如下所示:
#!/bin/bash
echo "Hello World"
whoami
因此,我使脚本可执行chmod u+x hello_world
(但这对带有选项的文件系统没有影响noexec
),并尝试运行它:
# ./hello_world
-bash: ./hello_world: Permission denied
但是,bash
对文件进行预处理会产生:
# bash hello_world
Hello World
root
然后我创建了一个简单的hello_world.c
包含以下内容的内容:
#include <stdio.h>
int main()
{
printf("Hello World\n");
return 0;
}
使用编译它 cc -o hello_world hello_world.c
现在运行:
# ./hello_world
-bash: ./hello_world: Permission denied
所以我尝试使用运行它
/lib64/ld-linux-x86-64.so.2 hello_world
错误:
./hello_world: error while loading shared libraries: ./hello_world: failed to map segment from shared object: Operation not permitted
所以这当然是正确的,因为ldd
返回以下内容:
ldd hello_world
ldd: warning: you do not have execution permission for `./hello_world'
not a dynamic executable
noexec
在另一个不适用挂载选项的系统上,我看到:
ldd hello_world
linux-vdso.so.1 (0x00007ffc1c127000)
libc.so.6 => /lib64/libc.so.6 (0x00007facd9d5a000)
/lib64/ld-linux-x86-64.so.2 (0x00007facd9f3e000)
现在我的问题是:为什么使用选项在文件系统上运行 bash 脚本可以noexec
工作,但c
编译后的程序却不行?幕后发生了什么?
答案1
两种情况发生的情况是相同的:要直接执行文件,需要设置执行位,并且无法使用 noexec 挂载文件系统。但这些事情并不能阻止任何事情阅读那些文件。
当 bash 脚本运行./hello_world
并且文件不可执行时(没有 exec 权限位,或者文件系统上的 noexec),该#!
行甚至没有检查,因为系统甚至不加载该文件。该脚本永远不会在相关意义上“执行”。
在这种情况下bash ./hello_world
,noexec 文件系统选项显然并不像您希望的那样智能。bash
运行的命令是,/bin/bash
并且/bin
不在带有 的文件系统上noexec
。所以,运行起来没有问题。系统并不关心 bash(或 python、perl 或其他)是解释器。它只是运行您给出的命令 ( /bin/bash
),参数恰好是一个文件。对于 bash 或其他 shell,该文件包含要执行的命令列表,但现在我们“过去”了任何要检查文件执行位的内容。该检查不对以后发生的事情负责。
考虑这种情况:
$ cat hello_world | /bin/bash
...或者对于那些不喜欢毫无意义地使用猫的人:
$ /bin/bash < hello_world
#!
文件开头的“shbang”序列只是一些很好的魔法,当您尝试将文件作为命令执行时,它可以有效地执行相同的操作。您可能会发现这篇 LWN.net 文章很有帮助:程序如何运行。
答案2
前面的答案解释了为什么当从命令行显式调用noexec
解释器(在您的情况下)时,该设置不会阻止脚本运行。/bin/bash
但如果这就是全部,那么这个命令也可以工作:
/lib64/ld-linux-x86-64.so.2 hello_world
正如您所指出的那样,这是行不通的。那是因为noexec
还有另一个效果。内核将不允许来自该文件系统的内存映射文件PROT_EXEC
启用。
内存映射文件用于多种场景。两种最常见的情况是针对可执行文件和库。当使用系统调用启动程序时execve
,内核将在内部为链接器和可执行文件创建内存映射。所需的任何其他库均由链接器通过启用的mmap
系统调用进行内存映射PROT_EXEC
。如果您尝试使用文件系统中的库,noexec
内核将拒绝执行该mmap
调用。
当您调用/lib64/ld-linux-x86-64.so.2 hello_world
系统execve
调用时,只会为链接器创建内存映射,链接器将打开hello_world
可执行文件并尝试以与为库所做的几乎相同的方式创建内存映射。此时内核拒绝执行调用mmap
,并且您会收到错误:
./hello_world: error while loading shared libraries: ./hello_world: failed to map segment from shared object: Operation not permitted
该noexec
设置仍然允许在没有执行权限的情况下进行内存映射(有时用于数据文件),并且它还允许正常读取文件,这就是bash hello_world
为您工作的原因。
答案3
以此方式执行命令:
bash hello_world
您bash
从文件中读取hello_world
(这不是禁止的)。
在其他情况下,操作系统尝试运行此文件hello_world
并因noexec
标志而失败
答案4
因为 bash 可执行文件并不驻留在所述文件系统上。