GNU/Linux >> Tutoriales Linux >  >> Linux

Linux:¿cómo garantizar que una biblioteca compartida tendrá sus páginas de memoria compartidas por varios procesos?

Permítanme describir primero lo que quiero hacer, luego lo que logro hacer y finalmente mi problema.

Objetivo:implementar el ataque flush+flush cache en C

Estoy tratando de implementar en C el ataque de caché flush+flush (https://gruss.cc/files/flushflush.pdf). Básicamente, aprovecha el hecho de que dos procesos diferentes pueden compartir las mismas páginas de memoria cuando usan bibliotecas compartidas. Esto da como resultado un "uso compartido" del caché.

Supongamos que tenemos un proceso víctima, que se ejecuta continuamente y, a veces, ejecuta una función func importado de una biblioteca compartida.

Paralelamente, asumimos que tenemos un proceso de espionaje, ejecutándose en la misma computadora que la víctima, cuyo objetivo es espiar cuando la víctima llama a func . El espía también tiene acceso a la misma biblioteca compartida. El pseudocódigo del proceso de espionaje es el siguiente:

i=0;
for (i = 0; i < trace_length ; i++)
{
    trace[i] = flush_time( address of function "func");
    i++;
}

donde flush_time( <address> ) es una función que devuelve el tiempo que tarda la CPU en vaciar la memoria señalada por address de todos los niveles de caché. En el procesador Intel, esto se puede lograr a través de la instrucción de ensamblaje clflush . Se puede observar que la ejecución de clflush es más rápido cuando la dirección no está presente en el caché. Como resultado, el tiempo requerido para vaciar una dirección de memoria puede traducirse directamente en su presencia (o no) dentro del caché.

El proceso de espionaje devuelve un vector de seguimiento que contiene los resultados de flush_time a lo largo del tiempo. De la observación anterior, este rastro exhibirá tiempos más altos cuando la víctima también llame a la función func . El espía deducirá así cuando la víctima está llamando a func .

Lo que logro hacer:hacer que el ataque funcione contra la biblioteca compartida de GSL

Implementé el ataque antes mencionado, donde la biblioteca compartida es la GSL. Arbitrariamente, elegí gsl_stats_mean (definido en gsl_statistics_double ) para actuar como la función func Estoy dispuesto a espiar.

En ese caso, el espionaje funciona perfectamente ya que claramente puedo ver una diferencia de tiempo cuando el programa de la víctima hace llamadas a gsl_stats_mean

Mi problema:el ataque no funciona en una biblioteca compartida casera

Ahora quiero crear mi propia biblioteca compartida y usarla para la prueba de espía/víctima. Supongamos . denota la carpeta en la que mi spy.c y victim.c los archivos son. Creé dos archivos myl.c y myl.h en una carpeta ./src/myl , que contienen respectivamente la descripción de func y su declaración. Como antes, el objetivo de mi espía es detectar el uso de func de la víctima.

Ambos spy.c y victim.c contener la línea de inclusión:

 #include "src/myl/myl.h"

La creación de la biblioteca compartida se realiza mediante los siguientes comandos:

gcc -c -fPIC src/myl/myl.c -o bin/shared/myl.o  #creation of object in ./bin/shared
gcc -shared bin/shared/myl.o -o bin/shared/libmyl.so #creation of the shared library in ./bin/shared
gcc -c spy.c -o spy.o #creation of spy's process object file
gcc -c victim.c -o victim.o #creation of victim's process object file
gcc spy.o -Lbin/shared -lmyl -o spy #creation of spy's executable
gcc victim.o -Lbin/shared -lmyl -o victim #creation of victim's executable

Luego lanzo a mi víctima y espío usando las siguientes líneas:

LD_LIBRARY_PATH=$(pwd)/bin/shared ./victim
LD_LIBRARY_PATH=$(pwd)/bin/shared ./spy

Sin embargo, a diferencia del caso en el que estaba usando la función GSL, ya no puedo ver ninguna actividad en el caché. Supongo que esto significa que mis procesos de espía y víctima no comparten la misma página de memoria para mi biblioteca compartida (aunque, sin embargo, era el caso cuando usaba la GSL). Tenga en cuenta que al compilar de esta manera, el espionaje aún funciona cuando se dirige a una función GSL.

Mi pregunta principal es la siguiente:¿cómo garantizar que una biblioteca compartida compilada casera tenga la paginación de memoria compartida cuando varios procesos la ejecutan al mismo tiempo? Parece ser el caso de la biblioteca "adecuada" que instalé, como GSL, gmp, bibliotecas nativas, etc. Pero no por el que hice yo mismo.

Gracias de antemano y pido disculpas si la respuesta es sencilla.

EDITAR:salida de LD_DEBUG=libs y files tanto para el espía como para la víctima.
NOTA:la víctima se llama pg2 y el espía se llama pg1 (perdón por eso)

Primero, libs para la víctima, seguido de archivos para la víctima (pg2 ). Luego, libs para el espía, seguido de archivos para el espía (pg1 ):

LD_DEBUG=libs LD_LIBRARY_PATH=$(pwd)/bin/shared ./pg2
 31714: find library=libmyl.so [0]; searching
 31714:  search path=/home/romain/Documents/work/test/page_sharing/bin/shared/tls/x86_64:/home/romain/Documents/work/test/page_sharing/bin/shared/tls:/home/romain/Documents/work/test/page_sharing/bin/shared/x86_64:/home/romain/Documents/work/test/page_sharing/bin/shared      (LD_LIBRARY_PATH)
 31714:   trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/tls/x86_64/libmyl.so
 31714:   trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/tls/libmyl.so
 31714:   trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/x86_64/libmyl.so
 31714:   trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/libmyl.so
 31714: 
 31714: find library=libc.so.6 [0]; searching
 31714:  search path=/home/romain/Documents/work/test/page_sharing/bin/shared       (LD_LIBRARY_PATH)
 31714:   trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/libc.so.6
 31714:  search cache=/etc/ld.so.cache
 31714:   trying file=/lib/x86_64-linux-gnu/libc.so.6
 31714: 
 31714: 
 31714: calling init: /lib/x86_64-linux-gnu/libc.so.6
 31714: 
 31714: 
 31714: calling init: /home/romain/Documents/work/test/page_sharing/bin/shared/libmyl.so
 31714: 
 31714: 
 31714: initialize program: ./pg2
 31714: 
 31714: 
 31714: transferring control: ./pg2
 31714: 



LD_DEBUG=files LD_LIBRARY_PATH=$(pwd)/bin/shared ./pg2
 31901: 
 31901: file=libmyl.so [0];  needed by ./pg2 [0]
 31901: file=libmyl.so [0];  generating link map
 31901:   dynamic: 0x00007f5a3b34be48  base: 0x00007f5a3b14b000   size: 0x0000000000201028
 31901:     entry: 0x00007f5a3b14b580  phdr: 0x00007f5a3b14b040  phnum:                  7
 31901: 
 31901: 
 31901: file=libc.so.6 [0];  needed by ./pg2 [0]
 31901: file=libc.so.6 [0];  generating link map
 31901:   dynamic: 0x00007f5a3b144ba0  base: 0x00007f5a3ad81000   size: 0x00000000003c99a0
 31901:     entry: 0x00007f5a3ada1950  phdr: 0x00007f5a3ad81040  phnum:                 10
 31901: 
 31901: 
 31901: calling init: /lib/x86_64-linux-gnu/libc.so.6
 31901: 
 31901: 
 31901: calling init: /home/romain/Documents/work/test/page_sharing/bin/shared/libmyl.so
 31901: 
 31901: 
 31901: initialize program: ./pg2
 31901: 
 31901: 
 31901: transferring control: ./pg2
 31901: 


LD_DEBUG=libs LD_LIBRARY_PATH=$(pwd)/bin/shared ./pg1
 31938: find library=libmyl.so [0]; searching
 31938:  search path=/home/romain/Documents/work/test/page_sharing/bin/shared/tls/x86_64:/home/romain/Documents/work/test/page_sharing/bin/shared/tls:/home/romain/Documents/work/test/page_sharing/bin/shared/x86_64:/home/romain/Documents/work/test/page_sharing/bin/shared      (LD_LIBRARY_PATH)
 31938:   trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/tls/x86_64/libmyl.so
 31938:   trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/tls/libmyl.so
 31938:   trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/x86_64/libmyl.so
 31938:   trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/libmyl.so
 31938: 
 31938: find library=libgsl.so.23 [0]; searching
 31938:  search path=/home/romain/Documents/work/test/page_sharing/bin/shared       (LD_LIBRARY_PATH)
 31938:   trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/libgsl.so.23
 31938:  search cache=/etc/ld.so.cache
 31938:   trying file=/usr/local/lib/libgsl.so.23
 31938: 
 31938: find library=libgslcblas.so.0 [0]; searching
 31938:  search path=/home/romain/Documents/work/test/page_sharing/bin/shared       (LD_LIBRARY_PATH)
 31938:   trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/libgslcblas.so.0
 31938:  search cache=/etc/ld.so.cache
 31938:   trying file=/usr/local/lib/libgslcblas.so.0
 31938: 
 31938: find library=libc.so.6 [0]; searching
 31938:  search path=/home/romain/Documents/work/test/page_sharing/bin/shared       (LD_LIBRARY_PATH)
 31938:   trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/libc.so.6
 31938:  search cache=/etc/ld.so.cache
 31938:   trying file=/lib/x86_64-linux-gnu/libc.so.6
 31938: 
 31938: find library=libm.so.6 [0]; searching
 31938:  search path=/home/romain/Documents/work/test/page_sharing/bin/shared       (LD_LIBRARY_PATH)
 31938:   trying file=/home/romain/Documents/work/test/page_sharing/bin/shared/libm.so.6
 31938:  search cache=/etc/ld.so.cache
 31938:   trying file=/lib/x86_64-linux-gnu/libm.so.6
 31938: 
 31938: 
 31938: calling init: /lib/x86_64-linux-gnu/libc.so.6
 31938: 
 31938: 
 31938: calling init: /lib/x86_64-linux-gnu/libm.so.6
 31938: 
 31938: 
 31938: calling init: /usr/local/lib/libgslcblas.so.0
 31938: 
 31938: 
 31938: calling init: /usr/local/lib/libgsl.so.23
 31938: 
 31938: 
 31938: calling init: /home/romain/Documents/work/test/page_sharing/bin/shared/libmyl.so
 31938: 
 31938: 
 31938: initialize program: ./pg1
 31938: 
 31938: 
 31938: transferring control: ./pg1
 31938: 
0: 322 # just some output of my spying program
1: 323 # just some output of my spying program
 31938: 
 31938: calling fini: ./pg1 [0]
 31938: 
 31938: 
 31938: calling fini: /home/romain/Documents/work/test/page_sharing/bin/shared/libmyl.so [0]
 31938: 
 31938: 
 31938: calling fini: /usr/local/lib/libgsl.so.23 [0]
 31938: 
 31938: 
 31938: calling fini: /usr/local/lib/libgslcblas.so.0 [0]
 31938: 
 31938: 
 31938: calling fini: /lib/x86_64-linux-gnu/libm.so.6 [0]
 31938: 




LD_DEBUG=files LD_LIBRARY_PATH=$(pwd)/bin/shared ./pg1
 31940: 
 31940: file=libmyl.so [0];  needed by ./pg1 [0]
 31940: file=libmyl.so [0];  generating link map
 31940:   dynamic: 0x00007fb3d8794e48  base: 0x00007fb3d8594000   size: 0x0000000000201028
 31940:     entry: 0x00007fb3d8594580  phdr: 0x00007fb3d8594040  phnum:                  7
 31940: 
 31940: 
 31940: file=libgsl.so.23 [0];  needed by ./pg1 [0]
 31940: file=libgsl.so.23 [0];  generating link map
 31940:   dynamic: 0x00007fb3d8582ac8  base: 0x00007fb3d8126000   size: 0x000000000046da60
 31940:     entry: 0x00007fb3d8180e30  phdr: 0x00007fb3d8126040  phnum:                  7
 31940: 
 31940: 
 31940: file=libgslcblas.so.0 [0];  needed by ./pg1 [0]
 31940: file=libgslcblas.so.0 [0];  generating link map
 31940:   dynamic: 0x00007fb3d8124df0  base: 0x00007fb3d7ee8000   size: 0x000000000023d050
 31940:     entry: 0x00007fb3d7eea120  phdr: 0x00007fb3d7ee8040  phnum:                  7
 31940: 
 31940: 
 31940: file=libc.so.6 [0];  needed by ./pg1 [0]
 31940: file=libc.so.6 [0];  generating link map
 31940:   dynamic: 0x00007fb3d7ee1ba0  base: 0x00007fb3d7b1e000   size: 0x00000000003c99a0
 31940:     entry: 0x00007fb3d7b3e950  phdr: 0x00007fb3d7b1e040  phnum:                 10
 31940: 
 31940: 
 31940: file=libm.so.6 [0];  needed by /usr/local/lib/libgsl.so.23 [0]
 31940: file=libm.so.6 [0];  generating link map
 31940:   dynamic: 0x00007fb3d7b1cd88  base: 0x00007fb3d7815000   size: 0x00000000003080f8
 31940:     entry: 0x00007fb3d781a600  phdr: 0x00007fb3d7815040  phnum:                  7
 31940: 
 31940: 
 31940: calling init: /lib/x86_64-linux-gnu/libc.so.6
 31940: 
 31940: 
 31940: calling init: /lib/x86_64-linux-gnu/libm.so.6
 31940: 
 31940: 
 31940: calling init: /usr/local/lib/libgslcblas.so.0
 31940: 
 31940: 
 31940: calling init: /usr/local/lib/libgsl.so.23
 31940: 
 31940: 
 31940: calling init: /home/romain/Documents/work/test/page_sharing/bin/shared/libmyl.so
 31940: 
 31940: 
 31940: initialize program: ./pg1
 31940: 
 31940: 
 31940: transferring control: ./pg1
 31940: 
0: 325 # just some output of my spying program
1: 327 # just some output of my spying program
 31940: 
 31940: calling fini: ./pg1 [0]
 31940: 
 31940: 
 31940: calling fini: /home/romain/Documents/work/test/page_sharing/bin/shared/libmyl.so [0]
 31940: 
 31940: 
 31940: calling fini: /usr/local/lib/libgsl.so.23 [0]
 31940: 
 31940: 
 31940: calling fini: /usr/local/lib/libgslcblas.so.0 [0]
 31940: 
 31940: 
 31940: calling fini: /lib/x86_64-linux-gnu/libm.so.6 [0]
 31940: 

Respuesta aceptada:

Dado que la salida de depuración del ld El enlazador/cargador dinámico confirma que tanto la victim y spy programas cargan el archivo de entrada correcto, el siguiente paso sería verificar si el kernel realmente ha configurado las páginas físicas donde libmyl.so se carga en la memoria para ser compartida entre la victim y spy .

En Linux esto es posible de verificar desde la versión 2.6.25 del kernel a través del pagemap interfaz en el kernel que permite que los programas del espacio de usuario examinen las tablas de páginas y la información relacionada mediante la lectura de archivos en /proc .

El procedimiento general para usar el mapa de páginas para averiguar si dos procesos comparten memoria es así:

  1. Leer/proc/<pid>/maps para que ambos procesos determinen qué partes del espacio de memoria se asignan a qué objetos.
  2. Seleccione los mapas que le interesen, en este caso las páginas a las que libmyl.so está mapeado.
  3. Abrir /proc/<pid>/pagemap . El pagemap consta de descriptores de mapas de página de 64 bits, uno por página. El mapeo entre la dirección de la página y la dirección de sus descriptores en el pagemap es dirección de la página/tamaño de la página * tamaño del descriptor . Busque los descriptores de las páginas que le gustaría examinar.
  4. Leer un descriptor de 64 bits como un número entero sin signo para cada página del pagemap .
  5. Compare el número de marco de página (PFN) en los bits 0-54 del descriptor de página entre libmyl.so páginas para victim y spy . Si los PFN coinciden, los dos procesos comparten las mismas páginas físicas.
Relacionado:Windows:¿cómo podría Windows NO corromper el sistema de archivos de Linux al estropear los esquemas de partición?

El siguiente código de ejemplo ilustra cómo el pagemap se puede acceder e imprimir desde dentro del proceso. Utiliza dl_iterate_phdr() para determinar la dirección virtual de cada biblioteca compartida cargada en el espacio de memoria de los procesos, luego busca e imprime el pagemap correspondiente de /proc/<pid>/pagemap .

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <inttypes.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <link.h>
#include <errno.h>
#include <error.h>

#define E_CANNOT_OPEN_PAGEMAP 1
#define E_CANNOT_READ_PAGEMAP 2

typedef struct __attribute__ ((__packed__)) {
    union {
        uint64_t pmd;
        uint64_t page_frame_number : 55;
        struct {
            uint64_t swap_type: 5;
            uint64_t swap_offset: 50;
            uint64_t soft_dirty: 1;
            uint64_t exclusive: 1;
            uint64_t zero: 4;
            uint64_t file_page: 1;
            uint64_t swapped: 1;
            uint64_t present: 1;
        };
    };
} pmd_t;


static int print_pagemap_for_phdr(struct dl_phdr_info *info,
                                  size_t size, void *data)
{
    struct stat statbuf;

    size_t pagesize = sysconf(_SC_PAGESIZE);

    char pagemap_path[BUFSIZ];
    int pagemap;

    uint64_t start_addr, end_addr;

    if (!strcmp(info->dlpi_name, "")) {
        return 0;
    }

    stat(info->dlpi_name, &statbuf);

    start_addr = info->dlpi_addr;
    end_addr = (info->dlpi_addr + statbuf.st_size + pagesize) & ~(pagesize-1);

     printf("n%10p-%10p %snn",
            (void *)start_addr,
            (void *)end_addr,
            info->dlpi_name);

     snprintf(pagemap_path, sizeof pagemap_path, "/proc/%d/pagemap", getpid());

     if ((pagemap = open(pagemap_path, O_RDONLY)) < 0) {
         error(E_CANNOT_OPEN_PAGEMAP, errno, 
               "cannot open pagemap: %s", pagemap_path);
     }

     printf("%10s %8s %7s %5s %8s %7s %7sn",
            "", "", "soft-", "", "file /", "", "");
     printf("%10s %8s %7s %5s %11s %7s %7sn",
            "address", "pfn", "dirty", "excl.",
            "shared anon", "swapped", "present");

     for (unsigned long i = start_addr; i < end_addr; i += pagesize) {
          pmd_t pmd;

          if (pread(pagemap, &pmd.pmd, sizeof pmd.pmd, (i / pagesize) * sizeof pmd) != sizeof pmd) {
              error(E_CANNOT_READ_PAGEMAP, errno,
                    "cannot read pagemap: %s", pagemap_path);
          }

          if (pmd.pmd != 0) {
              printf("0x%10" PRIx64 " %06" PRIx64 " %3d %5d %8d %9d %7dn", i,
                     (unsigned long)pmd.page_frame_number,
                     pmd.soft_dirty,
                     pmd.exclusive,
                     pmd.file_page,
                     pmd.swapped,
                     pmd.present);
          }
    }

    close(pagemap);

    return 0;
}

int main()
{
    dl_iterate_phdr(print_pagemap_for_phdr, NULL);

    exit(EXIT_SUCCESS);
}

La salida del programa debe ser similar a la siguiente:

$ sudo ./a.out

0x7f935408d000-0x7f9354256000 /lib/x86_64-linux-gnu/libc.so.6

                      soft-         file /                
   address      pfn   dirty excl. shared anon swapped present
0x7f935408d000 424416   1     0        1         0       1
0x7f935408e000 424417   1     0        1         0       1
0x7f935408f000 422878   1     0        1         0       1
0x7f9354090000 422879   1     0        1         0       1
0x7f9354091000 43e879   1     0        1         0       1
0x7f9354092000 43e87a   1     0        1         0       1
0x7f9354093000 424790   1     0        1         0       1
...

donde:

  • address es la dirección virtual de la página
  • pfn es el número de marco de página de las páginas
  • soft-dirty indica si el bit soft-dirty está configurado en las páginas Entrada de tabla de páginas (PTE).
  • excl. indica si la página está asignada exclusivamente (es decir, la página solo está asignada para este proceso).
  • file / shared anon indica si la página es una página de archivo o una página anónima compartida.
  • swapped indica si la página está actualmente intercambiada (implica present) es cero).
  • present indica si la página está actualmente presente en el conjunto residente de procesos (implica swapped es cero).
