安全上の理由でヒープがゼロに初期化された場合、スタックが初期化されていないのはなぜですか?

安全上の理由でヒープがゼロに初期化された場合、スタックが初期化されていないのはなぜですか?

私の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標準は含まれていません。 to呼び出しはmalloc()毎回同じアドレスを返す必要はありませんが、呼び出しはmalloc()毎回同じアドレスを返すので、注意すべき点は興味深いです。ヒープのメモリは毎回消去されます。

対照的に、スタックはまだゼロに設定されていないようです。

私はGNU / Linuxシステムのどの層が観察された動作を引き起こすのかわからないので、後ろのコードがあなたのコンピュータで何をするのかわかりません。試してみてください。

修正する

@Kusalanandaはコメントで次のように観察しました。

それにもかかわらず、最近のコードは、OpenBSDで実行されたときに他のアドレスと(時々)初期化されていない(ゼロ以外)データを返します。明らかに、これはLinuxで見られる動作を説明しません。

私の結果がOpenBSDの結果と異なることは本当に面白いです。明らかに私の実験で見つけたのは、私の考えのように、カーネル(またはリンカー)のセキュリティプロトコルではなく、単に実装アーティファクトであるということでした。

これを念頭に置いて、@mosvy、@StephenKitt、および@AndreasGrapentinの次の答えが私の問題を包括的に解決したと信じています。

スタックオーバーフローも参照してください。gccのmallocが値を0に初期化するのはなぜですか?(提供:@ bta)。

ベストアンサー1

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

malloc2番目の例では、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

おすすめ記事