GNU/Linux >> Tutoriels Linux >  >> Linux

Linux – Comment s'assurer qu'une bibliothèque partagée aura ses pages mémoire partagées par plusieurs processus ?

Permettez-moi d'abord de décrire ce que je veux faire, suivi de ce que j'arrive à faire, et enfin de mon problème.

Objectif :implémenter l'attaque flush+flush cache en C

J'essaie d'implémenter en C l'attaque de cache flush+flush (https://gruss.cc/files/flushflush.pdf). Fondamentalement, il exploite le fait que deux processus différents peuvent partager les mêmes pages mémoire lors de l'utilisation de bibliothèques partagées. Cela se traduit par une "utilisation partagée" du cache.

Supposons que nous ayons un processus victime, fonctionnant continuellement et exécutant parfois une fonction func importé depuis une bibliothèque partagée.

En parallèle, nous supposons que nous avons un processus d'espionnage, s'exécutant sur le même ordinateur que la victime, dont le but est d'espionner lorsque la victime appelle func . L'espion a également accès à la même bibliothèque partagée. Le pseudo-code du processus d'espionnage est le suivant :

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

flush_time( <address> ) est une fonction qui renvoie le temps nécessaire au CPU pour vider la mémoire pointée par address de tous les niveaux de cache. Sur le processeur Intel, cela peut être réalisé via l'instruction d'assemblage clflush . On peut observer que l'exécution de clflush est plus rapide lorsque l'adresse n'est pas présente dans le cache. En conséquence, le temps nécessaire pour vider une adresse mémoire peut être directement traduit en sa présence (ou non) à l'intérieur du cache.

Le processus espion renvoie un vecteur de trace qui contient les résultats flush_time au fil du temps. D'après l'observation précédente, cette trace présentera des délais plus élevés lorsque la victime appelle également la fonction func . L'espion déduira donc lorsque la victime appelle func .

Ce que j'ai réussi à faire :faire en sorte que l'attaque fonctionne contre la bibliothèque partagée GSL

J'ai implémenté l'attaque susmentionnée, où la bibliothèque partagée est le GSL. Arbitrairement, j'ai choisi gsl_stats_mean (défini dans gsl_statistics_double ) pour agir comme la fonction func Je suis prêt à espionner.

Dans ce cas, l'espionnage fonctionne parfaitement car je peux clairement voir une différence de temps lorsque le programme victime appelle gsl_stats_mean

Mon problème :l'attaque ne fonctionne pas sur une bibliothèque partagée maison

Je veux maintenant créer ma propre bibliothèque partagée et l'utiliser pour le test d'espionnage/victime. Supposons . désigne le dossier dans lequel mon spy.c et victim.c les fichiers sont. J'ai créé deux fichiers myl.c et myl.h dans un dossier ./src/myl , qui contiennent respectivement la description de func et sa déclaration. Comme précédemment, le but de mon espion est de détecter l'utilisation de func de la victime.

Les deux spy.c et victim.c contenir la ligne d'inclusion :

 #include "src/myl/myl.h"

La création de la bibliothèque partagée se fait à l'aide des commandes suivantes :

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

Je lance alors ma victime et mon espion en utilisant les lignes suivantes :

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

Cependant, contrairement au cas où j'utilisais la fonction GSL, je ne vois plus aucune activité sur le cache. Je suppose que cela signifie que mes processus espion et victime ne partagent pas la même page mémoire pour ma bibliothèque partagée (alors que c'était le cas lors de l'utilisation du GSL). Notez qu'en compilant de cette manière, l'espionnage fonctionne toujours lors du ciblage d'une fonction GSL.

Ma question principale est la suivante :comment s'assurer qu'une bibliothèque partagée compilée maison aura la pagination mémoire partagée lorsqu'elle est exécutée par plusieurs processus en même temps ? Il semble que ce soit le cas pour les bibliothèques "correctes" que j'ai installées, telles que GSL, gmp, les bibliothèques natives, etc. Mais pas pour celui que j'ai fait moi-même.

Merci d'avance, et je m'excuse si la réponse est simple.

EDIT :sortie de LD_DEBUG=libs et files pour l'espion et la victime.
REMARQUE :la victime est appelée pg2 et l'espion s'appelle pg1 (désolé pour ça)

D'abord, les bibliothèques pour la victime, suivies des fichiers pour la victime (pg2 ). Ensuite, les bibliothèques pour l'espion, suivies des fichiers pour l'espion (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: 

Réponse acceptée :

Depuis la sortie de débogage de ld l'éditeur de liens/chargeur dynamique confirme que la victim et spy programmes chargent le bon fichier d'entrée, l'étape suivante consisterait à vérifier si le noyau a réellement configuré les pages physiques où libmyl.so est chargé en mémoire pour être partagé entre la victim et spy .

