在指示运行的 shell 上运行提供strace
此输出,该输出在执行实际二进制文件之前显示大量统计信息:bash
mkdir
mkdir
BASH$> strace -f sh -c "bash -c \"mkdir /tmp\" 2>&1 | nl | grep -e "execve\|stat\|access"
[.....]
2766 [pid 17371] stat(".", {st_mode=S_IFDIR|0750, st_size=17262, ...}) = 0
2767 [pid 17371] stat("/usr/local/sbin/mkdir", 0x7ffd87aad0a0) = -1 ENOENT 2767 (No such file or directory)
2768 [pid 17371] stat("/usr/local/bin/mkdir", 0x7ffd87aad0a0) = -1 ENOENT (No such file or directory)
2769 [pid 17371] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
2770 [pid 17371] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
2771 [pid 17371] access("/usr/bin/mkdir", X_OK) = 0
2772 [pid 17371] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
2773 [pid 17371] access("/usr/bin/mkdir", R_OK) = 0
2774 [pid 17371] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
2775 [pid 17371] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
2776 [pid 17371] access("/usr/bin/mkdir", X_OK) = 0
2777 [pid 17371] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
2778 [pid 17371] access("/usr/bin/mkdir", R_OK) = 0
2779 [pid 17371] execve("/usr/bin/mkdir", ["mkdir", "/tmp"], 0x557ec7e15920 /* 5 vars */) = 0
我的问题是:是否正常(如果是,是什么原因)被/usr/bin/mkdir
stat()
大量使用?输出行已编号,特别是我想知道 line 2776
make once2771
已经运行有什么意义。此外,我的印象是 bash 可以保存从2770
开始到最终execve
的所有系统调用,因为它stat
应该立即提供信息?我错过了什么?
从那以后,我一直在寻求解释并检查了 shell 的替代 shell 的dash
行为方式它也显示了一些stat()
ing :
DASH$> strace -f sh -c "dash -c \"mkdir /tmp\" 2>&1 | nl | grep -e "execve\|stat\|access"
[....]
2792 [pid 17372] stat("/usr/local/sbin/mkdir", 0x7ffc66010b50) = -1 ENOENT (No such file or directory)
2793 [pid 17372] stat("/usr/local/bin/mkdir", 0x7ffc66010b50) = -1 ENOENT (No such file or directory)
2794 [pid 17372] stat("/usr/sbin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
2795 [pid 17372] execve("/usr/sbin/mkdir", ["mkdir", "/run"], 0x55d8d3453bb8 /* 6 vars */) = 0
我知道行2792
,2793
类似于行2767
2768 are because of searching the executable in the various directories in the current
PATH`。
如果打折,那么dash
只做一个统计,bash
做 10 个。再次提出问题:这正常吗?
更新:在 bash 统计中
有更多geteuid()
, getguid()
,getuid()
和getgid()
混杂
BASH$>strace -f sh -c "bash -c \"mkdir /tmp\"" 2>&1 | grep -e "execve\|stat\|access\|geteuid\|getegid\|getuid\|getgid"
[....]
[pid 24534] stat("/usr/local/bin/mkdir", 0x7fffda480f30) = -1 ENOENT (No such file or directory)
[pid 24534] stat("/usr/local/sbin/mkdir", 0x7fffda480f30) = -1 ENOENT (No such file or directory)
[pid 24534] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
[pid 24534] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
[pid 24534] geteuid() = 1000
[pid 24534] getegid() = 1000
[pid 24534] getuid() = 1000
[pid 24534] getgid() = 1000
[pid 24534] access("/usr/bin/mkdir", X_OK) = 0
[pid 24534] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
[pid 24534] geteuid() = 1000
[pid 24534] getegid() = 1000
[pid 24534] getuid() = 1000
[pid 24534] getgid() = 1000
[pid 24534] access("/usr/bin/mkdir", R_OK) = 0
[pid 24534] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
[pid 24534] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
[pid 24534] geteuid() = 1000
[pid 24534] getegid() = 1000
[pid 24534] getuid() = 1000
[pid 24534] getgid() = 1000
[pid 24534] access("/usr/bin/mkdir", X_OK) = 0
[pid 24534] stat("/usr/bin/mkdir", {st_mode=S_IFREG|0755, st_size=51136, ...}) = 0
[pid 24534] geteuid() = 1000
[pid 24534] getegid() = 1000
[pid 24534] getuid() = 1000
[pid 24534] getgid() = 1000
[pid 24534] access("/usr/bin/mkdir", R_OK) = 0
[pid 24534] execve("/usr/bin/mkdir", ["mkdir", "/tmp"], 0x55adcd4dc040 /* 55 vars */) = 0
所以也许这可以为 bash 的“这里发生的事情”提供线索?是否正在做一些检查以防止setuid
漏洞利用?
**更新 2:** geteuid()
、getguid()
、getuid()
和getgid()
access 组合似乎是 using库函数glibc
的标志。int eaccess(const char *pathname, int mode);
每次使用 都会eaccess
导致使用 all geteuid
、getguid
、和getuid
,因为 bash 运行findcmd.c的函数,而该函数又像这样运行 eaccess 两次。getgid
access
file_status
#if defined (HAVE_EACCESS)
/* Use eaccess(2) if we have it to take things like ACLs and other
file access mechanisms into account. eaccess uses the effective
user and group IDs, not the real ones. We could use sh_eaccess,
but we don't want any special treatment for /dev/fd. */
if (eaccess (name, X_OK) == 0)
r |= FS_EXECABLE;
if (eaccess (name, R_OK) == 0)
r |= FS_READABLE;
其中每个 eaccess 可能链接到 4 个系统调用。
你应该看看 中的循环
findcmd.c:find_user_command_in_path()
。stat()
file_status()
为路径中的每个元素调用 (from ) 两次:一次 viafind_in_path_element()
在第640行,一次 viais_directory()
在第645行。正如你提到的,它也在
file_status()
那个eaccess()
被调用的地方。虽然这可以优化,但请记住这没什么大不了的,因为路径随后被散列,并且所有这些搜索和统计仅在第一次使用命令时发生。
查看bash 的源代码,答案是:
是的,通话是正常的,它们是由于多种因素造成的,包括
bash
运行一个file_status
包含对的调用的函数,stat
在大多数 GNU/Linux 中设置两个单独的调用eaccess
fromglibc
glibc
的eaccess
本身再次stat
运行,然后是一大堆快乐的geteuid
,getegid
,getuid
,getgid
最后access
(因为它可能没有从它的 inital 记录任何有用的信息stat
,也许 glibc 根本不想节省系统调用(上下文切换无关紧要!)。bash
想要确保它找到的可以遍历目录的PATH
文件对于尝试这样做的用户来说确实是可执行和可读的。(一测)bash
运行一个hash
表以减少连续调用的搜索路径(第二个测试与file_status
)然后,这一切都会生成许多类似的多余系统调用。PATH 中的每个 cantate 的 6/11 系统调用和另一个 6/11 系统调用,一旦该命令先前被发现在哈希表中并因此被检查是否仍然有效。
在我为我的 linux box 编译的情况下,
file_status
bash 中的函数看起来像这样(即评估的 ifdefs)findcmd.c
stat()
最初将运行一次,然后依次运行eaccess()
两次(glibc
函数运行:stat
geteuid
getguid
getuid
getgid
access
)因此负责原始 bash 输出的这一部分: