GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Warum wächst die MAP_GROWSDOWN-Zuordnung nicht?

Ich weiß, dass das OP bereits eine der Antworten akzeptiert hat, aber leider erklärt es nicht, warum MAP_GROWSDOWN scheint manchmal zu funktionieren. Da diese Stack Overflow-Frage einer der ersten Treffer in Suchmaschinen ist, möchte ich meine Antwort für andere hinzufügen.

Die Dokumentation von MAP_GROWSDOWN muss aktualisiert werden. Insbesondere:

Dieses Wachstum kann wiederholt werden, bis die Abbildung auf eine Seite des oberen Endes der nächstniedrigeren Abbildung anwächst, wobei an diesem Punkt das Berühren der "Schutz"-Seite zu einem SIGSEGV-Signal führt.

In Wirklichkeit erlaubt der Kernel kein MAP_GROWSDOWN Mapping, um näher als stack_guard_gap zu wachsen Seiten von der vorherigen Zuordnung entfernt. Der Standardwert ist 256, kann aber auf der Kernel-Befehlszeile überschrieben werden. Da Ihr Code keine gewünschte Adresse für das Mapping angibt, wählt der Kernel automatisch eine aus, landet aber höchstwahrscheinlich innerhalb von 256 Seiten vom Ende eines bestehenden Mappings.

BEARBEITEN :

Darüber hinaus verweigern Kernel vor v5.0 den Zugriff auf eine Adresse, die mehr als 64k+256 Bytes unter dem Stapelzeiger liegt. Einzelheiten finden Sie in diesem Kernel-Commit.

Dieses Programm funktioniert auf x86 sogar mit Kerneln vor 5.0:

#include <sys/mman.h>
#include <stdint.h>
#include <stdio.h>

#define PAGE_SIZE   4096UL
#define GAP     512 * PAGE_SIZE

static void print_maps(void)
{
    FILE *f = fopen("/proc/self/maps", "r");
    if (f) {
        char buf[1024];
        size_t sz;
        while ( (sz = fread(buf, 1, sizeof buf, f)) > 0)
            fwrite(buf, 1, sz, stdout);
        fclose(f);
    }
}

int main()
{
    char *p;
    void *stack_ptr;

    /* Choose an address well below the default process stack. */
    asm volatile ("mov  %%rsp,%[sp]"
        : [sp] "=g" (stack_ptr));
    stack_ptr -= (intptr_t)stack_ptr & (PAGE_SIZE - 1);
    stack_ptr -= GAP;
    printf("Ask for a page at %p\n", stack_ptr);
    p = mmap(stack_ptr, PAGE_SIZE, PROT_READ | PROT_WRITE,
         MAP_PRIVATE | MAP_STACK | MAP_ANONYMOUS | MAP_GROWSDOWN,
         -1, 0);
    printf("Mapped at %p\n", p);
    print_maps();
    getchar();

    /* One page is already mapped: stack pointer does not matter. */
    *p = 'A';
    printf("Set content of that page to \"%s\"\n", p);
    print_maps();
    getchar();

    /* Expand down by one page. */
    asm volatile (
        "mov  %%rsp,%[sp]"  "\n\t"
        "mov  %[ptr],%%rsp" "\n\t"
        "movb $'B',-1(%%rsp)"   "\n\t"
        "mov  %[sp],%%rsp"
        : [sp] "+&g" (stack_ptr)
        : [ptr] "g" (p)
        : "memory");
    printf("Set end of guard page to \"%s\"\n", p - 1);
    print_maps();
    getchar();

    return 0;
}

Ersetzen:

volatile char *c_ptr_1 = mapped_ptr - 4096; //1 page below

Mit

volatile char *c_ptr_1 = mapped_ptr;

Denn:

Die Rücksprungadresse ist eine Seite tiefer als der Speicherbereich, der tatsächlich im virtuellen Adressraum des Prozesses angelegt wird. Durch Berühren einer Adresse auf der "Wächter"-Seite unterhalb der Zuordnung wird die Zuordnung um eine Seite erweitert.

Beachten Sie, dass ich die Lösung getestet habe und sie wie erwartet auf Kernel 4.15.0-45-generic funktioniert.


