Linux

如果為了安全起見堆是零初始化的,那麼為什麼堆棧只是未初始化呢?

  • March 30, 2019

在我的 Debian GNU/Linux 9 系統上,執行二進製文件時,

  • 堆棧未初始化,但
  • 堆是零初始化的。

為什麼?

我假設零初始化可以提高安全性,但是,如果對於堆,那麼為什麼不也用於堆棧呢?堆棧也不需要安全性嗎?

據我所知,我的問題並不特定於 Debian。

範例 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;
}

輸出:

a at 0x7ffe118997e0: 194 0 294230047 32766 294230046 32766 -550453275 32713 
b at 0x561d4bbfe010: 0 0 0 0 0 0 0 0 

當然,C 標準不要求malloc()在分配記憶體之前清除記憶體,但我的 C 程序只是為了說明。這個問題不是關於 C 或 C 標準庫的問題。相反,問題是關於為什麼核心和/或執行時載入程序將堆歸零而不是堆棧歸零的問題。

另一個實驗

我的問題是關於可觀察到的 GNU/Linux 行為而不是標准文件的要求。如果不確定我的意思,那麼試試這段程式碼,它會呼叫進一步的未定義行為(*未定義,*即就 C 標準而言)來說明這一點:

#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;
}

我機器的輸出:

0x555e86696010 0 1
0x555e86696010 0 1
0x555e86696010 0 1
0x555e86696010 0 1

就 C 標準而言,行為是未定義的,所以我的問題與 C 標準無關。呼叫malloc()不需要每次都返回相同的地址,但是由於每次呼叫malloc()確實碰巧返回相同的地址,有趣的是,堆上的記憶體每次都歸零。

相比之下,堆棧似乎並沒有歸零。

我不知道後面的程式碼會在你的機器上做什麼,因為我不知道 GNU/Linux 系統的哪一層導致了觀察到的行為。你可以嘗試一下。

更新

@Kusalananda 在評論中觀察到:

對於它的價值,您最近的程式碼在 OpenBSD 上執行時會返回不同的地址和(偶爾)未初始化(非零)數據。這顯然沒有說明您在 Linux 上看到的行為。

我的結果與 OpenBSD 上的結果不同確實很有趣。顯然,我的實驗發現的不是核心(或連結器)安全協議,正如我所想的那樣,而僅僅是一個實現工件。

有鑑於此,我相信@mosvy、@StephenKitt 和@AndreasGrapentin 下面的答案一起解決了我的問題。

另請參閱 Stack Overflow:為什麼 malloc 將 gcc 中的值初始化為 0?(信用:@bta)。

malloc() 返回的儲存不是零初始化的。永遠不要假設它是。

在您的測試程序中,這只是一個僥倖:我想malloc()剛剛獲得了一個新的塊mmap(),但也不要依賴它。

例如,如果我以這種方式在我的機器上執行您的程序:

$ 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

您的第二個範例只是malloc在 glibc 中公開實現的工件;如果您使用大於 8 字節的緩衝區重複malloc/ ,您將清楚地看到只有前 8 個字節被清零,如以下範常式式碼所示。free

#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;
}

輸出:

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

引用自:https://unix.stackexchange.com/questions/509232