[インデックス 16744] ファイルの概要
このコミットは、Go言語のリンカー(cmd/ld
)に関連する複数のファイルを変更しています。主な目的は、実行可能ファイル内の読み取り専用データを、実行不可能なメモリセグメントに配置することで、セキュリティを強化することです。
変更されたファイルは以下の通りです。
src/cmd/5l/asm.c
: ARMアーキテクチャ向けのリンカー/アセンブラ関連のコード。読み取り専用データセグメントの出力ロジックが変更されました。src/cmd/6l/asm.c
: x86-64アーキテクチャ向けのリンカー/アセンブラ関連のコード。読み取り専用データセグメントの出力ロジックが変更されました。src/cmd/8l/asm.c
: x86アーキテクチャ向けのリンカー/アセンブラ関連のコード。読み取り専用データセグメントの出力ロジックが変更されました。src/cmd/ld/data.c
: リンカーのデータセグメントと読み取り専用データセグメントの配置、アライメント、シンボル値の計算に関する主要なロジックが含まれています。src/cmd/ld/elf.c
: ELF(Executable and Linkable Format)形式の実行ファイル生成に関するコード。新しい読み取り専用セグメントがELF構造に正しく組み込まれるように変更されました。src/cmd/ld/lib.c
: リンカーのユーティリティ関数や外部リンカーとの連携に関するコード。gold
リンカー向けのオプションが追加されました。src/cmd/ld/lib.h
: リンカーで使用されるデータ構造、定数、セグメントタイプなどの定義が含まれています。新しい読み取り専用セグメントの型が追加されました。
コミット
commit d6d83c918c5847bbdbaf8c9de101e9ca32535df8
Author: Russ Cox <rsc@golang.org>
Date: Thu Jul 11 22:52:48 2013 -0400
cmd/ld: place read-only data in non-executable segment
R=golang-dev, dave, r
CC=golang-dev, nigeltao
https://golang.org/cl/10713043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d6d83c918c5847bbdbaf8c9de101e9ca32535df8
元コミット内容
cmd/ld: place read-only data in non-executable segment
変更の背景
このコミットの背景には、現代のオペレーティングシステムとセキュリティのベストプラクティスにおける重要な原則である「W^X (Write XOR Execute)」があります。W^Xは、「書き込み可能」と「実行可能」という2つのメモリパーミッションが同時に設定されるべきではないというセキュリティモデルです。つまり、あるメモリ領域が書き込み可能であれば実行不可能であるべきであり、実行可能であれば書き込み不可能であるべきです。
従来のリンカーの実装では、読み取り専用データ(定数、文字列リテラル、シンボルテーブルなど)が、実行可能コードを含むテキストセグメント(.text
)に配置されることがありました。これは、メモリの効率的な利用やリンカーの実装の単純化のためでしたが、セキュリティ上の脆弱性を生む可能性がありました。もし攻撃者がプログラムの脆弱性を悪用して、テキストセグメント内の読み取り専用データを書き換えることができた場合、その書き換えられたデータを実行してしまうことで、任意のコード実行(Arbitrary Code Execution)につながる恐れがありました。
このコミットは、このセキュリティリスクを軽減するために、読み取り専用データを専用の「読み取り専用かつ実行不可能な」セグメントに分離することを目的としています。これにより、たとえ読み取り専用データが何らかの理由で書き換えられたとしても、そのデータが実行されることはなくなり、攻撃の成功を困難にします。特にELF形式のバイナリでは、このようなセグメントの分離がOSレベルでサポートされており、この変更はその機能を活用するものです。
前提知識の解説
ELF (Executable and Linkable Format)
ELFは、Unix系OS(Linux、BSDなど)で広く使用されている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。ELFファイルは、プログラムヘッダとセクションヘッダという2つの主要な構造を持ちます。
- セクション (Sections): コンパイル時に生成されるコードやデータの論理的なまとまり(例:
.text
(コード),.data
(初期化済みデータ),.bss
(初期化なしデータ),.rodata
(読み取り専用データ))。 - セグメント (Segments): リンカーが実行可能ファイルを生成する際に、関連するセクションをまとめてメモリにロードするための単位。プログラムヘッダによって定義され、メモリ上の連続した領域に対応します。セグメントには、読み取り(R)、書き込み(W)、実行(X)などのパーミッションが設定されます。
メモリセグメントの種類とパーミッション
一般的な実行可能ファイルは、メモリ上でいくつかのセグメントに分割されてロードされます。
- テキストセグメント (
.text
): プログラムの実行可能コードが含まれます。通常、読み取り(R)と実行(X)のパーミッションを持ち、書き込み(W)は許可されません。 - データセグメント (
.data
): 初期化されたグローバル変数や静的変数が含まれます。通常、読み取り(R)と書き込み(W)のパーミッションを持ち、実行(X)は許可されません。 - BSSセグメント (
.bss
): 初期化されていないグローバル変数や静的変数が含まれます。実行時にゼロで初期化されます。通常、読み取り(R)と書き込み(W)のパーミッションを持ち、実行(X)は許可されません。 - 読み取り専用データセグメント (
.rodata
): 文字列リテラル、定数、シンボルテーブルなど、実行中に変更されないデータが含まれます。通常、読み取り(R)のみのパーミッションを持ち、書き込み(W)も実行(X)も許可されません。このコミットの焦点となるセグメントです。
W^X (Write XOR Execute)
W^Xは、メモリのセキュリティ原則の一つで、あるメモリページが同時に書き込み可能(W)と実行可能(X)であってはならないというものです。これにより、攻撃者が書き込み可能なメモリ領域に悪意のあるコードを注入し、それを実行することを防ぎます。読み取り専用データを実行不可能なセグメントに配置することは、このW^X原則を遵守するための重要なステップです。
リンカー (ld
)
リンカーは、コンパイラによって生成されたオブジェクトファイル(.o
ファイル)やライブラリを結合し、最終的な実行可能ファイルや共有ライブラリを生成するツールです。リンカーは、各セクションをメモリ上のどのセグメントに配置するか、シンボル参照を解決するかなどを決定します。Go言語のツールチェインでは、cmd/ld
がGoプログラムのリンクを担当します。
Go Toolchain (5l, 6l, 8l)
Go言語の初期のツールチェインでは、異なるアーキテクチャ(例: 5lはARM、6lはx86-64、8lはx86)ごとに専用のリンカー/アセンブラが存在しました。これらはGoのソースコードを各アーキテクチャのバイナリに変換する役割を担っていました。このコミットは、これらのアーキテクチャ固有のリンカーの共通部分であるcmd/ld
のロジックを変更しています。
gold
リンカー
gold
は、GNU Binutilsプロジェクトの一部である高速なリンカーです。従来のld
リンカーと比較して、大規模なプロジェクトでのリンク時間を大幅に短縮することを目的としています。gold
リンカーは、セグメントの配置やパーミッションに関する高度な制御オプションを提供しており、このコミットではその機能の一部(--rosegment
)を活用しています。
技術的詳細
このコミットの主要な技術的変更点は、Goリンカーが実行可能ファイルを生成する際に、読み取り専用データを専用の非実行可能セグメントに分離するロジックを導入したことです。
-
新しい読み取り専用データセグメント
segrodata
の導入:src/cmd/ld/lib.h
にEXTERN Segment segrodata;
が追加され、新しいセグメント構造が宣言されました。src/cmd/ld/data.c
では、dodata
関数内でsegrodata
セグメントが初期化され、読み取り専用データ(.rodata
、.typelink
、.gosymtab
、.gopclntab
など)がこのセグメントに追加されるようになりました。- この分離は、
iself
(ELF形式であるか) かつlinkmode == LinkInternal
(Goリンカーが内部的にリンクを行う場合) の場合にのみ適用されます。それ以外の場合(例: macOSやPlan 9、または外部リンカーを使用する場合)は、読み取り専用データは引き続きテキストセグメントに配置されます。これは、OSや外部リンカーが非実行可能読み取り専用セグメントをサポートしていない可能性があるため、互換性を保つための措置です。
-
セグメントのパーミッション設定:
src/cmd/ld/data.c
のaddress
関数内で、segrodata.rwx = 04;
が設定されています。これは、segrodata
セグメントが読み取り(R)可能であり、書き込み(W)も実行(X)も不可能であることを示します。これにより、W^X原則が適用されます。segrodata
は、テキストセグメントの直後に、ページ境界にアラインされて配置されます。これは、テキストセグメントと読み取り専用データセグメントが混在しないようにするためです。
-
アーキテクチャ固有のリンカーの変更 (
src/cmd/{5,6,8}l/asm.c
):- これらのファイルでは、以前は読み取り専用データがテキストセグメントの一部として出力されていました。
- 変更後、
segrodata.filelen > 0
の場合に、segrodata
の内容が明示的にファイルに出力されるようになりました。 - シンボルテーブルのオフセット計算 (
symo
) も、segrodata
のサイズを考慮するように更新されました。
-
ELFファイル生成ロジックの更新 (
src/cmd/ld/elf.c
):- ELFバイナリの生成において、
elfemitreloc
(再配置情報の出力),elfshalloc
(セクションヘッダの割り当て),elfphload
(プログラムヘッダのロード),elfshbits
(セクションの内容の書き込み),elfshreloc
(再配置セクションの書き込み) といった関数が、segrodata
セグメントも処理するように変更されました。これにより、生成されるELFファイルが新しいセグメント構造を正しく反映するようになります。 .plt
(Procedure Linkage Table) のタイプがSELFROSECT
からSELFRXSECT
に変更されました。これは、読み取り専用だが実行可能なセクション(コードを含む)と、読み取り専用で実行不可能なセクション(データのみ)を明確に区別するための型定義の変更です。
- ELFバイナリの生成において、
-
外部リンカー
gold
のサポート (src/cmd/ld/lib.c
):AssumeGoldLinker
というフラグが導入されました(デフォルトは0、つまり無効)。- もしこのフラグが有効で、かつELF形式の場合、外部リンカーに
"-Wl,--rosegment"
オプションが渡されるようになりました。このオプションはgold
リンカーに、読み取り専用データを専用のセグメントに配置するよう指示するものです。これにより、Goリンカーが内部リンクを行わない場合でも、外部リンカーの機能を利用してセキュリティ強化を図ることができます。
これらの変更により、Go言語でビルドされた実行可能ファイルは、特にLinuxなどのELFベースのシステムにおいて、より堅牢なメモリレイアウトとセキュリティ特性を持つようになりました。
コアとなるコードの変更箇所
src/cmd/ld/data.c
における segrodata
の導入と条件付き使用
--- a/src/cmd/ld/data.c
+++ b/src/cmd/ld/data.c
@@ -1252,27 +1253,56 @@ dodata(void)
\tdiag("unexpected symbol type %d for %s", s->type, s->name);
}
- /* we finished segdata, begin segtext */
+ /*
+ * We finished data, begin read-only data.
+ * Not all systems support a separate read-only non-executable data section.
+ * ELF systems do.
+ * OS X and Plan 9 do not.
+ * Windows PE may, but if so we have not implemented it.
+ * And if we're using external linking mode, the point is moot,
+ * since it's not our decision; that code expects the sections in
+ * segtext.
+ */
+ if(iself && linkmode == LinkInternal)
+ segro = &segrodata;
+ else
+ segro = &segtext;
+
s = datap;
-+
-+ datsize = 0;
-+
-+ /* read-only executable ELF, Mach-O sections */
-+ for(; s != nil && s->type < STYPE; s = s->next) {
-+ sect = addsection(&segtext, s->name, 04);
-+ sect->align = symalign(s);
-+ datsize = rnd(datsize, sect->align);
-+ sect->vaddr = datsize;
-+ s->sect = sect;
-+ s->type = SRODATA;
-+ s->value = datsize - sect->vaddr;
-+ growdatsize(&datsize, s);
-+ sect->len = datsize - sect->vaddr;
-+ }
/* read-only data */
- sect = addsection(&segtext, ".rodata", 04);
+ sect = addsection(segro, ".rodata", 04);
sect->align = maxalign(s, STYPELINK-1);
+ datsize = rnd(datsize, sect->align);
sect->vaddr = 0;
lookup("rodata", 0)->sect = sect;
lookup("erodata", 0)->sect = sect;
- datsize = 0;
for(; s != nil && s->type < STYPELINK; s = s->next) {
datsize = aligndatsize(datsize, s);
s->sect = sect;
s->type = SRODATA;
- s->value = datsize;
+ s->value = datsize - sect->vaddr;
growdatsize(&datsize, s);
}
sect->len = datsize - sect->vaddr;
src/cmd/ld/data.c
における segrodata
のパーミッション設定とアライメント
--- a/src/cmd/ld/data.c
+++ b/src/cmd/ld/data.c
@@ -1402,6 +1434,7 @@ address(void)
segtext.vaddr = va;
segtext.fileoff = HEADR;
for(s=segtext.sect; s != nil; s=s->next) {
+//print("%s at %#llux + %#llux\n", s->name, va, (vlong)s->len);
\tva = rnd(va, s->align);
\ts->vaddr = va;
\tva += s->len;
@@ -1409,8 +1442,25 @@ address(void)
segtext.len = va - INITTEXT;
segtext.filelen = segtext.len;
-\tva = rnd(va, INITRND);\n
+\tif(segrodata.sect != nil) {
+\t\t// align to page boundary so as not to mix
+\t\t// rodata and executable text.
+\t\tva = rnd(va, INITRND);
+\n
+\t\tsegrodata.rwx = 04;
+\t\tsegrodata.vaddr = va;
+\t\tsegrodata.fileoff = va - segtext.vaddr + segtext.fileoff;
+\t\tsegrodata.filelen = 0;
+\t\tfor(s=segrodata.sect; s != nil; s=s->next) {
+\t\t\tva = rnd(va, s->align);
+\t\t\ts->vaddr = va;
+\t\t\tva += s->len;
+\t\t}
+\t\tsegrodata.len = va - segrodata.vaddr;
+\t\tsegrodata.filelen = segrodata.len;
+\t}
\n
+\tva = rnd(va, INITRND);
segdata.rwx = 06;
segdata.vaddr = va;
segdata.fileoff = va - segtext.vaddr + segtext.fileoff;
コアとなるコードの解説
src/cmd/ld/data.c
における segrodata
の導入と条件付き使用
このコードブロックは、リンカーが読み取り専用データをどのように扱うかを決定する中心的な部分です。
if(iself && linkmode == LinkInternal)
segro = &segrodata;
else
segro = &segtext;
ここで、segro
というポインタが導入され、読み取り専用データを配置するセグメントが動的に選択されます。
iself
: 現在ビルドしているバイナリがELF形式であるかを示すフラグです。linkmode == LinkInternal
: Goリンカーが、外部のシステムリンカー(例:gcc
)を呼び出すのではなく、自身でリンク処理を完結させる「内部リンク」モードであるかを示すフラグです。
この条件文は、「もしELF形式のバイナリを内部リンクでビルドしているならば、読み取り専用データは新しく導入された segrodata
セグメントに配置する。そうでなければ、既存の segtext
セグメント(通常は実行可能コードを含む)に配置する」というロジックを表しています。これは、macOSやPlan 9のようなELF以外のシステムや、外部リンカーを使用する場合には、専用の非実行可能読み取り専用セグメントのサポートが期待できないため、互換性を維持するための重要な分岐です。
その後の addsection(segro, ".rodata", 04);
の行は、実際に .rodata
セクションを、上記の条件で選択された segro
セグメントに追加しています。04
は、このセクションが読み取り可能であることを示します。
src/cmd/ld/data.c
における segrodata
のパーミッション設定とアライメント
このコードブロックは、segrodata
セグメントがメモリ上でどのように配置され、どのようなパーミッションを持つかを定義しています。
if(segrodata.sect != nil) {
// align to page boundary so as not to mix
// rodata and executable text.
va = rnd(va, INITRND);
segrodata.rwx = 04;
segrodata.vaddr = va;
segrodata.fileoff = va - segtext.vaddr + segtext.fileoff;
segrodata.filelen = 0;
for(s=segrodata.sect; s != nil; s=s->next) {
va = rnd(va, s->align);
s->vaddr = va;
va += s->len;
}
segrodata.len = va - segrodata.vaddr;
segrodata.filelen = segrodata.len;
}
if(segrodata.sect != nil)
:segrodata
セグメントに実際にセクションが追加されている場合にのみ、以下の処理が実行されます。va = rnd(va, INITRND);
:va
(仮想アドレス) をINITRND
(通常はページのサイズ、例: 4KB) の倍数にアラインします。これは、実行可能コードを含むテキストセグメントと読み取り専用データセグメントが、メモリ上で異なるページに配置されるようにするためです。これにより、W^X原則を厳密に適用し、セキュリティを強化します。segrodata.rwx = 04;
: ここがこのコミットの核心的な変更点の一つです。rwx
はセグメントのパーミッションを表し、04
は読み取り(R)のみを意味します。これにより、segrodata
セグメントは書き込みも実行も不可能になります。segrodata.vaddr = va;
:segrodata
セグメントの仮想アドレスを設定します。segrodata.fileoff = va - segtext.vaddr + segtext.fileoff;
: ファイル内でのオフセットを計算します。- 続く
for
ループは、segrodata
に含まれる各セクションの仮想アドレスと長さを計算し、最終的にsegrodata
全体の長さ (segrodata.len
とsegrodata.filelen
) を決定します。
これらの変更により、Goリンカーは、ELF形式のバイナリにおいて、読み取り専用データを実行不可能な専用のメモリ領域に配置するようになり、セキュリティが向上しました。
関連リンク
- Go言語のリンカーに関するドキュメント(公式ドキュメントに直接的な詳細なリンカーの内部構造に関するドキュメントは少ないですが、Goのソースコード自体が最も正確な情報源です)
- ELFファイルフォーマットに関する資料
- W^X (Write XOR Execute) に関するセキュリティ資料
参考にした情報源リンク
- golang/go GitHubリポジトリ (特にコミット履歴とソースコード)
- Go CL 10713043 (元のコードレビューと変更の詳細)
- ELF (Executable and Linkable Format) - Wikipedia
- W^X - Wikipedia
- GNU gold linker documentation (特に
--rosegment
オプションについて)