私はELF仕様( http://www.skyfree.org/linux/references/ELF_Format.pdf )を研究していますが、プログラムの読み込みプロセスについて明確ではない点が1つあります。スタックが初期化され、初期ページサイズが何であるか。これがテストです(Ubuntu x86-64):
$ cat test.s
.text
.global _start
_start:
mov $0x3c,%eax
mov $0,%edi
syscall
$ as test.s -o test.o && ld test.o
$ gdb a.out -q
Reading symbols from a.out...(no debugging symbols found)...done.
(gdb) b _start
Breakpoint 1 at 0x400078
(gdb) run
Starting program: ~/a.out
Breakpoint 1, 0x0000000000400078 in _start ()
(gdb) print $sp
$1 = (void *) 0x7fffffffdf00
(gdb) info proc map
process 20062
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x400000 0x401000 0x1000 0x0 ~/a.out
0x7ffff7ffa000 0x7ffff7ffd000 0x3000 0x0 [vvar]
0x7ffff7ffd000 0x7ffff7fff000 0x2000 0x0 [vdso]
0x7ffffffde000 0x7ffffffff000 0x21000 0x0 [stack]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]
ELF仕様では、最初にこのスタックページがどのように、またはなぜ存在するかについてはほとんど言及していませんが、スタックはSP argcをポイントし、 argv、envp、およびそのすぐ上の補助ベクトル。これは確認済みですが、SPの下にはどのくらいのスペースがありますか?私のシステムでは、SPの下に0x1FF00
バイトがマッピングされていますが、おそらくこれは上からカウントダウンしていますスタックの0x7ffffffff000
にあり、フルマッピングには0x21000
バイトがあります。
スタックのすぐ下のページが「ガードページ」であり、それに書き込むと自動的に書き込み可能になり、「スタックが大きくなる」(おそらく単純なスタックの処理が「うまくいく」ようになる)ことを知っていますが、巨大なスタックフレームを使用すると、ガードページとsegfaultをオーバーシュートする可能性があるため、プロセスの開始時にすでに適切に割り当てられているスペースの量を確認します。
[〜#〜] edit [〜#〜]:いくつかのデータがあると、何が起こっているのかさらに不確かになります。テストは次のとおりです。
.text
.global _start
_start:
subq $0x7fe000,%rsp
movq $1,(%rsp)
mov $0x3c,%eax
mov $0,%edi
syscall
ここで定数0x7fe000
のさまざまな値を試して、何が起こるかを確認しました。この値の場合、segfaultが発生するかどうかは不確定です。 GDBによると、subq
命令はそれ自体でmmapのサイズを拡張します。これは私には不思議です(linuxはレジスターの内容をどのように知るのですか?)しかし、このプログラムは通常、なんらかの理由。 GOTやPLTセクションを使用していないため、ASLRが非決定性を引き起こすことはありません。実行ファイルは常に仮想メモリの同じ場所に常にロードされます。それで、これはPIDまたは物理メモリの不規則性の流出ですか?全体として、ランダムアクセスで実際に合法的に利用可能なスタックの量、およびRSPの変更または合法的なメモリの「範囲外」の領域への書き込みでどれほどの量が要求されるかについて、私は非常に混乱しています。
この質問がELFに関するものだとは思いません。私の知る限り、ELFはプログラムイメージを " flat pack "にファイルに変換し、最初の実行に備えて再構成する方法を定義しています。 OSの動作がPOSIXに昇格されていない場合、スタックの定義とその実装方法は、CPU固有とOS固有の間のどこかにあります。 ELF仕様では、スタック上で何が必要かについていくつかの要求がありますが、疑いはありません。
あなたの質問から:
スタックのすぐ下のページが「ガードページ」であり、それに書き込むと自動的に書き込み可能になり、「スタックが下がっていく」(おそらく単純なスタック処理が「うまくいく」ように)ことを知っていますが、巨大なスタックフレームを使用すると、ガードページとsegfaultをオーバーシュートする可能性があるため、プロセスの開始時にすでに適切に割り当てられているスペースの量を特定したいと思います。
これについての信頼できる参照を見つけるのに苦労しています。しかし、私はこれが正しくないことを示唆するのに十分な数の権限のない参照を見つけました。
私が読んだことから、ガードページは、スタックの最大割り当て以外のアクセスをキャッチするために使用されており、「通常の」スタックの成長のためではありません。実際のメモリ割り当て(メモリアドレスへのページのマッピング)は、オンデマンドで行われます。つまり、stack-baseとstack-base-max-stack-size + 1の間にあるメモリ内のマップされていないアドレスにアクセスすると、CPUによって例外がトリガーされる可能性がありますが、カーネルはページをマッピングすることによって例外を処理しますセグメンテーション違反をカスケードしないメモリの。
したがって、最大割り当て内のスタックにアクセスしても、セグメンテーション違反は発生しません。あなたが発見したように
ドキュメントの調査は、スレッドの作成とイメージの読み込みに関するLinuxドキュメントの行に従う必要があります( fork(2) 、 clone(2) 、 execve(2) =)。 execveのドキュメント は興味深いものについて言及しています:
引数と環境のサイズの制限
... snip ...
カーネル2.6.23以降では、ほとんどのアーキテクチャがソフトから派生したサイズ制限をサポートしています RLIMIT_STACK リソース制限( getrlimit(2) を参照)
... snip ...
これは、制限がそれをサポートするアーキテクチャを必要とすることを確認し、制限されている場所も参照します( getrlimit(2) )。
RLIMIT_STACK
これは、バイト単位のプロセススタックの最大サイズです。この制限に達すると、SIGSEGV信号が生成されます。このシグナルを処理するには、プロセスは代替シグナルスタック(sigaltstack(2))を使用する必要があります。
Linux 2.6.23以降、この制限により、プロセスのコマンドライン引数と環境変数に使用されるスペースの量も決まります。詳細については、execve(2)を参照してください。
x86アセンブラーはわかりません。しかし、SSレジスターが変更されたときにx86 CPUによってトリガーされる可能性がある「スタックフォールト例外」に注意を向けます。 間違っている場合は修正してくださいが、x86-64でSS:SPが「RSP」になったと思います。したがって、私が正しく理解していれば、減少したRSP(subq $0x7fe000,%rsp
)。
222ページを参照してください: https://xem.github.io/minix86/manual/intel-x86-and-64-manual-vol3/o_fe12b1e2a880e0ce.html
すべてのプロセスメモリ領域(コード、静的データ、ヒープ、スタックなど)には境界があり、領域外のメモリアクセス、または読み取り専用領域への書き込みアクセスはCPU例外を生成します。カーネルはこれらのメモリ領域を維持します。領域外へのアクセスは、セグメンテーションフォールト信号の形でユーザー空間まで伝播します。
すべての例外が領域外のメモリにアクセスすることによって生成されるわけではありません。リージョン内アクセスも例外を生成する可能性があります。たとえば、ページが物理メモリにマップされていない場合、ページフォールトハンドラーはこれを実行中のプロセスに対して透過的に処理します。
プロセスのメインスタック領域には、最初は少数のページフレームのみがマップされていますが、スタックポインターを介してさらにデータがプッシュされると、自動的に大きくなります。例外ハンドラーは、アクセスがスタック用に予約された領域内にあることを確認し、そうである場合は新しいページフレームを割り当てます。これは、ユーザーレベルのコードの観点から自動的に行われます。
スタック領域のオーバーランを検出するために、スタック領域の終わりの直後にガードページが配置されます。最近(2017年)、一部の人々は1つのガードページでは不十分であることに気付きました。プログラムがだまされて、スタックポインターが大量にデクリメントされる可能性があるため、スタックポインターが書き込みを許可する他の領域を指すようになる可能性があるためです。この問題の「解決策」は、4 kBのガードページを1 MBのガード領域に置き換えることでした。これを参照してください LWNの記事 。
この脆弱性が悪用されるのは簡単なことではありません。たとえば、ユーザーがalloca
の呼び出しを介してプログラムが割り当てるメモリの量を制御できることが必要です。堅牢なプログラムは、特にユーザー入力から派生した場合は、alloca
に渡されたパラメーターを確認する必要があります。