Permiso de ejecución inesperado de mmap cuando archivos de ensamblaje incluidos en el proyecto


94

Estoy golpeando mi cabeza contra la pared con esto.

En mi proyecto, cuando estoy asignando memoria con mmapel mapeo ( /proc/self/maps) muestra que es una región legible y ejecutable a pesar de que solo solicité memoria legible.

Después de analizar strace (que se veía bien) y otra depuración, pude identificar lo único que parece evitar este extraño problema: eliminar archivos de ensamblaje del proyecto y dejar solo C. (¡¿qué ?!)

Así que aquí está mi extraño ejemplo, estoy trabajando en Ubunbtu 19.04 y por defecto gcc.

Si compila el ejecutable de destino con el archivo ASM (que está vacío), mmapdevuelve una región legible y ejecutable, si construye sin él se comportará correctamente. Vea el resultado /proc/self/mapsque he incrustado en mi ejemplo.

ejemplo.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 : es un archivo vacío!

Salidas

Con la versión ASM incluida

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

Sin la versión incluida de ASM

VirtualBox:~/mechanics/build$ gcc example.c -o example && ./example
7f1569296000-7f1569298000 r--p 00000000 00:00 0 

55
Esto es muy raro.
fuz

66
Logré reproducir esto solo con GCC (sin CMake), así que edité la pregunta para hacer que el ejemplo sea más mínimo.
Joseph Sible-Reinstate a Monica el


Puede que tengas razón, parte de la respuesta de él tiene que ser sobre READ_IMPLIES_EXEC persona
Ben Hirschberg

Reúna sus archivos fuente con -Wa,--noexecstack.
jww

Respuestas:


90

Linux tiene un dominio de ejecución llamado READ_IMPLIES_EXEC, que hace PROT_READque también se den todas las páginas asignadas con PROT_EXEC. Este programa le mostrará si está habilitado para sí mismo:

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

Si compila eso junto con un .sarchivo vacío , verá que está habilitado, pero sin uno, estará deshabilitado. El valor inicial de esto proviene de la metainformación ELF en su binario . Hacer readelf -Wl example. Verá esta línea cuando compiló sin el .sarchivo vacío :

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

Pero este cuando lo compiló:

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

Nota en RWElugar de solo RW. La razón de esto es que el enlazador supone que sus archivos de ensamblaje requieren read-implica-exec a menos que se indique explícitamente que no lo hacen, y si alguna parte de su programa requiere read-implica-exec, entonces está habilitado para todo su programa . Los archivos de ensamblaje que compila GCC le dicen que no necesita esto, con esta línea (verá esto si compila con -S):

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

Pon esa línea example.sy servirá para decirle al enlazador que tampoco la necesita, y tu programa funcionará como se espera.


13
Santa mierda, ese es un extraño defecto. Supongo que la cadena de herramientas existía antes de noexec, y hacer que noexec sea el predeterminado podría haber roto las cosas. ¡Ahora tengo curiosidad de cómo otros ensambladores como NASM / YASM crean .oarchivos! Pero de todos modos, supongo que este es el mecanismo que gcc -zexecstackusa, y por qué hace que no solo la pila sino todo sea ejecutable.
Peter Cordes

23
@ Peter - Es por eso que proyectos como Botan, Crypto ++ y OpenSSL, que usan el ensamblador, agregan -Wa,--noexecstack. Creo que es un filo muy desagradable. La pérdida silenciosa de nx-stacks debería ser una vulnerabilidad de seguridad. La gente de Binutil debería arreglarlo.
jww

14
@jww De hecho, es un problema de seguridad, extraño que nadie lo haya informado antes
Ben Hirschberg

44
+1, pero esta respuesta sería mucho mejor si .note.GNU-stack,"",@progbitsse explicara el significado / la lógica de la línea ; en este momento es opaca, equivalente a "esta cadena mágica de caracteres causa este efecto", pero la cadena claramente parece que tiene algún tipo de semántica.
mtraceur

33

Como alternativa a modificar sus archivos de ensamblaje con variantes de directivas de sección específicas de GNU, puede agregar -Wa,--noexecstacka su línea de comando para construir archivos de ensamblaje. Por ejemplo, vea cómo lo hago en musl's configure:

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

Creo que al menos algunas versiones de clang con ensamblador integrado pueden requerir que se pase como --noexecstack(sin el -Wa), por lo que su script de configuración probablemente debería verificar ambos y ver cuál es aceptado.

También puede usar -Wl,-z,noexecstacken el momento del enlace (in LDFLAGS) para obtener el mismo resultado. La desventaja de esto es que no ayuda si su proyecto produce .aarchivos de biblioteca estática ( ) para su uso por otro software, ya que entonces no controla las opciones de tiempo de enlace cuando lo utilizan otros programas.


1
Hmm ... No sabía que eras Rich Felker antes de leer esta publicación. ¿Por qué tu nombre de usuario no es Dalias?
SS Anne
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.