Di sistem Debian GNU / Linux 9 saya, ketika biner dijalankan,
- stack belum diinisialisasi tetapi
- heap diinisialisasi nol.
Mengapa?
Saya berasumsi bahwa inisialisasi nol mempromosikan keamanan tetapi, jika untuk heap, lalu mengapa tidak juga untuk stack? Apakah tumpukan juga tidak membutuhkan keamanan?
Pertanyaan saya tidak spesifik untuk Debian sejauh yang saya tahu.
Contoh kode 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;
}
Keluaran:
a at 0x7ffe118997e0: 194 0 294230047 32766 294230046 32766 -550453275 32713
b at 0x561d4bbfe010: 0 0 0 0 0 0 0 0
Standar C tidak meminta malloc()
untuk menghapus memori sebelum mengalokasikannya, tentu saja, tetapi program C saya hanya untuk ilustrasi. Pertanyaannya bukan pertanyaan tentang C atau tentang perpustakaan standar C. Sebaliknya, pertanyaannya adalah pertanyaan tentang mengapa kernel dan / atau run-time loader memusatkan perhatian pada tumpukan tetapi bukan tumpukan.
EKSPERIMEN LAIN
Pertanyaan saya mengenai perilaku GNU / Linux yang dapat diamati daripada persyaratan dokumen standar. Jika tidak yakin apa yang saya maksud, maka coba kode ini, yang memanggil perilaku tidak terdefinisi lebih lanjut ( tidak terdefinisi, yaitu sejauh menyangkut standar C) untuk menggambarkan poin:
#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;
}
Output dari mesin saya:
0x555e86696010 0 1
0x555e86696010 0 1
0x555e86696010 0 1
0x555e86696010 0 1
Sejauh menyangkut standar C, perilaku tidak terdefinisi, jadi pertanyaan saya tidak mempertimbangkan standar C. Panggilan untuk malloc()
tidak perlu mengembalikan alamat yang sama setiap kali tetapi, karena panggilan untuk malloc()
ini memang benar-benar mengembalikan alamat yang sama setiap kali, menarik untuk memperhatikan bahwa memori, yang ada di heap, adalah nol setiap kali.
Tumpukan itu, sebaliknya, tampaknya tidak memusatkan perhatian.
Saya tidak tahu apa yang akan dilakukan kode terakhir pada mesin Anda, karena saya tidak tahu lapisan mana dari sistem GNU / Linux yang menyebabkan perilaku yang diamati. Anda bisa mencobanya.
MEMPERBARUI
@ Kusalananda telah mengamati dalam komentar:
Untuk apa nilainya, kode terbaru Anda mengembalikan alamat yang berbeda dan (sesekali) data yang tidak diinisialisasi (bukan nol) ketika dijalankan di OpenBSD. Ini jelas tidak mengatakan apa-apa tentang perilaku yang Anda saksikan di Linux.
Bahwa hasil saya berbeda dengan hasil di OpenBSD memang menarik. Rupanya, percobaan saya bukan menemukan protokol keamanan kernel (atau linker), seperti yang saya pikirkan, tetapi hanya artefak implementasi.
Dalam terang ini, saya percaya bahwa, bersama-sama, jawaban di bawah @mosvy, @StephenKitt dan @AndreasGrapentin menyelesaikan pertanyaan saya.
Lihat juga di Stack Overflow: Mengapa malloc menginisialisasi nilai ke 0 dalam gcc? (kredit: @bta).
new
operator dalam C ++ (juga "tumpukan") adalah di Linux hanya bungkus untuk malloc (); kernel tidak tahu atau tidak peduli apa "heap" itu.Jawaban:
Penyimpanan dikembalikan oleh malloc () tidak diinisialisasi nol. Jangan pernah berasumsi itu.
Dalam program pengujian Anda, itu hanya kebetulan: Saya kira
malloc()
baru saja mendapat blok barummap()
, tetapi jangan mengandalkan itu juga.Sebagai contoh, jika saya menjalankan program Anda di komputer saya dengan cara ini:
Contoh kedua Anda hanya mengekspos artefak
malloc
implementasi di glibc; jika Anda melakukannya berulangmalloc
/free
dengan buffer lebih besar dari 8 byte, Anda akan melihat dengan jelas bahwa hanya 8 byte pertama yang di-zeroed, seperti pada kode contoh berikut.Keluaran:
sumber
Terlepas dari bagaimana tumpukan diinisialisasi, Anda tidak melihat tumpukan yang asli, karena perpustakaan C melakukan beberapa hal sebelum memanggil
main
, dan mereka menyentuh tumpukan.Dengan pustaka GNU C, pada x86-64, eksekusi dimulai pada titik entri _start , yang memanggil
__libc_start_main
untuk mengatur segalanya, dan yang terakhir akhirnya memanggilmain
. Tetapi sebelum memanggilmain
, ia memanggil sejumlah fungsi lain, yang menyebabkan berbagai bagian data ditulis ke stack. Isi tumpukan tidak dihapus di antara panggilan fungsi, jadi ketika Anda masukmain
, tumpukan Anda berisi sisa dari panggilan fungsi sebelumnya.Ini hanya menjelaskan hasil yang Anda dapatkan dari tumpukan, lihat jawaban lain mengenai pendekatan dan asumsi umum Anda.
sumber
main()
dipanggil, rutin inisialisasi mungkin telah memodifikasi memori yang dikembalikan olehmalloc()
- terutama jika pustaka C ++ terhubung. Dengan asumsi "heap" diinisialisasi ke apa pun adalah asumsi yang benar-benar buruk.Dalam kedua kasus, Anda mendapatkan memori yang tidak diinisialisasi , dan Anda tidak dapat membuat asumsi tentang isinya.
Ketika OS harus membagi halaman baru ke proses Anda (apakah itu untuk tumpukan atau untuk arena yang digunakan oleh
malloc()
), itu menjamin bahwa ia tidak akan mengekspos data dari proses lain; cara biasa untuk memastikan itu adalah mengisinya dengan nol (tetapi sama-sama valid untuk menimpa dengan hal lain, termasuk bahkan nilai halaman/dev/urandom
- pada kenyataannya beberapamalloc()
implementasi debugging menulis pola non-nol, untuk menangkap asumsi yang salah seperti milik Anda).Jika
malloc()
dapat memenuhi permintaan dari memori yang sudah digunakan dan dirilis oleh proses ini, isinya tidak akan dihapus (sebenarnya, kliring tidak ada hubungannya denganmalloc()
dan tidak bisa - itu harus terjadi sebelum memori dipetakan ke dalam ruang alamat Anda). Anda mungkin mendapatkan memori yang sebelumnya telah ditulis oleh proses / program Anda (misalnya sebelumnyamain()
).Dalam program contoh Anda, Anda melihat
malloc()
wilayah yang belum ditulis oleh proses ini (yaitu langsung dari halaman baru) dan tumpukan yang telah ditulis ke (denganmain()
kode pra -program Anda). Jika Anda memeriksa lebih banyak tumpukan, Anda akan menemukan nol-diisi lebih jauh ke bawah (ke arah pertumbuhan).Jika Anda benar-benar ingin memahami apa yang terjadi di tingkat OS, saya sarankan Anda melewati lapisan C Library dan berinteraksi menggunakan panggilan sistem seperti
brk()
dan sebagaimmap()
gantinya.sumber
malloc()
danfree()
berulang kali. Meskipun tidak ada yang perlumalloc()
menggunakan kembali penyimpanan yang sama yang baru-baru ini dibebaskan, dalam percobaan,malloc()
memang melakukan itu. Itu terjadi untuk mengembalikan alamat yang sama setiap kali, tetapi juga membatalkan memori setiap kali, yang saya tidak harapkan. Ini menarik bagi saya. Eksperimen lebih lanjut telah menimbulkan pertanyaan hari ini.malloc()
benar-benar tidak melakukan apa - apa dengan memori yang mereka berikan kepada Anda - itu baik yang sebelumnya digunakan, atau baru ditugaskan (dan karena itu memusatkan perhatian oleh OS). Dalam tes Anda, Anda jelas mendapatkan yang terakhir. Demikian pula, memori tumpukan diberikan untuk proses Anda dalam keadaan dihapus, tetapi Anda tidak memeriksanya cukup jauh untuk melihat bagian-bagian proses Anda belum menyentuh. Stack memori Anda adalah dibersihkan sebelum itu diberikan kepada proses Anda.calloc
mungkin menjadi pilihan (bukanmemset
)mmap(MAP_ANONYMOUS)
kecuali Anda menggunakannyaMAP_POPULATE
juga. Mudah-mudahan halaman stack baru didukung oleh halaman fisik baru dan ditransfer (dipetakan dalam tabel halaman perangkat keras, serta pointer / daftar panjang pemetaan kernel) ketika tumbuh, karena biasanya memori stack baru sedang ditulis ketika disentuh untuk pertama kalinya . Tetapi ya, kernel harus menghindari kebocoran data, dan nol adalah yang termurah dan paling berguna.Premis Anda salah.
Apa yang Anda gambarkan sebagai 'keamanan' adalah benar-benar kerahasiaan , artinya tidak ada proses yang dapat membaca memori proses lainnya, kecuali jika memori ini dibagi secara eksplisit di antara proses-proses ini. Dalam sistem operasi, ini adalah salah satu aspek dari isolasi kegiatan bersamaan, atau proses.
Apa yang dilakukan sistem operasi untuk memastikan isolasi ini, adalah setiap kali memori diminta oleh proses untuk alokasi tumpukan atau tumpukan, memori ini berasal dari suatu wilayah dalam memori fisik yang diisi dengan nol, atau yang diisi dengan sampah yang berasal dari proses yang sama .
Hal ini memastikan bahwa Anda hanya pernah melihat nol, atau junk Anda sendiri, sehingga kerahasiaan terjamin, dan kedua tumpukan dan tumpukan adalah 'mengamankan', meskipun tidak selalu (nol) diinisialisasi.
Anda terlalu banyak membaca pengukuran Anda.
sumber