É normal que bash stat () e access (), antes dos comandos?

6

Executando strace no shell bash que é instruído a executar mkdir , desde que essa saída apresente muitas estatísticas antes de executar o% realmkdir binary:

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

A minha pergunta é: É normal (e se sim por que razão) é muito% /usr/bin/mkdir stat() ? As linhas de saída são numeradas e, em particular, pergunto-me em que sentido a linha 2776 fará quando 2771 já foi executada. Também fiquei com a impressão de que o bash poderia ter salvo todas as chamadas do sistema de 2770 para o final execve , pois o stat deveria ter fornecido as informações de uma só vez? O que estou perdendo?

Eu já procurei uma explicação e verifiquei como um shell alternativo que o dash shell iria se comportar também mostrava alguns 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

Estou ciente de que as linhas 2792 , 2793 , semelhantes às linhas 2767 , 2768 are because of searching the executable in the various directories in the current PATH '.

Se isso for descontado, dash fará apenas uma única estatística e bash fará 10. Novamente, levantando a questão: isso é normal?

UPDATE: Havia mais geteuid() , getguid() , getuid() e getgid() misturados nas estatísticas bash

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

então talvez isso possa dar uma pista do que está "acontecendo aqui" com o bash? Está fazendo alguma verificação para evitar setuid exploits?

** ATUALIZAÇÃO 2: ** A combinação geteuid() , getguid() , getuid() , getgid() e acesso parece ser a marca registrada da utilização da função de biblioteca glibc int eaccess(const char *pathname, int mode); . Cada uso de eaccess incorre no uso de todos os geteuid , getguid , getuid , getgid e access , já que o bash é executado findcmd.c file_status função que por sua vez executa eaccess duas vezes como esta.

#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;

em que cada acesso pode estar vinculado a 4 syscalls.

    
por humanityANDpeace 16.10.2018 / 20:09

2 respostas

3

Você deve analisar o loop em findcmd.c:find_user_command_in_path() .

stat() é chamado (de file_status() ) duas vezes para cada elemento no caminho: uma vez por meio de find_in_path_element() na linha 640 e uma vez via is_directory() na linha 645 .

Como você mencionou, também é em file_status() que eaccess() é chamado.

Embora isso possa ser otimizado, lembre-se de que não é grande coisa, porque o caminho é então dividido em hash, e toda essa pesquisa e estatísticas acontecem apenas na primeira vez que um comando é usado.

    
por 16.10.2018 / 22:31
0

Olhando para o código-fonte do bash , a resposta é:

Sim as chamadas são normais, devido a vários fatores, incluindo

  1. bash executa uma função file_status que inclui uma chamada para stat e na maioria das configurações GNU / Linux duas chamadas separadas para eaccess de glibc
  2. glibc eaccess próprio é executado novamente stat e, em seguida, todo o grupo feliz de geteuid , getegid , getuid , getgid e finalmente access (como talvez ele não tenha gravado nenhuma informação útil de seu inital stat e talvez a glibc simplesmente não tenha a sensação de salvar em syscalls (as opções de contexto não importam!).
  3. bash quer ter certeza de que o arquivo encontrado pode ser indexado pelos diretórios em PATH e, de fato, executável e legível para o usuário que está tentando fazer isso. (um teste)
  4. bash executa uma tabela hash para reduzir o caminho de pesquisa em invocações sucessivas (o teste secont com file_status )

Isso tudo gera as numerosas chamadas sys superfluentes. 6/11 syscalls para cada cantada no PATH e outro 6/11 syscalls uma vez que o comando foi encontrado anteriormente para estar na tabela de hash e é, portanto, verificado por ainda ser válido.

a função file_status no findcmd.c do bash parece no caso da minha compilação para minha caixa linux como esta (ou seja, o ifdefs é avaliado)

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;
}

que será executado uma vez stat() inicialmente e eaccess() duas vezes em turnos (sendo a função glibc executada:

  1. stat
  2. geteuid
  3. getguid
  4. getuid
  5. getgid
  6. %código% )

e, portanto, é responsável por esta parte da saída bash original:

[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
    
por 16.10.2018 / 21:55