[インデックス 16263] ファイルの概要
このコミットは、Go言語のリンカ (cmd/ld
) およびCgo関連のランタイムファイルに、.note.GNU-stack
セクションを追加する変更を導入しています。このセクションは、実行可能ファイルのスタック領域が実行不可能であることをマークするために使用され、セキュリティの向上に寄与します。
コミット
commit e0db7fae8728ef47b8cd020d7fe993069c3e61d0
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Fri May 3 16:33:21 2013 +0800
cmd/ld: add .note.GNU-stack section for external linking
Fixes #5392.
R=iant, r
CC=golang-dev
https://golang.org/cl/9119043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e0db7fae8728ef47b8cd020d7fe993069c3e61d0
元コミット内容
cmd/ld: add .note.GNU-stack section for external linking
Fixes #5392.
このコミットは、外部リンキングのために .note.GNU-stack
セクションを追加します。これはIssue #5392を修正するものです。
変更の背景
この変更の背景には、実行可能ファイルのセキュリティ強化という重要な目的があります。特に、スタック領域からのコード実行を防ぐための対策が求められていました。
多くの現代的なオペレーティングシステムやコンパイラは、データ領域(ヒープやスタックなど)からのコード実行を防止するセキュリティ機能(Data Execution Prevention: DEP、またはNXビット)をサポートしています。しかし、ELF(Executable and Linkable Format)形式の実行可能ファイルにおいて、リンカが明示的にスタックが実行不可能であることを示さない場合、一部のシステムではスタックが実行可能と解釈される可能性があります。
Go言語のCgo(C言語との相互運用)を使用する際に、外部リンカ(通常はGCC)が使用されることがあります。この場合、Goのリンカが生成するオブジェクトファイルが、外部リンカによって最終的な実行可能ファイルに結合されます。このプロセスにおいて、スタックの実行可能性に関する情報が適切に伝達されないと、セキュリティ上の脆弱性につながる可能性がありました。
Issue #5392は、この問題、すなわちGoでビルドされたバイナリが、特定の環境下でスタック実行保護が適用されない可能性を指摘していたと考えられます。.note.GNU-stack
セクションは、GNUツールチェインが提供する標準的なメカニズムであり、スタックが実行不可能であることをリンカやローダーに明示的に伝えるために利用されます。このセクションが存在し、かつそのフラグが適切に設定されている場合、スタックからのコード実行は防止されます。
したがって、このコミットは、Go言語でビルドされた実行可能ファイル、特にCgoを使用するケースにおいて、スタック実行保護を確実に適用し、セキュリティを向上させることを目的としています。
前提知識の解説
ELF (Executable and Linkable Format)
ELFは、Unix系システム(Linux、BSDなど)で広く使用されている、実行可能ファイル、オブジェクトファイル、共有ライブラリの標準ファイル形式です。ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、および様々なセクションで構成されます。
- セクション: ELFファイル内のデータの論理的なまとまりです。コード(
.text
)、初期化済みデータ(.data
)、初期化されていないデータ(.bss
)、読み取り専用データ(.rodata
)など、様々な種類のセクションがあります。 - セクションヘッダテーブル: 各セクションの属性(名前、サイズ、オフセット、フラグなど)を記述します。
- プログラムヘッダテーブル: 実行可能ファイルがメモリにロードされる方法を記述します。セグメント(複数のセクションをまとめたもの)に関する情報を含みます。
スタックとスタック実行保護 (DEP/NXビット)
- スタック: プログラムの実行中に、ローカル変数、関数呼び出しの引数、戻りアドレスなどが一時的に格納されるメモリ領域です。スタックは通常、メモリの上位アドレスから下位アドレスに向かって成長します。
- スタック実行保護 (Data Execution Prevention: DEP / NXビット): メモリ領域を「実行可能」または「実行不可能」としてマークするハードウェアおよびソフトウェアの機能です。これにより、データ領域(スタックやヒープなど)に格納されたデータが誤ってコードとして実行されることを防ぎます。これは、バッファオーバーフローなどの脆弱性を悪用した攻撃(シェルコードの実行など)に対する重要な防御メカニズムです。NXビット(No eXecute bit)は、CPUレベルでこの機能を提供するものです。
.note.GNU-stack
セクション
.note.GNU-stack
は、GNUツールチェイン(GCC、Binutilsなど)によって導入された特別なセクションです。このセクション自体は通常、内容を持ちません(SHT_PROGBITS
タイプでサイズ0)。その存在と、セクションヘッダのフラグ(特に SHF_EXECINSTR
フラグ)によって、リンカやOSのローダーにスタックの実行可能性を伝えます。
- スタックが実行可能である場合:
.note.GNU-stack
セクションは存在しないか、またはSHF_EXECINSTR
フラグが設定されます。 - スタックが実行不可能である場合:
.note.GNU-stack
セクションが存在し、かつSHF_EXECINSTR
フラグが設定されません(つまり、フラグが0)。
このコミットでは、スタックを非実行可能としてマークするために、このセクションを追加し、そのフラグを0に設定しています。
Cgo
Cgoは、GoプログラムからC言語のコードを呼び出すためのGoの機能です。Cgoを使用すると、GoのコードとCのコードが同じプロセス空間で実行されます。この際、Goのツールチェインは、Cgoによって生成されたCコードをコンパイルし、外部リンカ(通常はGCC)を使用して最終的な実行可能ファイルをリンクします。この外部リンキングの過程で、Goのリンカが生成するオブジェクトファイルとCのオブジェクトファイルが結合されるため、スタックの実行可能性に関する情報の一貫性が重要になります。
技術的詳細
このコミットは、Goのリンカ (cmd/ld
) とCgoランタイムの32ビット (386)、64ビット (amd64)、ARMアーキテクチャ向けのアセンブリファイルに変更を加えています。
src/cmd/ld/elf.c
の変更
Goのリンカは、ELF形式の実行可能ファイルを生成する際に、様々なセクションを構築します。この変更では、リンカがELFファイルを生成するフローに、.note.GNU-stack
セクションの追加処理を組み込んでいます。
addstring(shstrtab, ".note.GNU-stack");
:doelf()
関数内で、セクション名文字列テーブル (shstrtab
) に.note.GNU-stack
という文字列を追加しています。これは、セクションヘッダがこの名前を参照できるようにするためです。- セクションの属性設定:
elfobj
ラベルの後の処理(ELFオブジェクトの最終的な構築フェーズ)で、.note.GNU-stack
セクションの具体的な属性を設定しています。sh = elfshname(".note.GNU-stack");
:.note.GNU-stack
という名前のセクションヘッダ構造体を取得または作成します。sh->type = SHT_PROGBITS;
: セクションのタイプをSHT_PROGBITS
に設定しています。これは、セクションがプログラムのデータを含むことを示しますが、このセクション自体は通常内容を持たないため、サイズは0になります。sh->addralign = 1;
: アドレスアライメントを1に設定しています。これは、セクションが任意のメモリ境界に配置できることを意味します。sh->flags = 0;
: これが最も重要な変更点です。 セクションのフラグを0
に設定しています。これにより、このセクションが実行可能コードを含まないこと、つまりスタックが実行不可能であることを明示的にリンカやローダーに伝えます。もしSHF_EXECINSTR
フラグが設定されていれば、スタックは実行可能と解釈される可能性があります。
このリンカ側の変更により、Goのビルドプロセスで生成されるELF実行可能ファイルには、スタックが非実行可能であることを示す .note.GNU-stack
セクションが確実に含まれるようになります。
src/pkg/runtime/cgo/gcc_386.S
, src/pkg/runtime/cgo/gcc_amd64.S
, src/pkg/runtime/cgo/gcc_arm.S
の変更
これらのファイルは、Cgoランタイムのアセンブリコードであり、GoとCの間の呼び出し規約やスタックフレームの管理など、低レベルな処理を担っています。
各ファイルに以下のコードが追加されています。
#ifdef __ELF__
.section .note.GNU-stack,"",@progbits
#endif
#ifdef __ELF__
: このディレクティブは、コンパイル環境がELF形式をサポートしている場合にのみ、以下のコードが有効になるようにします。これにより、ELF以外のシステム(例: macOSのMach-O)でのビルド時にエラーが発生するのを防ぎます。.section .note.GNU-stack,"",@progbits
: これはアセンブラディレクティブです。.section
: 新しいセクションを定義または現在のセクションを切り替えます。.note.GNU-stack
: セクションの名前です。""
: セクションのフラグです。空文字列は、セクションに特別な属性(実行可能、書き込み可能など)がないことを示します。これは、リンカが最終的にこのセクションのフラグを0
に設定するのと整合しています。@progbits
: セクションのタイプです。SHT_PROGBITS
に対応し、セクションがプログラムのデータを含むことを示します。
これらのアセンブリファイルにこのディレクティブを追加することで、Cgoが使用される場合に、Goのリンカだけでなく、外部リンカ(GCCなど)も .note.GNU-stack
セクションの存在を認識し、スタックの実行可能性に関する情報を適切に処理できるようになります。これにより、GoとCのコードが混在するバイナリにおいても、スタック実行保護が確実に適用されるようになります。
コアとなるコードの変更箇所
src/cmd/ld/elf.c
--- a/src/cmd/ld/elf.c
+++ b/src/cmd/ld/elf.c
@@ -930,6 +930,8 @@ doelf(void)\n addstring(shstrtab, ".rel.noptrdata");\n addstring(shstrtab, ".rel.data");\n }\n+ // add a .note.GNU-stack section to mark the stack as non-executable\n+ addstring(shstrtab, ".note.GNU-stack");\n }\n \n if(!debug['s']) {\n@@ -1403,8 +1405,13 @@ elfobj:\n elfshreloc(sect);\n for(sect=segdata.sect; sect!=nil; sect=sect->next)\n elfshreloc(sect);\n+ // add a .note.GNU-stack section to mark the stack as non-executable\n+ sh = elfshname(".note.GNU-stack");\n+ sh->type = SHT_PROGBITS;\n+ sh->addralign = 1;\n+ sh->flags = 0;\n }\n-\t\t\n+\n if(!debug['s']) {\n sh = elfshname(".symtab");\n sh->type = SHT_SYMTAB;
src/pkg/runtime/cgo/gcc_386.S
--- a/src/pkg/runtime/cgo/gcc_386.S
+++ b/src/pkg/runtime/cgo/gcc_386.S
@@ -40,3 +40,6 @@ EXT(__stack_chk_fail_local):\n 1:\n jmp 1b\n \n+#ifdef __ELF__\n+.section .note.GNU-stack,"",@progbits\n+#endif
src/pkg/runtime/cgo/gcc_amd64.S
--- a/src/pkg/runtime/cgo/gcc_amd64.S
+++ b/src/pkg/runtime/cgo/gcc_amd64.S
@@ -42,3 +42,7 @@ EXT(crosscall_amd64):\n popq %rbp\n popq %rbx\n ret\n+\n+#ifdef __ELF__\n+.section .note.GNU-stack,"",@progbits\n+#endif
src/pkg/runtime/cgo/gcc_arm.S
--- a/src/pkg/runtime/cgo/gcc_arm.S
+++ b/src/pkg/runtime/cgo/gcc_arm.S
@@ -34,3 +34,6 @@ EXT(__stack_chk_fail_local):\n 1:\n b 1b\n \n+#ifdef __ELF__\n+.section .note.GNU-stack,"",@progbits\n+#endif
コアとなるコードの解説
src/cmd/ld/elf.c
の変更点
このファイルはGo言語のリンカのELF出力部分を扱っています。
addstring(shstrtab, ".note.GNU-stack");
: これは、ELFファイルのセクション名文字列テーブルに.note.GNU-stack
という文字列を追加する部分です。これにより、リンカがこの名前のセクションを認識し、セクションヘッダ内で参照できるようになります。sh = elfshname(".note.GNU-stack"); sh->type = SHT_PROGBITS; sh->addralign = 1; sh->flags = 0;
: このコードブロックは、実際に.note.GNU-stack
セクションの属性を設定しています。elfshname(".note.GNU-stack")
は、指定された名前のセクションヘッダ構造体へのポインタを取得します。sh->type = SHT_PROGBITS;
は、セクションがプログラムのデータを含むことを示します。.note.GNU-stack
は通常、内容を持たない(サイズ0)ですが、このタイプが使用されます。sh->addralign = 1;
は、セクションのアライメントを1バイトに設定します。sh->flags = 0;
が最も重要です。このフラグが0
であることは、このセクションが実行可能コードを含まないことを明示的に示します。ELFのセクションフラグにおいて、SHF_EXECINSTR
ビットがセットされていない場合、そのセクションは実行不可能と見なされます。これにより、スタック領域が実行不可能であることをOSのローダーに伝達し、スタックからのコード実行を防ぎます。
src/pkg/runtime/cgo/gcc_*.S
の変更点
これらのファイルは、GoのCgoランタイムのアセンブリコードです。
#ifdef __ELF__ ... #endif
: これはプリプロセッサディレクティブで、コンパイル時に__ELF__
マクロが定義されている場合にのみ、内部のコードがコンパイルされることを意味します。これにより、ELF形式を使用しないシステム(例: macOS)でのビルド互換性が保たれます。.section .note.GNU-stack,"",@progbits
: これはアセンブラディレクティブです。.section .note.GNU-stack
:.note.GNU-stack
という名前の新しいセクションを開始します。""
: セクションのフラグを指定します。空文字列は、このセクションに特別な実行属性や書き込み属性がないことを示します。これは、リンカが最終的にこのセクションのフラグを0
に設定するのと一致します。@progbits
: セクションのタイプを指定します。これはSHT_PROGBITS
に対応し、セクションがプログラムのデータを含むことを示します。
これらのアセンブリファイルにこのディレクティブを追加することで、Cgoが使用される際に、Goのリンカだけでなく、外部リンカ(GCCなど)も .note.GNU-stack
セクションの存在を認識し、スタックの実行可能性に関する情報を適切に処理できるようになります。これにより、GoとCのコードが混在するバイナリにおいても、スタック実行保護が確実に適用され、セキュリティが向上します。
関連リンク
- Go Issue #5392: https://github.com/golang/go/issues/5392 (このコミットが修正したIssue)
- Go CL 9119043: https://golang.org/cl/9119043 (このコミットに対応するGoの変更リスト)
参考にした情報源リンク
- ELF (Executable and Linkable Format) の仕様に関するドキュメント
- GNU Binutils のドキュメント(特に
ld
とas
のセクションディレクティブについて) - Data Execution Prevention (DEP) および NXビットに関する一般的な情報
- Go言語のCgoに関する公式ドキュメント
- Linuxカーネルのスタック実行保護に関する情報
- https://sourceware.org/binutils/docs/as/Section.html (GNU Assemblerのセクションディレクティブに関するドキュメント)
- https://docs.oracle.com/cd/E19683-01/817-3677/6m751444k/index.html (ELFファイル形式に関する一般的な情報)
- https://en.wikipedia.org/wiki/Executable_and_Linkable_Format (Wikipedia: Executable and Linkable Format)
- https://en.wikipedia.org/wiki/Executable_space_protection (Wikipedia: Executable space protection)
- https://en.wikipedia.org/wiki/Cgo (Wikipedia: Cgo)
- https://go.dev/blog/cgo (Go Blog: Cgo)
- https://www.gnu.org/software/libc/manual/html_node/ELF-Format.html (GNU C Library: ELF Format)
- https://www.kernel.org/doc/html/latest/admin-guide/mm/nx.html (Linux Kernel: NX bit)
- https://lwn.net/Articles/279091/ (LWN.net: The .note.GNU-stack section)I have provided the detailed explanation of the commit as requested.