私の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
malloc
2番目の例では、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
答え2
スタックがどのように初期化されるかにかかわらず、Cライブラリはスタックを呼び出す前に多くのことを行い、スタックに触れるため、元のmain
スタックを見ることはできません。
x86-64では、GNU Cライブラリを使用して次のように実行します。_スタートエントリポイント、通貨__libc_start_main
main
ただし、呼び出される前にさまざまなmain
データがスタックに書き込まれるようにするさまざまな関数を呼び出します。スタックの内容は関数呼び出しの間に消去されないため、 を入力するとスタックに前のmain
関数呼び出しの残りの内容が含まれます。
これはスタックから得られた結果だけを説明します。一般的なアプローチと前提についての他の回答を参照してください。
答え3
どちらの場合も、あなたは得るでしょう初期化されていない記憶はその内容について何の仮定もできません。
オペレーティングシステムがプロセスに新しいページを割り当てる必要がある場合(スタックまたは使用されている領域についてmalloc()
)、他のプロセスのデータがゼロで埋められるようにする一般的な方法が保証されます。しかし、他のもので上書きすることもうまくいきます。ページの内容も同様です。/dev/urandom
実際、一部のデバッグmalloc()
実装では、誤った仮定をキャッチするためにゼロ以外のパターンを作成します。
プロセスによって使用および解放されたメモリに対する要求が満たされた場合、その内容はmalloc()
消去されません(実際の消去はこれに関連しておらず、そうではmalloc()
ありません。メモリがマッピングされる前に発生する必要があります)。あなたのアドレス空間に)。以前にプロセス/プログラムによって作成されたメモリを取得できます(例:before main()
)。
サンプルプログラムでは、malloc()
このプロセスによって記録されていない領域(つまり、新しいページから直接取得)と記録されたスタック(main()
プログラムのプリコーディングを介して)を表示できます。スタックをさらに詳しく見ると、下部(増加する方向)がゼロで埋められていることがわかります。
オペレーティングシステムレベルで何が起こっているのかを本当に理解したい場合は、Cライブラリ階層を迂回してシステムコール(たとえば、および)を使用して対話することをお勧めしbrk()
ますmmap()
。
答え4
あなたの前提が間違っています。
あなたが言う「安全」は本当に秘密これは、メモリがそのプロセス間で明示的に共有されない限り、どのプロセスも他のプロセスのメモリを読み取ることができないことを意味します。オペレーティングシステムでは、次のようになります。分離同時活動またはプロセス。
この分離を保証するためにオペレーティングシステムが実行することは、プロセスがヒープまたはスタック割り当てのためにメモリを要求するたびに、そのメモリがゼロで満たされた物理メモリ領域またはガベージで埋められた領域から来ることです(ここで同じプロセス。
これはあなたがゼロまたは自分のゴミだけを見ることを保証することによって機密性を保証します。両方山そしてスタックは「安全」ですが、必ずしも(0)初期化する必要はありません。
測定値を読みすぎています。