命令之前 bash stat() 和 access() 太多,正常吗?

命令之前 bash stat() 和 access() 太多,正常吗?

在指示运行的 shellstrace上运行,提供以下输出,该输出显示在执行实际二进制文件之前的大量统计信息:bashmkdirmkdir

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()我的问题是:经常被编辑是否正常(如果是的话,出于什么原因) ?输出行已编号,特别是我想知道2776一旦2771运行了该行将产生什么意义。另外,我的印象是 bash 可以保存从前2770到最后的所有系统调用execve,因为它stat应该立即提供信息?我缺少什么?

此后我寻求解释并检查了替代 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, , 与2768 PATH`2793行类似。2767are because of searching the executable in the various directories in the current

如果这是打折的,那么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()和access 组合似乎是 using库函数getgid()的标志。每次使用 都会导致使用所有、、和,因为bash 运行glibcint eaccess(const char *pathname, int mode);eaccessgeteuidgetguidgetuidgetgidaccessfindcmd.cfile_status函数又像这样运行 eaccess 两次。

#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 个系统调用。

答案1

你应该看看循环findcmd.c:find_user_command_in_path()

stat()被称为(从file_status()) 路径中的每个元素两次:一次通过find_in_path_element()at 行640一旦通过is_directory()at 线第645章

正如您所提到的,它也在file_status()所谓的之中eaccess()

虽然这可以优化,但请记住,这没什么大不了的,因为路径随后被散列,并且所有这些搜索和统计仅在第一次使用命令时发生。

答案2

看着bash的源代码, 答案是:

是的呼叫是正常的,这是由于多种因素造成的,包括

  1. bashfile_status运行一个包含调用的函数stat运行一个包含对和 在大多数 GNU/Linux 设置中分开的呼叫eaccess来自glibc
  2. glibceaccess运行再次 stat然后是一堆快乐的geteuidgetegidgetuidgetgid最后access(因为它可能没有记录任何有用的信息stat,也许 glibc 根本不想节省系统调用(上下文切换并不重要!)。
  3. bash想要确保它找到的可以迭代目录的文件PATH对于尝试这样做的用户来说确实是可执行和可读的。 (一项测试)
  4. bash运行一个hash表以减少连续调用的搜索路径(第二个测试file_status

这一切都会生成大量看似多余的系统调用。 PATH 中的每个 cantate 都会进行 6/11 次系统调用,一旦先前发现该命令位于哈希表中,就会进行另外 6/11 次系统调用,因此会检查该命令是否仍然有效。

file_statusbash 中的函数在findcmd.c我为我的 Linux 机器进行编译的情况下看起来是这样的(即 ifdefs 评估)

int
file_status (name)
     const char *name;
{
  struct stat finfo;
  int r;

  /* Determine whether this file exists or not. */
  if (stat (name, &finfo) < 0)
    return (0);

  /* If the file is a directory, then it is not "executable" in the
     sense of the shell. */
  if (S_ISDIR (finfo.st_mode))
    return (FS_EXISTS|FS_DIRECTORY);

  r = FS_EXISTS;

  /* 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 (exec_name_should_ignore (name) == 0 && eaccess (name, X_OK) == 0)
    r |= FS_EXECABLE;
  if (eaccess (name, R_OK) == 0)
    r |= FS_READABLE;

  return r;
}

stat()最初将运行一次,然后eaccess()依次运行两次(glibc函数运行:

  1. stat
  2. geteuid
  3. getguid
  4. getuid
  5. getgid
  6. access

因此负责原始 bash 输出的这一部分:

[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

相关内容