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

Wenn der Heap aus Sicherheitsgründen mit null initialisiert ist, warum wird der Stack dann nur nicht initialisiert?

Der von malloc() zurückgegebene Speicher ist nicht Null initialisiert. Gehen Sie niemals davon aus, dass dies der Fall ist.

In Ihrem Testprogramm ist es nur ein Zufall:Ich tippe auf malloc() habe gerade einen frischen Block von mmap() bekommen , aber verlassen Sie sich auch nicht darauf.

Zum Beispiel, wenn ich Ihr Programm auf meiner Maschine so ausführe:

$ echo 'void __attribute__((constructor)) p(void){
    void *b = malloc(4444); memset(b, 4, 4444); free(b);
}' | cc -include stdlib.h -include string.h -xc - -shared -o pollute.so

$ LD_PRELOAD=./pollute.so ./your_program
a at 0x7ffd40d3aa60: 1256994848 21891 1256994464 21891 1087613792 32765 0 0
b at 0x55834c75d010: 67372036 67372036 67372036 67372036 67372036 67372036 67372036 67372036

Ihr zweites Beispiel zeigt einfach ein Artefakt der malloc Implementierung in glibc; Wenn Sie das wiederholen, malloc /free Bei einem Puffer größer als 8 Bytes werden Sie deutlich sehen, dass nur die ersten 8 Bytes auf Null gesetzt werden, wie im folgenden Beispielcode.

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>

const size_t n = 4;
const size_t m = 0x10;

int main()
{
    for (size_t i = n; i; --i) {
        int *const p = malloc(m*sizeof(int));
        printf("%p ", p);
        for (size_t j = 0; j < m; ++j) {
            printf("%d:", p[j]);
            ++p[j];
            printf("%d ", p[j]);
        }
        free(p);
        printf("\n");
    }
    return 0;
}

Ausgabe:

0x55be12864010 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 
0x55be12864010 0:1 0:1 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 
0x55be12864010 0:1 0:1 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 
0x55be12864010 0:1 0:1 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4

Unabhängig davon, wie der Stack initialisiert wird, sehen Sie keinen ursprünglichen Stack, da die C-Bibliothek eine Reihe von Dingen ausführt, bevor sie main aufruft , und sie berühren den Stapel.

Bei der GNU-C-Bibliothek beginnt die Ausführung auf x86-64 am Einstiegspunkt _start, der __libc_start_main aufruft Dinge einzurichten, und letzteres ruft am Ende main auf . Aber bevor Sie main aufrufen , ruft es eine Reihe anderer Funktionen auf, wodurch verschiedene Daten auf den Stack geschrieben werden. Der Inhalt des Stapels wird zwischen Funktionsaufrufen nicht gelöscht, also wenn Sie in main kommen , Ihr Stack enthält Reste der vorherigen Funktionsaufrufe.

Dies erklärt nur die Ergebnisse, die Sie aus dem Stack erhalten, siehe die anderen Antworten zu Ihrem allgemeinen Ansatz und Ihren Annahmen.


In beiden Fällen werden Sie nicht initialisiert Speicher, und Sie können keine Vermutungen über seinen Inhalt anstellen.

Wenn das Betriebssystem Ihrem Prozess eine neue Seite zuweisen muss (sei es für seinen Stack oder für die von malloc() verwendete Arena). ), es garantiert, dass es keine Daten von anderen Prozessen offenlegt; Der übliche Weg, dies sicherzustellen, besteht darin, es mit Nullen zu füllen (aber es ist genauso gültig, es mit irgendetwas anderem zu überschreiben, einschließlich sogar einer Seite im Wert von /dev/urandom - Tatsächlich etwas Debugging malloc() Implementierungen schreiben Nicht-Null-Muster, um falsche Annahmen wie Ihre abzufangen).

Wenn malloc() kann die Anforderung aus dem Speicher erfüllen, der bereits verwendet und von diesem Prozess freigegeben wurde, wird sein Inhalt nicht gelöscht (tatsächlich hat das Löschen nichts mit malloc() zu tun und es kann nicht sein - es muss passieren, bevor der Speicher Ihrem Adressraum zugeordnet wird). Möglicherweise erhalten Sie Speicher, der zuvor von Ihrem Prozess/Programm geschrieben wurde (z. B. vor main() ).

In Ihrem Beispielprogramm sehen Sie einen malloc() Region, die noch nicht von diesem Prozess geschrieben wurde (d. h. sie stammt direkt von einer neuen Seite) und ein Stack, in den geschrieben wurde (von pre-main() Code in Ihrem Programm). Wenn Sie mehr von dem Stapel untersuchen, werden Sie feststellen, dass er weiter unten (in seiner Wachstumsrichtung) mit Nullen gefüllt ist.

Wenn Sie wirklich verstehen wollen, was auf Betriebssystemebene passiert, empfehle ich Ihnen, die Ebene der C-Bibliothek zu umgehen und mit Systemaufrufen wie brk() zu interagieren und mmap() stattdessen.


Linux
  1. Warum hat der Server meine IP blockiert?

  2. Gibt es CLI-Tools zum Zeichnen von Grafiken auf dem Bildschirm während einer X-Sitzung?

  3. Warum cPanel die beste Lösung für Website-Designer ist

  4. Warum Ubuntu das Beste für die Serververwaltung ist

  5. Wie kann man den Stack für den Systemaufruf clone() unter Linux zuordnen?

Warum den Pantheon-Desktop für Linux Elementary OS verwenden

Warum ich Alpine immer noch für E-Mail am Linux-Terminal liebe

Slack für die CLI – Slack

Die 8 besten sicheren Linux-Telefone für Datenschutz und Sicherheit

Warum ist die Standardstapelgröße unter modernem Linux so riesig - 8 MB (bei einigen Distributionen sogar 10)

Warum ist das Ausführen von named(bind) in Chroot für die Sicherheit so wichtig? Oder vielleicht doch nicht?