W moim systemie Debian GNU / Linux 9, gdy plik binarny jest wykonywany,
- stos jest niezainicjowany, ale
- sterta jest inicjowana zerem.
Dlaczego?
Zakładam, że inicjalizacja zera promuje bezpieczeństwo, ale jeśli dla stosu, to dlaczego nie także dla stosu? Czy stos też nie potrzebuje bezpieczeństwa?
O ile wiem, moje pytanie nie jest specyficzne dla Debiana.
Przykładowy kod C:
#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;
}
Wynik:
a at 0x7ffe118997e0: 194 0 294230047 32766 294230046 32766 -550453275 32713
b at 0x561d4bbfe010: 0 0 0 0 0 0 0 0
malloc()
Oczywiście standard C nie wymaga wyczyszczenia pamięci przed jej przydzieleniem, ale mój program C służy jedynie do ilustracji. Pytanie nie jest pytaniem o C ani o standardową bibliotekę C. Raczej pytanie dotyczy tego, dlaczego jądro i / lub moduł ładujący w czasie wykonywania zerują stertę, ale nie stos.
KOLEJNY DOŚWIADCZENIE
Moje pytanie dotyczy obserwowalnego zachowania GNU / Linuksa, a nie wymagań dokumentów standardów. Jeśli nie jestem pewien, co mam na myśli, wypróbuj ten kod, który wywołuje dalsze niezdefiniowane zachowanie ( niezdefiniowane, to znaczy w odniesieniu do standardu C), aby zilustrować tę kwestię:
#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;
}
Dane wyjściowe z mojej maszyny:
0x555e86696010 0 1
0x555e86696010 0 1
0x555e86696010 0 1
0x555e86696010 0 1
Jeśli chodzi o standard C, zachowanie jest niezdefiniowane, więc moje pytanie nie dotyczy normy C. Wywołanie, które malloc()
nie musi zwracać tego samego adresu za każdym razem, ale ponieważ to wezwanie malloc()
rzeczywiście rzeczywiście zwraca ten sam adres za każdym razem, interesujące jest zauważenie, że pamięć, która jest na stercie, jest zerowana za każdym razem.
Natomiast stos nie wydawał się wyzerowany.
Nie wiem, co ten ostatni kod zrobi na twoim komputerze, ponieważ nie wiem, która warstwa systemu GNU / Linux powoduje obserwowane zachowanie. Możesz tylko spróbować.
AKTUALIZACJA
@Kusalananda zauważył w komentarzach:
Jeśli chodzi o wartość, najnowszy kod zwraca różne adresy i (sporadycznie) niezainicjowane (niezerowe) dane, gdy jest uruchamiany na OpenBSD. To oczywiście nie mówi nic o zachowaniu, którego obserwujesz w Linuksie.
To, że mój wynik różni się od wyniku na OpenBSD, jest naprawdę interesujące. Najwyraźniej moje eksperymenty odkrywały nie protokół bezpieczeństwa jądra (lub linkera), jak myślałem, ale zwykły artefakt implementacyjny.
W tym świetle uważam, że łącznie poniższe odpowiedzi @mosvy, @StephenKitt i @AndreasGrapentin rozstrzygają moje pytanie.
Zobacz także Przepełnienie stosu: dlaczego malloc inicjuje wartości na 0 w gcc? (kredyt: @bta).
new
operator C ++ (także „sterty”) jest Linux tylko owijka malloc (); jądro nie wie ani nie obchodzi, czym jest „sterta”.Odpowiedzi:
Pamięć zwrócona przez malloc () nie jest inicjowana zerem. Nigdy nie zakładaj, że tak jest.
W twoim programie testowym jest to tylko przypadek: Myślę, że
malloc()
właśnie dostałem świeżą blokadęmmap()
, ale też nie polegaj na tym.Na przykład, jeśli uruchomię twój program na moim komputerze w ten sposób:
Drugi przykład to po prostu odsłonięcie artefaktu
malloc
implementacji w glibc; jeśli powtórzyszmalloc
/free
z buforem większym niż 8 bajtów, zobaczysz, że tylko pierwsze 8 bajtów jest zerowanych, jak w poniższym przykładowym kodzie.Wynik:
źródło
Niezależnie od tego, w jaki sposób inicjowany jest stos, nie widzisz nieskazitelnego stosu, ponieważ biblioteka C wykonuje wiele czynności przed wywołaniem
main
i dotykają stosu.W bibliotece GNU C na x86-64 wykonywanie rozpoczyna się w punkcie wejścia _start , który wywołuje
__libc_start_main
konfigurację, a ten ostatni kończy się wywołaniemmain
. Ale przed wywołaniemmain
wywołuje szereg innych funkcji, które powodują zapisywanie różnych danych na stosie. Zawartość stosu nie jest usuwana między wywołaniami funkcji, więc gdy się w nią wejdzieszmain
, stos zawiera resztki poprzednich wywołań funkcji.To tłumaczy tylko wyniki uzyskane ze stosu, zobacz inne odpowiedzi dotyczące twojego ogólnego podejścia i założeń.
źródło
main()
wywołania procedury inicjujące mogą równie dobrze zwrócić zmodyfikowaną pamięćmalloc()
- szczególnie jeśli biblioteki C ++ są połączone. Założenie, że „sterty” jest inicjowane na cokolwiek, jest naprawdę złym założeniem.W obu przypadkach otrzymujesz niezainicjowaną pamięć i nie możesz przyjmować żadnych założeń dotyczących jej zawartości.
Kiedy system operacyjny musi przypisać nową stronę do twojego procesu (czy to ze względu na stos, czy arenę, z której korzysta
malloc()
), gwarantuje, że nie ujawni danych z innych procesów; jest to zwykły sposób na wypełnienie go zerami (ale równie ważne jest zastąpienie go czymkolwiek innym, w tym nawet wartością strony/dev/urandom
- w rzeczywistości niektóremalloc()
implementacje debugujące zapisują niezerowe wzorce, aby uchwycić błędne założenia, takie jak twoje).Jeśli
malloc()
może zaspokoić żądanie z pamięci, która została już wykorzystana i zwolniona przez ten proces, jego zawartość nie zostanie wyczyszczona (w rzeczywistości czyszczenie nie ma nic wspólnegomalloc()
i nie może być - musi się zdarzyć, zanim pamięć zostanie zmapowana w Twoja przestrzeń adresowa). Możesz dostać pamięć, która została wcześniej zapisana przez twój proces / program (np. Wcześniejmain()
).W twoim przykładowym programie widzisz
malloc()
region, który nie został jeszcze zapisany przez ten proces (tj. Jest bezpośrednio z nowej strony) i stos, do którego został zapisany (przezmain()
kod wstępny w twoim programie). Jeśli przyjrzysz się większej ilości stosu, zobaczysz, że jest on wypełniony zerami w dół (w kierunku wzrostu).Jeśli naprawdę chcesz zrozumieć, co dzieje się na poziomie systemu operacyjnego, zalecamy ominięcie warstwy biblioteki C i interakcję za pomocą wywołań systemowych, takich jak
brk()
immap()
zamiast.źródło
malloc()
ifree()
wielokrotnie. Chociaż nic nie wymagamalloc()
ponownego użycia tej samej pamięci ostatnio zwolnionej, w eksperymenciemalloc()
zdarzyło się to zrobić. Zdarzyło się, że za każdym razem zwracał ten sam adres, ale za każdym razem zerował pamięć, czego się nie spodziewałem. To było dla mnie interesujące. Dalsze eksperymenty doprowadziły do dzisiejszego pytania.malloc()
nie robi absolutnie nic z pamięcią, którą ci dają - jest albo wcześniej używana, albo świeżo przypisana (a zatem zerowana przez system operacyjny). W teście najwyraźniej masz ten drugi. Podobnie pamięć stosu jest przekazywana procesowi w stanie wyczyszczonym, ale nie sprawdza się go wystarczająco daleko, aby zobaczyć części, których proces jeszcze nie dotknął. Pamięć stosu jest czyszczona, zanim zostanie przekazana procesowi.calloc
opcją może być (zamiastmemset
)mmap(MAP_ANONYMOUS)
chyba że używaszMAP_POPULATE
również. Mam nadzieję, że nowe strony stosu są wspierane przez świeże strony fizyczne i łączone (mapowane w tabelach stron sprzętowych, a także wskaźnik / lista długości mapowań jądra) podczas powiększania, ponieważ normalnie nowa pamięć stosu jest zapisywana po pierwszym dotknięciu . Ale tak, jądro musi jakoś uniknąć wycieku danych, a zerowanie jest najtańszym i najbardziej użytecznym.Twoje założenie jest złe.
To, co określasz jako „bezpieczeństwo”, jest naprawdę poufnością , co oznacza, że żaden proces nie może odczytać pamięci innych procesów, chyba że pamięć ta jest jawnie dzielona między te procesy. W systemie operacyjnym jest to jeden aspekt izolacji jednoczesnych działań lub procesów.
System operacyjny robi to, aby zapewnić tę izolację, za każdym razem, gdy proces żąda pamięci dla alokacji stosu lub stosu, pamięć ta pochodzi albo z obszaru pamięci fizycznej wypełnionego zerami, albo wypełnionego śmieciami, które są pochodzący z tego samego procesu .
Zapewnia to, że zawsze widzisz tylko zera lub własne śmieci, więc zapewniona jest poufność, a zarówno stos, jak i stos są „bezpieczne”, choć niekoniecznie (zerowo) inicjowane.
Za dużo czytasz w swoich pomiarach.
źródło