Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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の署名生成ロジックをリファクタリングし、共通の処理を抽象化することで実現されています。

具体的には、以下の変更が行われています。

  1. OpenBSD識別子の追加:

    • ElfStrNoteOpenbsdIdent という新しい文字列識別子が追加され、OpenBSDの PT_NOTE セグメントのセクション名 (.note.openbsd.ident) を表します。
    • リンカの初期化フェーズ (doelf 関数) で、HEADTYPE == Hopenbsd の場合にこの識別子が設定されます。
  2. 共通のELFノート処理の抽象化:

    • 既存の elfnetbsdsig 関数(NetBSDの署名生成ロジック)が、より汎用的な elfnote 関数にリファクタリングされました。
    • elfnote 関数は、Elf_Note のサイズ計算、セクションヘッダ (ElfShdr) のタイプ、フラグ、アラインメント、アドレス、オフセット、サイズの設定を行います。これにより、NetBSDとOpenBSDの両方の署名生成で共通のロジックが利用できるようになりました。
    • elfwritenotehdr 関数が新しく導入され、Elf_Note ヘッダの書き込みを抽象化します。これにより、名前サイズ、記述子サイズ、タイプタグを引数として受け取り、実際のノートヘッダをファイルに書き込むことができます。
  3. 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) が定義されています。
  4. リンカのフローへの統合:

    • asmb 関数(リンカの主要なアセンブル処理)内で、HEADTYPE == Hopenbsd の場合にOpenBSDの署名生成ロジックが呼び出されるように変更されました。
    • 具体的には、PT_NOTE セグメントのセクションヘッダとプログラムヘッダが適切に設定され、バイナリの最後に署名データが書き込まれるようになります。

この変更により、GoリンカはOpenBSDの新しいバイナリ要件に準拠し、Goでビルドされた実行可能ファイルがOpenBSDシステムで正しく認識され、実行されることが保証されます。

コアとなるコードの変更箇所

このコミットにおけるコアとなるコードの変更は、主に src/cmd/ld/elf.csrc/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 のサイズ計算とセクションヘッダの基本設定を行います。
  • 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 関数の新規追加:
    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);
    }
    
    OpenBSDの署名に必要なサイズを計算し、elfnote を呼び出します。
  • elfwriteopenbsdsig 関数の新規追加:
    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;
    }
    
    OpenBSDの署名データをファイルに書き込みます。

src/cmd/ld/elf.h

  • elfopenbsdsigelfwriteopenbsdsig 関数のプロトタイプ宣言が追加されました。

src/cmd/{5l,6l,8l}/asm.c

  • ElfStrNoteOpenbsdIdentenum に追加されました。
  • doelf 関数内で、HEADTYPE == Hopenbsd の場合に ElfStrNoteOpenbsdIdent が初期化されるようになりました。
  • asmb 関数内で、HEADTYPE == Hopenbsd の場合に elfopenbsdsigelfwriteopenbsdsig が呼び出されるように変更されました。これにより、OpenBSDのELF署名がバイナリに組み込まれます。

コアとなるコードの解説

このコミットの核心は、ELFバイナリにOS固有の識別子を埋め込むための汎用的なメカニズムを導入し、それをOpenBSDの要件に適用した点にあります。

  1. elfnote 関数の役割: この関数は、PT_NOTE セグメントに対応するセクションヘッダ (ElfShdr) の基本的なプロパティを設定します。SHT_NOTE タイプ、SHF_ALLOC フラグ、アラインメント、そして最も重要なのは、バイナリ内のどこにノートデータが配置されるかを示すアドレスとオフセット、およびそのサイズを計算することです。resoff は、バイナリの末尾からのオフセットを示し、ノートデータがバイナリの最後に配置されることを意味します。

  2. elfwritenotehdr 関数の役割: この関数は、Elf_Note 構造体のヘッダ部分(n_namesz, n_descsz, n_type)をELFファイルに書き込む責任を持ちます。これにより、各OS固有の署名生成関数は、この共通のヘッダ書き込みロジックを再利用できます。stridx は、セクションヘッダテーブル内のノートセクションの名前(例: .note.netbsd.ident.note.openbsd.ident)へのインデックスです。

  3. OpenBSD固有の署名ロジック (elfopenbsdsig, elfwriteopenbsdsig):

    • elfopenbsdsig は、OpenBSDの署名に必要な Elf_Note の名前と記述子の合計サイズを計算し、elfnote を呼び出してセクションヘッダを設定します。
    • elfwriteopenbsdsig は、elfwritenotehdr を使って共通のノートヘッダを書き込んだ後、OpenBSD固有の名前文字列 ("OpenBSD\0") とバージョン (0) をファイルに書き込みます。これにより、OpenBSDシステムがバイナリを正しく識別できるようになります。
  4. リンカの統合: 各アーキテクチャのリンカ (5l, 6l, 8l) の asmb 関数は、最終的なバイナリをアセンブルする場所です。ここで、ターゲットOSがOpenBSDである場合 (HEADTYPE == Hopenbsd) に、新しく追加された elfopenbsdsigelfwriteopenbsdsig が呼び出されます。これにより、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:
  • NetBSD ELF Notes (Example Context):
    • NetBSDのソースコード (sys/exec_elf.h など) は、NetBSDがどのようにELFノートを使用しているかの参考になります。
  • OpenBSD ELF Notes (Example Context):
    • OpenBSDのソースコードや開発者メーリングリストの議論は、OpenBSDがなぜ特定のELFノートを要求するようになったかの背景情報を提供します。
  • Go Toolchain Documentation:
    • Goの公式ドキュメントやソースコードは、リンカの内部動作を理解する上で役立ちます。