Permissão de execução inesperada do mmap quando arquivos de montagem incluídos no projeto

94

Estou batendo minha cabeça na parede com isso.

No meu projeto, quando estou alocando memória com mmapo mapping ( /proc/self/maps) mostra que é uma região legível e executável, apesar de ter solicitado apenas memória legível.

Depois de analisar o strace (que estava com boa aparência) e outras depurações, consegui identificar a única coisa que parece evitar esse problema estranho: remover arquivos de montagem do projeto e deixar apenas C. puro (o que ?!)

Então, aqui está meu exemplo estranho, estou trabalhando no Ubunbtu 19.04 e no gcc padrão.

Se você compilar o executável de destino com o arquivo ASM (que está vazio), mmapretornará uma região legível e executável, se você construir sem ele, ele se comportará corretamente. Veja a saída da /proc/self/mapsqual eu integrei no meu exemplo.

example.c

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

int main()
{
    void* p;
    p = mmap(NULL, 8192,PROT_READ,MAP_ANONYMOUS|MAP_PRIVATE,-1,0);

    {
        FILE *f;
        char line[512], s_search[17];
        snprintf(s_search,16,"%lx",(long)p);
        f = fopen("/proc/self/maps","r");
        while (fgets(line,512,f))
        {
            if (strstr(line,s_search)) fputs(line,stderr);
        }

        fclose(f);
    }

    return 0;
}

example.s : é um arquivo vazio!

Saídas

Com a versão incluída do ASM

VirtualBox:~/mechanics/build$ gcc example.c example.s -o example && ./example
7f78d6e08000-7f78d6e0a000 r-xp 00000000 00:00 0 

Sem a versão incluída do ASM

VirtualBox:~/mechanics/build$ gcc example.c -o example && ./example
7f1569296000-7f1569298000 r--p 00000000 00:00 0 
Ben Hirschberg
fonte
5
Isso é seriamente estranho.
fuz 6/10/19
6
Consegui reproduzir isso apenas com o GCC (sem CMake), então editei a pergunta para tornar o exemplo mais minimalista.
Joseph Sible-Reinstate Monica
2
Possivelmente relacionado stackoverflow.com/questions/32730643/…
Sami Kuhmonen 6/19
Você pode estar certo, parte do que resposta tem que ser em torno de READ_IMPLIES_EXEC persona
Ben Hirschberg
Monte seus arquivos de origem com -Wa,--noexecstack.
JWW

Respostas:

90

O Linux possui um domínio de execução chamado READ_IMPLIES_EXEC, que faz com que todas as páginas alocadas PROT_READtambém sejam fornecidas PROT_EXEC. Este programa mostra se está habilitado para si:

#include <stdio.h>
#include <sys/personality.h>

int main(void) {
    printf("Read-implies-exec is %s\n", personality(0xffffffff) & READ_IMPLIES_EXEC ? "true" : "false");
    return 0;
}

Se você compilar isso junto com um .sarquivo vazio , verá que ele está ativado, mas sem um, ele será desativado. O valor inicial disso vem das meta-informações ELF no seu binário . Faça readelf -Wl example. Você verá esta linha quando compilar sem o .sarquivo vazio :

  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10

Mas este quando você compilou:

  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10

Observe em RWEvez de apenas RW. A razão para isso é que o vinculador pressupõe que seus arquivos de montagem requerem leitura implícita-exec, a menos que seja explicitamente informado que não o fazem, e se alguma parte do seu programa exigir leitura implícita-exec, ele será ativado para todo o programa . Os arquivos de montagem que o GCC compila informam que não precisa disso, com esta linha (você verá isso se compilar com -S):

        .section        .note.GNU-stack,"",@progbits

Coloque essa linha example.se servirá para informar ao vinculador que ele também não precisa, e seu programa funcionará conforme o esperado.

Joseph Sible-Restabelecer Monica
fonte
13
Caramba, isso é um padrão estranho. Eu acho que a cadeia de ferramentas existia antes do noexec, e tornar o noexec o padrão poderia ter quebrado as coisas. Agora estou curioso para saber como outros montadores, como NASM / YASM, criam .oarquivos! De qualquer forma, acho que esse é o mecanismo que gcc -zexecstackusa e por que ele torna não apenas a pilha, mas tudo executável.
Peter Cordes
23
@ Peter - É por isso que projetos como Botan, Crypto ++ e OpenSSL, que usam o assembler, adicionam -Wa,--noexecstack. Eu acho que é uma borda afiada muito desagradável. A perda silenciosa de nx-stacks deve ser uma vulnerabilidade de segurança. O pessoal da Binutil deve corrigi-lo.
JWW
14
@jww Na verdade, é um problema de segurança, por mais estranho que ninguém relatou isso antes
Ben Hirschberg
4
+1, mas essa resposta seria muito melhor se o significado / lógica da linha .note.GNU-stack,"",@progbitsfosse explicado - agora é opaco, equivalente a "essa sequência mágica de caracteres causa esse efeito", mas a sequência parece claramente ter algum tipo de semântica.
Mtraceur 8/10/19
33

Como alternativa para modificar seus arquivos de montagem com variantes de diretiva de seção específicas do GNU, você pode adicionar -Wa,--noexecstackà sua linha de comando para criar arquivos de montagem. Por exemplo, veja como eu faço isso no musl's configure:

https://git.musl-libc.org/cgit/musl/commit/configure?id=adefe830dd376be386df5650a09c313c483adf1a

Acredito que pelo menos algumas versões do clang com o assembler-assembler podem exigir que ele seja passado como --noexecstack(sem o -Wa), portanto, seu script de configuração provavelmente deve verificar os dois e ver qual é aceito.

Você também pode usar -Wl,-z,noexecstackno tempo do link (in LDFLAGS) para obter o mesmo resultado. A desvantagem disso é que não ajuda se o seu projeto produz .aarquivos de biblioteca estáticos ( ) para uso por outro software, já que você não controla as opções de tempo do link quando usadas por outros programas.

R .. GitHub PARE DE AJUDAR O GELO
fonte
11
Hmm ... eu não sabia que você era Rich Felker antes de ler este post. Por que o seu nome de exibição não é Dalias?
SS Anne