Sous Linux, il est possible de vérifier depuis la version 2.6.25 du noyau via le pagemap interface dans le noyau qui permet aux programmes de l'espace utilisateur d'examiner les tables de pages et les informations associées en lisant les fichiers dans /proc .

La procédure générale d'utilisation de pagemap pour savoir si deux processus partagent de la mémoire ressemble à ceci :

  1. Lire/proc/<pid>/maps pour les deux processus afin de déterminer quelles parties de l'espace mémoire sont mappées à quels objets.
  2. Sélectionnez les cartes qui vous intéressent, en l'occurrence les pages vers lesquelles libmyl.so est mappé.
  3. Ouvrir /proc/<pid>/pagemap . Le pagemap se compose de descripteurs de pagemap 64 bits, un par page. Le mappage entre l'adresse de la page et l'adresse des descripteurs dans le pagemap est adresse de la page / taille de la page * taille du descripteur . Recherchez les descripteurs des pages que vous souhaitez examiner.
  4. Lire un descripteur 64 bits sous la forme d'un entier non signé pour chaque page du pagemap .
  5. Comparez le numéro de cadre de page (PFN) dans les bits 0 à 54 du descripteur de page entre libmyl.so pages pour victim et spy . Si les PFN correspondent, les deux processus partagent les mêmes pages physiques.
Connexe :Windows – Comment Windows pourrait-il NE PAS corrompre le système de fichiers Linux en perturbant les schémas de partition ?

L'exemple de code suivant illustre comment le pagemap peuvent être consultés et imprimés depuis le processus. Il utilise dl_iterate_phdr() pour déterminer l'adresse virtuelle de chaque bibliothèque partagée chargée dans l'espace mémoire des processus, puis recherche et imprime le pagemap correspondant depuis /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 sortie du programme devrait ressembler à ce qui suit :

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

où :

  • address est l'adresse virtuelle de la page
  • pfn est le numéro de cadre de page des pages
  • soft-dirty indique si le bit soft-dirty est défini dans les pages Page Table Entry (PTE).
  • excl. indique si la page est exclusivement mappée (c'est-à-dire que la page n'est mappée que pour ce processus).
  • file / shared anon indique si la page est un fichier pages ou une page anonyme partagée.
  • swapped indique si la page est actuellement permutée (implique present est zéro).
  • present indique si la page est actuellement présente dans l'ensemble résident des processus (implique swapped est zéro).
Connexe :linux de base (1) Aide-mémoire

(Remarque :j'exécute le programme d'exemple avec sudo comme depuis Linux 4.0 uniquement les utilisateurs avec le CAP_SYS_ADMIN la capacité peut obtenir des PFN à partir de /proc/<pid>/pagemap . À partir de Linux 4.2, le champ PFN est mis à zéro si l'utilisateur n'a pas CAP_SYS_ADMIN . La raison de ce changement est de rendre plus difficile l'exploitation d'une autre vulnérabilité liée à la mémoire, l'attaque Rowhammer, en utilisant les informations sur le mappage virtuel-physique exposées par les PFN.)

Si vous exécutez l'exemple de programme plusieurs fois, vous devriez remarquer que l'adresse virtuelle de la page devrait changer (en raison de l'ASLR), mais le PFN des bibliothèques partagées utilisées par d'autres processus devrait rester le même.

Si les PFN pour libmyl.so correspondance entre la victim et spy programme, je commencerais à chercher une raison pour laquelle l'attaque échoue dans le code d'attaque lui-même. Si les PFN ne correspondent pas, les bits supplémentaires peuvent indiquer pourquoi les pages ne sont pas configurées pour être partagées. Le pagemap les bits indiquent ce qui suit :

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)

Pages de copie sur écriture dans (MAP_FILE | MAP_PRIVATE) les zones sont anonymes dans ce contexte.

Bonus : Pour obtenir le nombre de fois une page a été mappée, le PFN peut être utilisé pour rechercher la page dans /proc/kpagecount . Ce fichier contient un décompte 64 bits du nombre de fois où chaque page est mappée, indexée par PFN.


Linux
  1. Comment tuer les processus en cours d'exécution sous Linux

  2. Comment initialiser une bibliothèque partagée sous Linux

  3. Comment faire le versioning d'une bibliothèque partagée sous Linux ?

  4. Comment configurer googleTest en tant que bibliothèque partagée sous Linux

  5. Comment lire les pages de manuel Linux ?

Comment vérifier la mémoire partagée Linux à l'aide de la commande ipcs

Comment effacer la mémoire d'échange sous Linux

Comment effacer le cache mémoire sous Linux

Comment trouver les principaux processus en cours d'exécution par utilisation de la mémoire et du processeur sous Linux

Comment calculer l'utilisation du processeur d'un processus et de tous ses processus enfants sous Linux ?

Comment une bibliothèque partagée (.so) peut-elle appeler une fonction qui est implémentée dans son programme de chargement ?