Mon problème

J'ai un singleton dont la mémoire est corrompue par un corrupteur inconnu. Quelque chose écrase la mémoire du singleton, et des centaines d'octets autour de lui, avec la valeur 0. Une fois l'objet construit via new, il est en lecture seule pour la durée de vie de l'application.

Mon but

Je voudrais capturer le corrupteur au moment de la corruption. Je voudrais mprotecter en lecture seule la mémoire de l'objet après construction. De cette façon plus tard, lorsque la corruption se produit, le système fera une erreur de segmentation au moment de la corruption.

Ma question

Il semble que mprotect est granulaire au niveau de la page. Comment pourrais-je "surallouer" pour l'instance singleton une page complète pour l'objet (il est bien plus petit que 4k, la taille de page standard) et ensuite mprotéger cette page?

0
firebush 2 avril 2020 à 00:14

3 réponses

Meilleure réponse

Vous pouvez utiliser anonyme mmap pour allouer une page complète au singleton, puis y construire l'objet avec le placement new.

1
Brian 1 avril 2020 à 21:23

Merci @Brian. Voici mon exemple minimal d'utilisation de mmap comme il le suggère, suivi d'un placement nouveau pour utiliser cette mémoire, puis de mprotect pour le rendre en lecture seule:

#include <iostream>
#include <sys/mman.h>
#include <unistd.h>

using namespace std;

struct MySingleton
{
    int some_value;

    static MySingleton* init(int a_value)
    {
        // Get the system's page size.
        const auto pagesize = getpagesize();
        // mmap one page worth of memory, initially writable.
        void* map = mmap(0, pagesize, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, 0, 0);
        // Use placement new using that memory.
        MySingleton::_instance = new(map) MySingleton(a_value);
        // Now make that memory read-only.
        mprotect(map, pagesize, PROT_READ);
        return MySingleton::_instance;
    }

    static MySingleton* instance()
    {
        return _instance;
    }

private:
    MySingleton(int a_value)
        : some_value{a_value}
    {
    }

    static MySingleton *_instance;
};

MySingleton *MySingleton::_instance = nullptr;


int
main(int argc, char* argv[])
{
    MySingleton *instance = MySingleton::init(10);

    // Read is OK.
    cout << instance->some_value << endl;

    // This should crash;
    instance->some_value = 5;
    cout << instance->some_value << endl;

    return 0;
}

Quand je compile et exécute ceci, j'obtiens le crash que je désire:

g++ -g -Wall -Werror -std=c++17 test.cc -o test
./test
10
runit: line 4: 18029 Bus error: 10           ./test

Le débogueur pointe directement vers l'écriture:

$ lldb test
(lldb) target create "test"
Current executable set to 'test' (x86_64).
(lldb) run
Process 18056 launched: '<snip>' (x86_64)
10
Process 18056 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x10011d000)
    frame #0: 0x0000000100000c50 test`main(argc=1, argv=0x00007ffeefbff9a8) at test.cc:50:26
   47       cout << instance->some_value << endl;
   48   
   49       // This should crash;
-> 50       instance->some_value = 5;
   51       cout << instance->some_value << endl;
   52   
   53       return 0;
Target 0: (test) stopped.
1
firebush 1 avril 2020 à 22:06

Pratiquement chaque débogueur a un outil pour surveiller la mémoire pour le changement (dans la commande gdb littéralement appelée watch)

Au lieu d'essayer de contourner le problème, vous devez trouver la source, la corruption due à une écriture hors-limite peut toucher autre chose, même vitale et difficile à détecter.

Pour répondre à votre question, C ++ a obtenu une nouvelle expression de placement, une surcharge pour un nouvel opérateur qui permet d'allouer un objet à une adresse particulière de la mémoire pré-allouée

0
Swift - Friday Pie 1 avril 2020 à 22:21