任何人都可以详细解释以下内容。假设我正在安装一个带有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)
现在我的问题是:为什么在带有noexec
选项的文件系统上运行 bash 脚本而不是c
编译程序?引擎盖下发生了什么?
两种情况的情况是一样的:直接执行文件,需要设置执行位,文件系统不能挂载noexec。但是这些东西并不能阻止任何东西读取这些文件。
当 bash 脚本以 as 运行
./hello_world
并且文件不可执行(没有 exec 权限位或文件系统上的 noexec )时,甚至不会检查#!
该行,因为系统甚至不会加载文件。该脚本永远不会在相关意义上“执行”。在这种情况下
bash ./hello_world
,noexec 文件系统选项只是普通的并不像您希望的那样聪明。运行的bash
命令是/bin/bash
,并且/bin
不在带有noexec
. 所以,它运行没有问题。系统并不关心 bash(或 python 或 perl 或其他)是解释器。它只是运行您给出的命令 (/bin/bash
) 并带有恰好是一个文件的参数。对于 bash 或其他 shell,该文件包含要执行的命令列表,但现在我们“过去”了任何要检查文件执行位的内容。该检查不对以后发生的事情负责。考虑这种情况:
......或者对于那些不喜欢毫无意义地使用猫的人:
#!
当您尝试将文件作为命令执行时,文件开头的“shbang”序列只是一个很好的魔法,可以有效地做同样的事情。您可能会发现这篇 LWN.net 文章很有帮助:程序如何运行。以前的答案解释了为什么当从命令行显式调用
noexec
解释器(在您的情况下)时,该设置不会阻止脚本运行。/bin/bash
但是,如果仅此而已,则此命令也可以正常工作:正如你指出的那样,这是行不通的。那是因为
noexec
还有另一个影响。内核将不允许来自该文件系统的内存映射文件PROT_EXEC
启用。内存映射文件用于多种场景。两个最常见的场景是可执行文件和库。当使用
execve
系统调用启动程序时,内核将在内部为链接器和可执行文件创建内存映射。所需的任何其他库都是链接器通过启用的mmap
系统调用映射的内存。PROT_EXEC
如果您尝试使用带有noexec
内核的文件系统中的库,则会拒绝执行该mmap
调用。当您调用系统调用时
/lib64/ld-linux-x86-64.so.2 hello_world
,execve
只会为链接器创建内存映射,链接器将打开hello_world
可执行文件并尝试以几乎与库相同的方式创建内存映射。这就是内核拒绝执行mmap
调用并且您收到错误的地方:该
noexec
设置仍然允许在没有执行权限的情况下进行内存映射(有时用于数据文件),它还允许正常读取文件,这就是为什么bash hello_world
对您有用。以这种方式执行命令:
你
bash
从文件中读取hello_world
(这是不被禁止的)。在其他情况下,操作系统尝试运行此文件
hello_world
并由于noexec
标志而失败当您通过 bash 运行脚本时,它只是读入文件并解释它。
但是,当您将名称传递给内核时——它确实会检查文件中的“#!” 并加载根据内核选项“CONFIG_BINFMT_SCRIPT”指定的解释器。它说:
以上是与此选项相关的帮助文本。另一个有趣的区别。我写了我的脚本:
用 bash 运行它仍然运行 bash 的解释器:
但是内核现在可以:
内核打印出包含第一行的脚本,因为它称为“cat”。
对于 C 程序,您不会调用解释器来运行二进制文件。内核尝试直接运行它。尽管如此,如果您使用某些调试器将所有可执行文件加载到内存中,您仍然可以在通过调试器加载程序时“运行”您的程序。
'noexec' 选项就像关闭二进制文件上的执行位并禁用内核“本机”运行二进制文件。
顺便说一句,这确实会有所不同,如果您的程序在程序上设置了 SetUID 位 - 使用解释器加载它不会设置您的 UID,只有当内核加载它时才能启用该权限。
FWIW——windows 具有相同类型的机制。
如果您将“.sh”添加为“.exe”或“.vbs”之类的“可执行后缀”,Windows 将根据您设置要执行的“.sh”文件的方式自动运行您的文件。从理论上讲,如果您在命令行中输入文件名,您可以设置“.txt”文件自动输出。
同样,您可以对程序进行一些简短的调用,以将文本文件打印到屏幕上。这就是不要让您的自我登录在公共场所的原因。
因为 bash 可执行文件不驻留在所述文件系统上。