Relacionado:Linux básico (1) Hoja de trucos

(Nota:ejecuto el programa de ejemplo con sudo como desde Linux 4.0 solo usuarios con CAP_SYS_ADMIN la capacidad puede obtener PFN de /proc/<pid>/pagemap . A partir de Linux 4.2, el campo PFN se pone a cero si el usuario no tiene CAP_SYS_ADMIN . El motivo de este cambio es dificultar la explotación de otra vulnerabilidad relacionada con la memoria, el ataque Rowhammer, utilizando la información de la asignación virtual a física expuesta por los PFN.)

Si ejecuta el programa de ejemplo varias veces, debe notar que la dirección virtual de la página debe cambiar (debido a ASLR), pero el PFN para las bibliotecas compartidas que están en uso por otros procesos debe permanecer igual.

Si los PFN para libmyl.so coincidencia entre la victim y spy programa, comenzaría a buscar una razón por la cual el ataque falla en el propio código de ataque. Si los PFN no coinciden, los bits adicionales pueden dar alguna pista de por qué las páginas no están configuradas para compartirse. El pagemap los bits indican lo siguiente:

present file exclusive state:
   0      0     0      non-present
   1      1     0      file page mapped somewhere else
   1      1     1      file page mapped only here
   1      0     0      anonymous non-copy-on-write page (shared with parent/child)
   1      0     1      anonymous copy-on-write page (or never forked)

Copiar en páginas de escritura en (MAP_FILE | MAP_PRIVATE) las áreas son anónimas en este contexto.

Bonificación: Para obtener el número de veces una página ha sido mapeada, el PFN se puede usar para buscar la página en /proc/kpagecount . Este archivo contiene un recuento de 64 bits del número de veces que se mapea cada página, indexado por PFN.


Linux
  1. Cómo matar procesos en ejecución en Linux

  2. Introducción a las bibliotecas compartidas de Linux (Cómo crear bibliotecas compartidas)

  3. ¿Cómo enumerar los procesos adjuntos a un segmento de memoria compartida en Linux?

  4. Cómo usar la memoria compartida con Linux en C

  5. ¿Cómo hacer el control de versiones de una biblioteca compartida en Linux?

Cómo verificar la memoria compartida de Linux usando el comando ipcs

Cómo instalar la biblioteca Ncurses en Linux

Cómo borrar la memoria de intercambio en Linux

Cómo encontrar los principales procesos en ejecución por memoria y uso de CPU en Linux

¿Cómo calcular la utilización de la CPU de un proceso y todos sus procesos secundarios en Linux?

Cómo configurar googleTest como una biblioteca compartida en Linux