Alguém pode explicar em detalhes o que está acontecendo com o seguinte. Vamos imaginar que estou montando um diretório com noexec
opção da seguinte forma:
mount -o noexec /dev/mapper/fedora-data /data
Então, para verificar isso, executei mount | grep data
:
/dev/mapper/fedora-data on /data type ext4 (rw,noexec,relatime,seclabel,data=ordered)
Agora dentro /data
estou criando um script simples chamado hello_world
da seguinte forma:
#!/bin/bash
echo "Hello World"
whoami
Então eu fiz o script executável por chmod u+x hello_world
(isso, no entanto, não terá efeito em um sistema de arquivos com noexec
opções) e tentei executá-lo:
# ./hello_world
-bash: ./hello_world: Permission denied
No entanto, pré-pender bash
para o arquivo resulta em:
# bash hello_world
Hello World
root
Então criei um simples hello_world.c
com o seguinte conteúdo:
#include <stdio.h>
int main()
{
printf("Hello World\n");
return 0;
}
Compilei usando cc -o hello_world hello_world.c
Agora rodando:
# ./hello_world
-bash: ./hello_world: Permission denied
Então eu tentei executá-lo usando
/lib64/ld-linux-x86-64.so.2 hello_world
O erro:
./hello_world: error while loading shared libraries: ./hello_world: failed to map segment from shared object: Operation not permitted
Portanto, é claro que isso é verdade, pois ldd
retorna o seguinte:
ldd hello_world
ldd: warning: you do not have execution permission for `./hello_world'
not a dynamic executable
Em outro sistema em que noexec
a opção de montagem não se aplica, vejo:
ldd hello_world
linux-vdso.so.1 (0x00007ffc1c127000)
libc.so.6 => /lib64/libc.so.6 (0x00007facd9d5a000)
/lib64/ld-linux-x86-64.so.2 (0x00007facd9f3e000)
Agora minha pergunta é esta: Por que a execução de um script bash em um sistema de arquivos com noexec
opção funciona, mas não um c
programa compilado? O que está acontecendo sob o capô?
O que está acontecendo nos dois casos é o mesmo: para executar um arquivo diretamente, o bit de execução precisa ser definido, e o sistema de arquivos não pode ser montado noexec. Mas essas coisas não impedem nada de ler esses arquivos.
Quando o script bash é executado como
./hello_world
e o arquivo não é executável (sem bit de permissão exec ou noexec no sistema de arquivos), a#!
linha nem é verificada , porque o sistema nem carrega o arquivo. O script nunca é "executado" no sentido relevante.No caso de
bash ./hello_world
, bem, a opção do sistema de arquivos noexec simplesmente não é tão inteligente quanto você gostaria que fosse. Obash
comando executado é/bin/bash
, e/bin
não está em um sistema de arquivos comnoexec
. Então, ele roda sem problemas. O sistema não se importa que o bash (ou python ou perl ou qualquer outra coisa) seja um interpretador. Ele apenas executa o comando que você deu (/bin/bash
) com o argumento que por acaso é um arquivo. No caso do bash ou outro shell, esse arquivo contém uma lista de comandos a serem executados, mas agora estamos "passando" qualquer coisa que vá verificar os bits de execução do arquivo. Essa verificação não é responsável pelo que acontece depois.Considere este caso:
… ou para quem não gosta de Pointless Use of Cat:
A sequência "shbang"
#!
no início de um arquivo é apenas uma boa mágica para fazer efetivamente a mesma coisa quando você tenta executar o arquivo como um comando. Você pode achar útil este artigo do LWN.net: Como os programas são executados .As respostas anteriores explicam por que a
noexec
configuração não impede que um script seja executado quando o interpretador (no seu caso/bin/bash
) é chamado explicitamente na linha de comando. Mas se isso fosse tudo, este comando também teria funcionado:E como você observou que não funciona. Isso porque
noexec
tem outro efeito também. O kernel não permitirá arquivos mapeados de memória desse sistema de arquivos comPROT_EXEC
ativado.Os arquivos mapeados de memória são usados em vários cenários. Os dois cenários mais comuns são para executáveis e bibliotecas. Quando um programa é iniciado usando a
execve
chamada do sistema, o kernel criará internamente mapeamentos de memória para o vinculador e o executável. Quaisquer outras bibliotecas necessárias são mapeadas na memória pelo vinculador por meio dammap
chamada do sistema comPROT_EXEC
enabled. Se você tentasse usar uma biblioteca de um sistema de arquivos comnoexec
o kernel se recusaria a fazer ammap
chamada.Quando você invocou
/lib64/ld-linux-x86-64.so.2 hello_world
a chamada doexecve
sistema, apenas criará um mapeamento de memória para o vinculador e o vinculador abrirá ohello_world
executável e tentará criar um mapeamento de memória praticamente da mesma maneira que teria feito para uma biblioteca. E este é o ponto em que o kernel se recusa a realizar ammap
chamada e você recebe o erro:A
noexec
configuração ainda permite mapeamentos de memória sem permissão de execução (como às vezes é usado para arquivos de dados) e também permite a leitura normal de arquivos e é por isso quebash hello_world
funcionou para você.Executando o comando desta forma:
você faz a
bash
leitura do arquivohello_world
(o que não é proibido).Em outros casos, o sistema operacional tenta executar este arquivo
hello_world
e falha por causa donoexec
sinalizadorQuando você executa o script via bash, ele apenas lê o arquivo e o interpreta.
No entanto, quando você passa o nome para o kernel - ele realmente inspeciona o arquivo para "#!" e carrega o interpretador especificado de acordo com a opção do kernel "CONFIG_BINFMT_SCRIPT". Diz:
O texto acima é o texto de ajuda associado a esta opção. Para outra diferença interessante. Eu escrevi meu roteiro:
Executá-lo com o bash ainda executa o interpretador do bash:
No entanto, o kernel agora faz:
O kernel imprimiu o script incluindo a 1ª linha porque chamou 'cat'.
No caso do programa C, você não está chamando um interpretador para executar o binário. O kernel tenta executá-lo diretamente. Ainda assim, se você carregou todo o seu executável na memória usando alguns depuradores, ainda poderá "executar" seu programa enquanto ele está sendo carregado pelo depurador.
A opção 'noexec' é como desligar o bit de execução em seu binário e desabilita o kernel de executar o binário "nativamente".
Isso faz diferença, BTW, se o seu programa tiver um bit SetUID definido no programa - carregá-lo com um interpretador não definirá seu UID, somente quando o kernel for carregado, esse privilégio poderá ser ativado.
FWIW -- o Windows tem o mesmo tipo de mecanismo.
Se você adicionar ".sh" como um "sufixo executável" como ".exe" ou ".vbs", o Windows executará automaticamente seu arquivo de acordo com a configuração dos arquivos ".sh" a serem executados. Teoricamente, você pode configurar arquivos ".txt" para serem digitados automaticamente se você inserir o nome deles na linha de comando.
Da mesma forma, você pode fazer uma chamada curta para um programa para imprimir arquivos de texto na tela. Essa é uma razão para não deixar você logado em um local público.
Porque o executável bash não reside no referido sistema de arquivos.