[インデックス 13890] ファイルの概要
このコミットは、Go言語のリンカツール (cmd/ld
, cmd/5l
, cmd/6l
, cmd/8l
) において、OpenBSDのELFバイナリ署名への対応を追加し、既存のNetBSDのELF署名コードをリファクタリングするものです。これにより、GoでビルドされたバイナリがOpenBSDのシステム要件を満たすようになります。
コミット
commit 31758b2c1a4fef9c387d039190e55c640bda9408
Author: Joel Sing <jsing@google.com>
Date: Fri Sep 21 12:51:39 2012 +1000
cmd/{ld,5l,6l,8l}: add support for OpenBSD ELF signatures
OpenBSD now requires ELF binaries to have a PT_NOTE that identifies
it as an OpenBSD binary. Refactor the existing NetBSD ELF signature
code and implement support for OpenBSD ELF signatures.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6489131
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/31758b2c1a4fef9c387d039190e55c640bda9408
元コミット内容
cmd/{ld,5l,6l,8l}: add support for OpenBSD ELF signatures
OpenBSD now requires ELF binaries to have a PT_NOTE that identifies
it as an OpenBSD binary. Refactor the existing NetBSD ELF signature
code and implement support for OpenBSD ELF signatures.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6489131
変更の背景
この変更の背景には、OpenBSDオペレーティングシステムがELF (Executable and Linkable Format) バイナリに対して、特定の識別子を持つ PT_NOTE
セグメントの存在を要求するようになったという要件があります。PT_NOTE
は、バイナリに関する追加情報(例えば、ビルド情報、OSのバージョン、セキュリティ関連のメタデータなど)を格納するために使用されるELFセグメントの一種です。OpenBSDは、システムがバイナリを正しく認識し、セキュリティポリシーを適用するために、この PT_NOTE
セグメント内に特定の署名(識別情報)を必要とするようになりました。
Go言語のリンカは、実行可能ファイルを生成する際に、ターゲットOSの特定の要件を満たす必要があります。NetBSDも同様にELFバイナリに署名を要求していましたが、OpenBSDの新しい要件に対応するためには、既存のNetBSD向けコードをリファクタリングし、OpenBSD固有の署名生成ロジックを追加する必要が生じました。これにより、GoでビルドされたプログラムがOpenBSD上で問題なく実行できるようになります。
前提知識の解説
ELF (Executable and Linkable Format)
ELFは、Unix系オペレーティングシステム(Linux, BSD系OSなど)で広く使用されている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準ファイルフォーマットです。ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、そして実際のデータ(コード、データ、シンボルテーブルなど)で構成されます。
- ELFヘッダ: ファイルの基本的な情報(マジックナンバー、アーキテクチャ、OS ABIなど)を含みます。
- プログラムヘッダテーブル (Program Header Table): 実行時にメモリにロードされるセグメント(コード、データなど)のレイアウトを記述します。各エントリは
Elf_Phdr
構造体で表されます。 - セクションヘッダテーブル (Section Header Table): リンク時に使用されるファイルの論理的な構造(
.text
、.data
、.bss
などのセクション)を記述します。各エントリはElf_Shdr
構造体で表されます。
PT_NOTE セグメント
PT_NOTE
はプログラムヘッダテーブルのエントリタイプの一つで、バイナリに関する特別な情報を格納するために使用されます。このセグメントは実行可能コードやデータを含まず、通常はOSやツールがバイナリを識別したり、特定の動作を決定したりするためのメタデータを提供します。PT_NOTE
セグメント内のデータは、通常 Elf_Note
構造体のシーケンスとして構成されます。
Elf_Note 構造体
Elf_Note
構造体は、PT_NOTE
セグメント内に格納される個々のノートエントリのヘッダを定義します。
typedef struct {
Elf32_Word n_namesz; // Name size
Elf32_Word n_descsz; // Descriptor size
Elf32_Word n_type; // Type of note
} Elf32_Nhdr; // For 32-bit ELF
typedef struct {
Elf64_Word n_namesz; // Name size
Elf64_Word n_descsz; // Descriptor size
Elf64_Word n_type; // Type of note
} Elf64_Nhdr; // For 64-bit ELF
n_namesz
: ノートの所有者を示す名前のサイズ(ヌル終端文字を含む)。n_descsz
: ノートの記述子(データ)のサイズ。n_type
: ノートのタイプ。これは、記述子の内容を解釈するためのベンダー固有の識別子です。
Elf_Note
ヘッダの後に、n_namesz
バイトの名前データ、そして n_descsz
バイトの記述子データが続きます。これらのデータは通常、ワード境界にアラインされます。
Go言語のリンカ (cmd/ld
, cmd/5l
, cmd/6l
, cmd/8l
)
Go言語のツールチェインには、異なるアーキテクチャ向けのリンカが含まれています。
cmd/ld
: 汎用リンカ。cmd/5l
: ARMアーキテクチャ向けのリンカ。cmd/6l
: AMD64 (x86-64) アーキテクチャ向けのリンカ。cmd/8l
: x86 (32-bit) アーキテクチャ向けのリンカ。
これらのリンカは、Goのコンパイラによって生成されたオブジェクトファイルを結合し、実行可能なバイナリを生成する役割を担っています。OS固有のバイナリ要件(今回のELF署名など)は、これらのリンカによって処理されます。
SHT_NOTE, SHF_ALLOC
SHT_NOTE
: セクションヘッダのタイプの一つで、ノートセクションを示します。ノートセクションは、PT_NOTE
セグメントの元となるデータを含みます。SHF_ALLOC
: セクションフラグの一つで、実行時にメモリにロードされるセクションであることを示します。PT_NOTE
セグメントは通常、このフラグを持ちます。
技術的詳細
このコミットの主要な技術的変更点は、OpenBSDのELF署名要件を満たすために、GoリンカがELFバイナリに特定の PT_NOTE
セグメントを追加するロジックを実装したことです。これは、既存のNetBSDの署名生成ロジックをリファクタリングし、共通の処理を抽象化することで実現されています。
具体的には、以下の変更が行われています。
-
OpenBSD識別子の追加:
ElfStrNoteOpenbsdIdent
という新しい文字列識別子が追加され、OpenBSDのPT_NOTE
セグメントのセクション名 (.note.openbsd.ident
) を表します。- リンカの初期化フェーズ (
doelf
関数) で、HEADTYPE == Hopenbsd
の場合にこの識別子が設定されます。
-
共通のELFノート処理の抽象化:
- 既存の
elfnetbsdsig
関数(NetBSDの署名生成ロジック)が、より汎用的なelfnote
関数にリファクタリングされました。 elfnote
関数は、Elf_Note
のサイズ計算、セクションヘッダ (ElfShdr
) のタイプ、フラグ、アラインメント、アドレス、オフセット、サイズの設定を行います。これにより、NetBSDとOpenBSDの両方の署名生成で共通のロジックが利用できるようになりました。elfwritenotehdr
関数が新しく導入され、Elf_Note
ヘッダの書き込みを抽象化します。これにより、名前サイズ、記述子サイズ、タイプタグを引数として受け取り、実際のノートヘッダをファイルに書き込むことができます。
- 既存の
-
OpenBSD署名生成ロジックの実装:
elfopenbsdsig
関数が追加され、OpenBSDのPT_NOTE
セグメントのサイズを計算します。elfwriteopenbsdsig
関数が追加され、OpenBSD固有のElf_Note
ヘッダとそれに続く名前 ("OpenBSD\0"
) およびバージョン (0
) をELFファイルに書き込みます。- OpenBSDの署名に必要な定数 (
ELF_NOTE_OPENBSD_NAMESZ
,ELF_NOTE_OPENBSD_DESCSZ
,ELF_NOTE_OPENBSD_TAG
,ELF_NOTE_OPENBSD_NAME
,ELF_NOTE_OPENBSD_VERSION
) が定義されています。
-
リンカのフローへの統合:
asmb
関数(リンカの主要なアセンブル処理)内で、HEADTYPE == Hopenbsd
の場合にOpenBSDの署名生成ロジックが呼び出されるように変更されました。- 具体的には、
PT_NOTE
セグメントのセクションヘッダとプログラムヘッダが適切に設定され、バイナリの最後に署名データが書き込まれるようになります。
この変更により、GoリンカはOpenBSDの新しいバイナリ要件に準拠し、Goでビルドされた実行可能ファイルがOpenBSDシステムで正しく認識され、実行されることが保証されます。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に src/cmd/ld/elf.c
と src/cmd/ld/elf.h
に集中しています。また、各アーキテクチャ固有のリンカファイル (src/cmd/5l/asm.c
, src/cmd/6l/asm.c
, src/cmd/8l/asm.c
) も、新しいOpenBSDの識別子と署名生成ロジックを呼び出すように変更されています。
src/cmd/ld/elf.c
elfnetbsdsig
関数のリファクタリング:- 以前はNetBSD固有のロジックを含んでいた
elfnetbsdsig
が、より汎用的なelfnote
関数を呼び出すように変更されました。 elfnote
関数は、Elf_Note
のサイズ計算とセクションヘッダの基本設定を行います。
- 以前はNetBSD固有のロジックを含んでいた
elfwritenetbsdsig
関数のリファクタリング:elfwritenetbsdsig
は、新しく導入されたelfwritenotehdr
を呼び出してElf_Note
ヘッダを書き込み、その後NetBSD固有のデータ(名前とバージョン)を書き込むように変更されました。
elfnote
関数の新規追加:
この関数は、int elfnote(ElfShdr *sh, uint64 startva, uint64 resoff, int sz) { uint64 n; n = sizeof(Elf_Note) + sz + resoff % 4; // Calculate total size including padding sh->type = SHT_NOTE; sh->flags = SHF_ALLOC; sh->addralign = 4; sh->addr = startva + resoff - n; sh->off = resoff - n; sh->size = n; return n; }
PT_NOTE
セグメントのセクションヘッダ (ElfShdr
) のプロパティを設定し、そのサイズを計算します。elfwritenotehdr
関数の新規追加:
この関数は、ElfShdr * elfwritenotehdr(vlong stridx, uint32 namesz, uint32 descsz, uint32 tag) { ElfShdr *sh = nil; int i; for(i=0; i<nshdr; i++) if(shdr[i]->name == stridx) sh = shdr[i]; if(sh == nil) return nil; // Write Elf_Note header. cseek(sh->off); LPUT(namesz); LPUT(descsz); LPUT(tag); return sh; }
Elf_Note
ヘッダのn_namesz
,n_descsz
,n_type
フィールドをファイルに書き込みます。- OpenBSD署名関連の定数定義:
#define ELF_NOTE_OPENBSD_NAMESZ 8 #define ELF_NOTE_OPENBSD_DESCSZ 4 #define ELF_NOTE_OPENBSD_TAG 1 #define ELF_NOTE_OPENBSD_NAME "OpenBSD\0" #define ELF_NOTE_OPENBSD_VERSION 0
elfopenbsdsig
関数の新規追加:
OpenBSDの署名に必要なサイズを計算し、int elfopenbsdsig(ElfShdr *sh, uint64 startva, uint64 resoff) { int n; n = ELF_NOTE_OPENBSD_NAMESZ + ELF_NOTE_OPENBSD_DESCSZ; return elfnote(sh, startva, resoff, n); }
elfnote
を呼び出します。elfwriteopenbsdsig
関数の新規追加:
OpenBSDの署名データをファイルに書き込みます。int elfwriteopenbsdsig(vlong stridx) { ElfShdr *sh; // Write Elf_Note header. sh = elfwritenotehdr(stridx, ELF_NOTE_OPENBSD_NAMESZ, ELF_NOTE_OPENBSD_DESCSZ, ELF_NOTE_OPENBSD_TAG); if(sh == nil) return 0; // Followed by OpenBSD string and version. cwrite(ELF_NOTE_OPENBSD_NAME, ELF_NOTE_OPENBSD_NAMESZ); LPUT(ELF_NOTE_OPENBSD_VERSION); return sh->size; }
src/cmd/ld/elf.h
elfopenbsdsig
とelfwriteopenbsdsig
関数のプロトタイプ宣言が追加されました。
src/cmd/{5l,6l,8l}/asm.c
ElfStrNoteOpenbsdIdent
がenum
に追加されました。doelf
関数内で、HEADTYPE == Hopenbsd
の場合にElfStrNoteOpenbsdIdent
が初期化されるようになりました。asmb
関数内で、HEADTYPE == Hopenbsd
の場合にelfopenbsdsig
とelfwriteopenbsdsig
が呼び出されるように変更されました。これにより、OpenBSDのELF署名がバイナリに組み込まれます。
コアとなるコードの解説
このコミットの核心は、ELFバイナリにOS固有の識別子を埋め込むための汎用的なメカニズムを導入し、それをOpenBSDの要件に適用した点にあります。
-
elfnote
関数の役割: この関数は、PT_NOTE
セグメントに対応するセクションヘッダ (ElfShdr
) の基本的なプロパティを設定します。SHT_NOTE
タイプ、SHF_ALLOC
フラグ、アラインメント、そして最も重要なのは、バイナリ内のどこにノートデータが配置されるかを示すアドレスとオフセット、およびそのサイズを計算することです。resoff
は、バイナリの末尾からのオフセットを示し、ノートデータがバイナリの最後に配置されることを意味します。 -
elfwritenotehdr
関数の役割: この関数は、Elf_Note
構造体のヘッダ部分(n_namesz
,n_descsz
,n_type
)をELFファイルに書き込む責任を持ちます。これにより、各OS固有の署名生成関数は、この共通のヘッダ書き込みロジックを再利用できます。stridx
は、セクションヘッダテーブル内のノートセクションの名前(例:.note.netbsd.ident
や.note.openbsd.ident
)へのインデックスです。 -
OpenBSD固有の署名ロジック (
elfopenbsdsig
,elfwriteopenbsdsig
):elfopenbsdsig
は、OpenBSDの署名に必要なElf_Note
の名前と記述子の合計サイズを計算し、elfnote
を呼び出してセクションヘッダを設定します。elfwriteopenbsdsig
は、elfwritenotehdr
を使って共通のノートヘッダを書き込んだ後、OpenBSD固有の名前文字列 ("OpenBSD\0"
) とバージョン (0
) をファイルに書き込みます。これにより、OpenBSDシステムがバイナリを正しく識別できるようになります。
-
リンカの統合: 各アーキテクチャのリンカ (
5l
,6l
,8l
) のasmb
関数は、最終的なバイナリをアセンブルする場所です。ここで、ターゲットOSがOpenBSDである場合 (HEADTYPE == Hopenbsd
) に、新しく追加されたelfopenbsdsig
とelfwriteopenbsdsig
が呼び出されます。これにより、Goでビルドされた実行可能ファイルにOpenBSDが必要とするPT_NOTE
セグメントが確実に含まれるようになります。
この一連の変更により、GoリンカはELFバイナリの生成において、より柔軟かつ拡張性の高い方法でOS固有の要件に対応できるようになりました。
関連リンク
- Go Issue/Change List: https://golang.org/cl/6489131 (コミットメッセージに記載されているGoの変更リストへのリンク)
- OpenBSD ELF Binary Requirements (General Information): OpenBSDの公式ドキュメントやメーリングリストのアーカイブで、ELFバイナリの要件に関する詳細情報が見つかる可能性があります。
参考にした情報源リンク
- ELF (Executable and Linkable Format) Specification:
- PT_NOTE Segment:
- https://docs.oracle.com/cd/E19683-01/817-3677/6m751112j/index.html (Oracle Solaris Studioのドキュメントですが、PT_NOTEの一般的な説明があります)
- NetBSD ELF Notes (Example Context):
- NetBSDのソースコード (
sys/exec_elf.h
など) は、NetBSDがどのようにELFノートを使用しているかの参考になります。
- NetBSDのソースコード (
- OpenBSD ELF Notes (Example Context):
- OpenBSDのソースコードや開発者メーリングリストの議論は、OpenBSDがなぜ特定のELFノートを要求するようになったかの背景情報を提供します。
- Go Toolchain Documentation:
- Goの公式ドキュメントやソースコードは、リンカの内部動作を理解する上で役立ちます。