Zunächst einmal wollen Sie MAP_GROWSDOWN nicht , und so funktioniert der Haupt-Thread-Stack nicht. Speicherzuordnung eines Prozesses mit pmap analysieren. [Stack] Nichts verwendet es und so ziemlich nichts sollte benutze es. Das Zeug in der Manpage, das besagt, dass es "für Stacks verwendet" wird, ist falsch und sollte behoben werden.

Ich vermute, dass es fehlerhaft sein könnte (weil nichts es verwendet, also normalerweise niemand sich darum kümmert oder es sogar bemerkt, wenn es kaputt geht.)

Ihr Code funktioniert für mich, wenn ich den mmap ändere Anruf, um mehr als 1 Seite zuzuordnen. Insbesondere habe ich 4096 * 100 ausprobiert . Ich verwende Linux 5.0.1 (Arch Linux) auf Bare Metal (Skylake).

/proc/PID/smaps zeigt eine gd an Flagge.

Und dann (bei Single-Stepping des asm) maps Der Eintrag ändert sich tatsächlich in eine niedrigere Startadresse, aber dieselbe Endadresse, sodass er buchstäblich nach unten wächst, wenn ich mit einem 400k-Mapping beginne. Dies ergibt eine Anfangszuweisung von 400.000 oben die Absenderadresse, die bei Ausführung des Programms auf 404 KB anwächst. (Die Größe für einen _GROWSDOWN Zuordnung ist nicht die Wachstumsgrenze oder so etwas.)

https://bugs.centos.org/view.php?id=4767 könnte verwandt sein; zwischen den Kernelversionen in CentOS 5.3 und 5.5 hat sich etwas geändert. Und/oder es hatte etwas damit zu tun, in einer VM (5.3) zu arbeiten und nicht auf Bare Metal (5.5) zu wachsen und Fehler zu machen.

Ich habe das C vereinfacht, um ptr[-4095] zu verwenden etc:

int main(void){
    volatile char *ptr = mmap(NULL, 4096*100,
                            PROT_READ | PROT_WRITE,
                            MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK | MAP_GROWSDOWN,
                            -1, 0);
    if(ptr == MAP_FAILED){
        int error_code = errno;
        fprintf(stderr, "Cannot do MAP_FIXED mapping."
                        "Error code = %d, details = %s\n", error_code, strerror(error_code));
                        exit(EXIT_FAILURE);
    }

    ptr[0] = 'a';      //address returned by mmap
    ptr[-4095] = 'b';  // grow by 1 page
}

Kompilieren mit gcc -Og gibt asm, das nett zu Single-Step ist.

Übrigens sind verschiedene Gerüchte über die Entfernung des Flags aus glibc offensichtlich falsch. Diese Quelle lässt sich kompilieren, und es ist klar, dass sie auch vom Kernel unterstützt und nicht stillschweigend ignoriert wird. (Obwohl das Verhalten, das ich bei der Größe 4096 statt 400 KB sehe, genau damit übereinstimmt, dass das Flag stillschweigend ignoriert wird. Der gd VmFlag ist immer noch in smaps vorhanden , also wird es in diesem Stadium nicht ignoriert.)

Ich überprüfte und es gab Platz für es zu wachsen, ohne in die Nähe einer anderen Zuordnung zu kommen. Also IDK, warum es nicht gewachsen ist, als das GD-Mapping nur 1 Seite war. Ich habe es ein paar Mal versucht und es ist jedes Mal segfaulted. Mit der größeren Anfangszuordnung hat es nie einen Fehler gegeben.

Beide Male wurde der mmap-Rückgabewert gespeichert (die erste Seite des eigentlichen Mappings), dann 4095 Bytes darunter gespeichert.


Linux
  1. Mapping von Metadaten mit Avconv funktioniert nicht?

  2. Linux – Warum funktioniert Setuid nicht?

  3. Warum funktioniert `exit &` nicht?

  4. Warum funktioniert find -exec mv {} ./target/ + nicht?

  5. Warum heißt es:Wir dürfen limit.h nicht einschließen! in dirent.h?

Linux – Warum funktioniert Locale Es_mx, aber nicht Es?

Linux – Warum behält Rsync unter Linux nicht alle Zeitstempel (Erstellungszeit) bei?

Warum funktioniert Tomcat mit Port 8080, aber nicht mit 80?

Warum funktioniert „dd“ nicht zum Erstellen eines bootfähigen USB-Sticks?

Warum zeigt yum updateinfo nicht alle Updates an?

Warum nicht ICMP blockieren?