Como garantir que uma biblioteca compartilhada tenha suas páginas de memória compartilhadas por vários processos?

2

Primeiro, descrevo o que quero fazer, depois o que consigo fazer e, finalmente, meu problema.

Objetivo: implementar o ataque de cache flush + flush em C

Estou tentando implementar, no C, o ataque flush + flush ( link ). Basicamente, ele explora o fato de que dois processos diferentes podem compartilhar as mesmas páginas de memória ao usar bibliotecas compartilhadas. Isso resulta em um "uso compartilhado" do cache.

Vamos supor que temos um processo de vítima, executando continuamente e, às vezes, executando uma função func importada de uma biblioteca compartilhada.

Em paralelo, assumimos que temos um processo de espionagem, rodando no mesmo computador que a vítima, cujo objetivo é espionar quando a vítima chama func . O espião também tem acesso à mesma biblioteca compartilhada. O pseudo-código do processo de espionagem é o seguinte:

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

onde flush_time( <address> ) é uma função que retorna o tempo que leva para a CPU liberar a memória apontada por address de todos os níveis de cache. No processador Intel, isso pode ser obtido através da instrução de montagem clflush . Pode-se observar que a execução de clflush é mais rápida quando o endereço não está presente no cache. Como resultado, o tempo necessário para liberar um endereço de memória pode ser diretamente traduzido em sua presença (ou não) dentro do cache.

O processo de espionagem retorna um vetor de rastreamento que contém os resultados flush_time ao longo do tempo. Da observação anterior, esse traço exibirá tempos mais altos quando a vítima também chamar a função func . O espião deduzirá assim quando a vítima estiver chamando func .

O que eu consigo fazer: fazer o ataque funcionar contra a biblioteca compartilhada da GSL

Implementei o ataque mencionado anteriormente, em que a biblioteca compartilhada é a GSL . Arbitrariamente, escolhi gsl_stats_mean (definido em gsl_statistics_double ) para atuar como a função func que estou disposto a espionar.

Nesse caso, a espionagem funciona perfeitamente, pois vejo claramente uma diferença de tempo quando o programa vítima faz chamadas para gsl_stats_mean

Meu problema: o ataque não funciona em uma biblioteca compartilhada caseira

Agora quero criar minha própria biblioteca compartilhada e usá-la para o teste de espião / vítima. Suponhamos que . indique a pasta na qual meus arquivos spy.c e victim.c são. Eu criei dois arquivos myl.c e myl.h em uma pasta ./src/myl , que contêm respectivamente a descrição de func e sua declaração. Como anteriormente, o objetivo do meu espião é detectar o uso de func da vítima.

Tanto spy.c como victim.c contêm a linha de inclusão:

 #include "src/myl/myl.h"

A criação da biblioteca compartilhada é feita usando os seguintes 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

Eu, então, lanço minha vítima e espio usando as seguintes linhas:

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

No entanto, ao contrário do caso em que eu estava usando a função GSL, não consigo mais ver nenhuma atividade no cache. Eu acho que isso significa que meus processos de espionagem e vítima não compartilham a mesma página de memória da minha biblioteca compartilhada (embora, no entanto, tenha sido o caso ao usar o GSL). Observe que, ao compilar dessa maneira, a espionagem ainda funciona ao segmentar uma função GSL.

Minha principal questão é a seguinte: como garantir que uma biblioteca compartilhada compilada caseira tenha a paginação de memória compartilhada ao ser executada por vários processos ao mesmo tempo? Parece ser o caso da biblioteca "adequada" que eu instalei, como o GSL, gmp, bibliotecas nativas, etc .... Mas não para o que eu fiz a mim mesmo.

Agradecemos antecipadamente e peço desculpas se a resposta for direta.

EDIT: saída de LD_DEBUG=libs e files para espião e vítima. NOTA: a vítima é chamada pg2 e o espião é chamado pg1 (desculpe por isso)

