Auf meinem Debian GNU / Linux 9-System, wenn eine Binärdatei ausgeführt wird,
- Der Stack ist aber nicht initialisiert
- Der Heap wird auf Null gesetzt.
Warum?
Ich gehe davon aus, dass die Nullinitialisierung die Sicherheit fördert, aber wenn für den Heap, warum dann nicht auch für den Stack? Braucht der Stack auch keine Sicherheit?
Meine Frage ist meines Wissens nicht Debian-spezifisch.
Beispiel-C-Code:
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
const size_t n = 8;
// --------------------------------------------------------------------
// UNINTERESTING CODE
// --------------------------------------------------------------------
static void print_array(
const int *const p, const size_t size, const char *const name
)
{
printf("%s at %p: ", name, p);
for (size_t i = 0; i < size; ++i) printf("%d ", p[i]);
printf("\n");
}
// --------------------------------------------------------------------
// INTERESTING CODE
// --------------------------------------------------------------------
int main()
{
int a[n];
int *const b = malloc(n*sizeof(int));
print_array(a, n, "a");
print_array(b, n, "b");
free(b);
return 0;
}
Ausgabe:
a at 0x7ffe118997e0: 194 0 294230047 32766 294230046 32766 -550453275 32713
b at 0x561d4bbfe010: 0 0 0 0 0 0 0 0
Der C-Standard fordert natürlich nicht malloc()
zum Löschen des Speichers auf, bevor er zugewiesen wird, sondern mein C-Programm dient lediglich der Veranschaulichung. Die Frage ist keine Frage zu C oder zur Standardbibliothek von C. Die Frage ist vielmehr eine Frage, warum der Kernel und / oder der Laufzeit-Loader den Heap auf Null setzen, nicht aber den Stack.
Ein weiteres Experiment
Meine Frage bezieht sich eher auf ein beobachtbares GNU / Linux-Verhalten als auf die Anforderungen von Standarddokumenten. Wenn Sie sich nicht sicher sind, was ich meine, versuchen Sie diesen Code, der weiteres undefiniertes Verhalten aufruft ( undefiniert, das heißt, soweit der C-Standard betroffen ist), um den Punkt zu veranschaulichen:
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
const size_t n = 4;
int main()
{
for (size_t i = n; i; --i) {
int *const p = malloc(sizeof(int));
printf("%p %d ", p, *p);
++*p;
printf("%d\n", *p);
free(p);
}
return 0;
}
Ausgabe von meiner Maschine:
0x555e86696010 0 1
0x555e86696010 0 1
0x555e86696010 0 1
0x555e86696010 0 1
In Bezug auf den C-Standard ist das Verhalten undefiniert, sodass meine Frage den C-Standard nicht berücksichtigt. Ein Aufruf, der malloc()
nicht jedes Mal dieselbe Adresse zurückgeben muss, aber da dieser Aufruf malloc()
tatsächlich jedes Mal dieselbe Adresse zurückgibt, ist es interessant zu bemerken, dass der Speicher, der sich auf dem Heap befindet, jedes Mal auf Null gesetzt wird.
Im Gegensatz dazu schien der Stapel nicht auf Null gestellt zu sein.
Ich weiß nicht, was der letztere Code auf Ihrem Computer bewirken wird, da ich nicht weiß, welche Schicht des GNU / Linux-Systems das beobachtete Verhalten verursacht. Sie können es aber versuchen.
AKTUALISIEREN
@ Kusalananda hat in Kommentaren beobachtet:
Ihr neuester Code gibt verschiedene Adressen und (gelegentlich) nicht initialisierte (von Null verschiedene) Daten zurück, wenn er unter OpenBSD ausgeführt wird. Dies sagt offensichtlich nichts über das Verhalten aus, das Sie unter Linux beobachten.
Dass sich mein Ergebnis von dem unter OpenBSD unterscheidet, ist in der Tat interessant. Anscheinend entdeckten meine Experimente kein Kernel- (oder Linker-) Sicherheitsprotokoll, wie ich gedacht hatte, sondern nur ein Implementierungsartefakt.
In diesem Licht glaube ich, dass die folgenden Antworten von @mosvy, @StephenKitt und @AndreasGrapentin zusammen meine Frage regeln.
Siehe auch Stack Overflow: Warum initialisiert malloc die Werte in gcc auf 0? (Kredit: @bta).
new
Operator in C ++ (auch "Heap") ist unter Linux nur ein Wrapper für malloc (); Dem Kernel ist es egal, was der "Haufen" ist.Antworten:
Der von malloc () zurückgegebene Speicher ist nicht null-initialisiert. Nimm niemals an, dass es so ist.
In Ihrem Testprogramm ist es nur ein Zufall: Ich denke, die haben
malloc()
gerade einen neuen Block entferntmmap()
, aber verlassen Sie sich auch nicht darauf.Wenn ich zum Beispiel Ihr Programm auf meinem Computer so ausführe:
Ihr zweites Beispiel besteht einfach darin, ein Artefakt der
malloc
Implementierung in glibc verfügbar zu machen. wenn man das wiederholt tunmalloc
/free
mit einem Puffer größer als 8 Bytes, werden Sie deutlich sehen , dass nur das erste 8 Bytes auf Null gesetzt werden, wie in dem folgenden Beispielcode.Ausgabe:
quelle
Unabhängig davon, wie der Stapel initialisiert wird, sehen Sie keinen unberührten Stapel, da die C-Bibliothek eine Reihe von Vorgängen ausführt, bevor sie aufruft
main
, und sie berühren den Stapel.Bei der GNU C-Bibliothek unter x86-64 beginnt die Ausführung am _start- Einstiegspunkt, der
__libc_start_main
zum Einrichten der Dinge aufruft , und letzterer endet mit dem Aufrufenmain
. Vor dem Aufrufmain
werden jedoch eine Reihe anderer Funktionen aufgerufen, wodurch verschiedene Daten in den Stapel geschrieben werden. Der Inhalt des Stapels wird zwischen den Funktionsaufrufen nicht gelöscht. Wenn Sie inmain
den Stapel eintreten, enthält der Stapel also Reste der vorherigen Funktionsaufrufe.Dies erklärt nur die Ergebnisse, die Sie vom Stapel erhalten, und zeigt die anderen Antworten zu Ihrer allgemeinen Vorgehensweise und Ihren Annahmen.
quelle
main()
Aufruf der Zeit Initialisierungsroutinen möglicherweise den von zurückgegebenen modifizierten Speicher geändert habenmalloc()
- insbesondere, wenn C ++ - Bibliotheken eingebunden sind. Angenommen, der "Heap" ist auf irgendetwas initialisiert, ist eine wirklich, wirklich schlechte Annahme.In beiden Fällen erhalten Sie nicht initialisierten Speicher, und Sie können keine Annahmen über dessen Inhalt treffen.
Wenn das Betriebssystem Ihrem Prozess eine neue Seite zuweisen muss (sei es für den Stapel oder für die von verwendete Arena
malloc()
), stellt es sicher, dass keine Daten von anderen Prozessen verfügbar gemacht werden. Der übliche Weg, dies sicherzustellen, besteht darin, es mit Nullen zu füllen (es ist jedoch auch möglich, es mit etwas anderem zu überschreiben, einschließlich einer Seite im Wert von/dev/urandom
- tatsächlichmalloc()
schreiben einige Debug- Implementierungen Muster ungleich Null, um fehlerhafte Annahmen wie Ihre zu erfassen).Wenn
malloc()
die Anforderung aus dem Speicher, der bereits verwendet und durch diesen Prozess freigegeben wurde, erfüllt werden kann, wird der Inhalt nicht gelöscht (in der Tat hat das Löschen nichts damit zu tunmalloc()
und kann es nicht sein - es muss geschehen, bevor der Speicher zugeordnet wird Ihr Adressraum). Möglicherweise erhalten Sie Speicher, der zuvor von Ihrem Prozess / Programm geschrieben wurde (zmain()
. B. zuvor ).In Ihrem Beispielprogramm sehen Sie eine
malloc()
Region, die durch diesen Prozess noch nicht geschrieben wurde (dh direkt von einer neuen Seite), und einen Stapel, in den geschrieben wurde (durch Vorcodemain()
in Ihrem Programm). Wenn Sie mehr von dem Stapel untersuchen, werden Sie feststellen, dass er weiter unten (in seiner Wachstumsrichtung) mit Null gefüllt ist.Wenn Sie wirklich verstehen wollen , was auf OS - Ebene geschieht, empfehle ich Ihnen , Bypass - C - Bibliothek Schicht und interact System ruft wie
brk()
undmmap()
stattdessen.quelle
malloc()
und zwarfree()
wiederholt. Obwohl es nicht erforderlich istmalloc()
, denselben kürzlich freigegebenen Speicher erneut zu verwenden, wurde dies im Experimentmalloc()
tatsächlich durchgeführt. Es gab jedes Mal dieselbe Adresse zurück, löschte aber auch jedes Mal den Speicher, was ich nicht erwartet hatte. Das war interessant für mich. Weitere Experimente haben zur heutigen Frage geführt.malloc()
machen absolut nichts mit dem Speicher, den sie Ihnen zur Verfügung stellen - es ist entweder zuvor verwendet oder frisch zugewiesen (und daher vom Betriebssystem auf Null gesetzt). In Ihrem Test haben Sie offensichtlich Letzteres erhalten. In ähnlicher Weise wird der Stapelspeicher im gelöschten Zustand an Ihren Prozess übergeben, aber Sie untersuchen ihn nicht weit genug, um festzustellen, welche Teile Ihr Prozess noch nicht berührt hat. Ihr Stapelspeicher wird gelöscht, bevor er Ihrem Prozess übergeben wird.calloc
kann dies eine Option sein (anstelle vonmemset
)mmap(MAP_ANONYMOUS)
sofern Sie dies nicht ebenfalls verwendenMAP_POPULATE
. Es ist zu hoffen, dass neue Stapelseiten durch neue physische Seiten gesichert und verkabelt werden (in den Hardwareseitentabellen sowie in der Liste der Zuordnungen für Zeiger und Länge des Kernels), wenn sie größer werden, da normalerweise beim ersten Berühren neuer Stapelspeicher geschrieben wird . Aber ja, der Kernel muss verhindern, dass Daten verloren gehen, und das Nullsetzen ist am billigsten und nützlichsten.Ihre Prämisse ist falsch.
Was Sie als "Sicherheit" bezeichnen, ist Vertraulichkeit. Dies bedeutet, dass kein Prozess den Speicher eines anderen Prozesses lesen kann, es sei denn, dieser Speicher wird ausdrücklich von diesen Prozessen gemeinsam genutzt. In einem Betriebssystem ist dies ein Aspekt der Isolierung von gleichzeitigen Aktivitäten oder Prozessen.
Was das Betriebssystem tut, um diese Isolation sicherzustellen, ist, wann immer Speicher vom Prozess für Heap- oder Stapelzuweisungen angefordert wird, dieser Speicher kommt entweder aus einem Bereich im physischen Speicher, der mit Nullen gefüllt ist, oder der mit Junk gefüllt ist aus dem gleichen Prozess kommen .
Dies stellt sicher, dass Sie immer nur Nullen oder Ihren eigenen Müll sehen, sodass die Vertraulichkeit gewährleistet ist und sowohl der Heap als auch der Stack "sicher" sind, wenn auch nicht unbedingt (null-) initialisiert.
Sie lesen zu viel in Ihre Messungen.
quelle