Primeiro, libs para vítima, seguidas por arquivos para vítima ( pg2 ). Então, libs para o espião, seguidas por arquivos para o espião ( 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: 
    
por Romain Poussier 24.07.2018 / 12:31

1 resposta

1

Como a saída de depuração do carregador / carregador dinâmico ld confirma que ambos os programas victim e spy carregam o arquivo de entrada correto, a próxima etapa seria verificar se o kernel realmente configurou o arquivo físico. páginas em que libmyl.so é carregado na memória para ser compartilhado entre victim e spy .

No Linux, isso é possível verificar desde a versão do kernel 2.6.25 através do pagemap interface no kernel que permite que os programas userspace examinem as tabelas de páginas e informações relacionadas lendo arquivos em /proc .

O procedimento geral para usar o pagemap para descobrir se dois processos compartilham memória é o seguinte:

  1. Leia /proc/<pid>/maps para ambos os processos para determinar quais partes do espaço da memória são mapeadas para quais objetos.
  2. Selecione os mapas em que você está interessado, neste caso, as páginas para as quais libmyl.so está mapeado.
  3. Abra /proc/<pid>/pagemap . O pagemap consiste em descritores de mapa de página de 64 bits, um por página. O mapeamento entre o endereço da página e o endereço dos descritores no pagemap é endereço da página / tamanho da página * tamanho do descritor . Procure os descritores das páginas que você gostaria de examinar.
  4. Leia um descritor de 64 bits como um inteiro não assinado para cada página do pagemap .
  5. Compare o número do quadro de página (PFN) nos bits 0 a 54 do descritor de página entre as páginas libmyl.so para victim e spy . Se as PFNs corresponderem, os dois processos compartilharão as mesmas páginas físicas.

O código de exemplo a seguir ilustra como o pagemap pode ser acessado e impresso a partir do processo. Ele usa dl_iterate_phdr() para determinar o endereço virtual de cada biblioteca compartilhada carregada em o espaço de memória do processo, depois procura e imprime o pagemap correspondente 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 %s\n\n",
            (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 %7s\n",
            "", "", "soft-", "", "file /", "", "");
     printf("%10s %8s %7s %5s %11s %7s %7s\n",
            "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 %7d\n", 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);
}

A saída do programa deve ser semelhante à seguinte:

$ 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
...

onde:

  • address é o endereço virtual da página
  • pfn é o número do quadro da página de páginas
  • soft-dirty indica que o bit suja e macia está definido nas páginas Entrada da tabela de páginas (PTE).
  • excl. indica se a página está exclusivamente mapeada (ou seja, a página é mapeada apenas para esse processo).
  • file / shared anon indica se a página é uma página de arquivos ou uma página anônima compartilhada.
  • swapped indica se a página está atualmente trocada (implica present é zero).
  • present indica se a página está atualmente presente no conjunto residente de processos (implica swapped é zero).

(Nota: Eu executo o programa de exemplo com sudo , já que desde o Linux 4.0 somente usuários com o recurso CAP_SYS_ADMIN podem obter PFNs de /proc/<pid>/pagemap . A partir do Linux 4.2 o campo PFN é zerado se o usuário não tiver CAP_SYS_ADMIN .O motivo para essa mudança é dificultar a exploração de outra vulnerabilidade relacionada à memória, o ataque de Rowhammer , usando as informações sobre o mapeamento virtual para físico exposto pelos PFNs.)

Se você executar o programa de exemplo várias vezes, deverá notar que o endereço virtual da página deve ser alterado (devido a ASLR ), mas o PFN para bibliotecas compartilhadas que estão em uso por outros processos deve permanecer o mesmo.

Se as PFNs para libmyl.so coincidirem entre o programa victim e spy , eu começaria procurando por um motivo pelo qual o ataque falha no próprio código de ataque. Se os PFNs não corresponderem, os bits adicionais podem dar alguma dica porque as páginas não estão configuradas para serem compartilhadas. Os pagemap bits indicam o seguinte :

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)

As páginas de copy-on-write em (MAP_FILE | MAP_PRIVATE) areas são anônimas neste contexto.

Bônus: Para obter o número de vezes em que uma página foi mapeada, o PFN pode ser usado para procurar a página em /proc/kpagecount . Este arquivo contém uma contagem de 64 bits do número de vezes que cada página é mapeada, indexada por PFN.

    
por 26.07.2018 / 13:57