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

[インデックス 10043] ファイルの概要

コミット

コミットハッシュ: 78ad19f214394a1cb1e96d448bacb84011204452
作成者: Mikkel Krautz mikkel@krautz.dk
日付: Tue Oct 18 16:31:03 2011 -0400
タイトル: ld: modify macho linkedit segment to enable OS X code signing

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/78ad19f214394a1cb1e96d448bacb84011204452

元コミット内容

ld: modify macho linkedit segment to enable OS X code signing

Move string table to the end of the __LINKEDIT segment.

This change allows Apple's codesign(1) utility to successfully sign
Go binaries, as long as they don't contain DWARF data (-w flag to
8l/6l).  This is because codesign(1) expects the string table to be
the last part of the file.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5271050

変更の背景

2011年当時、GoコンパイラはmacOS(当時はOS X)でAppleのcode signing機能との互換性に問題を抱えていました。この問題は、GoのリンカーがMach-Oバイナリ形式を生成する際に、Apple独自の要求するファイル構造に従っていなかったことが原因でした。

AppleのcodesignユーティリティはMach-Oバイナリの__LINKEDITセグメント内で、文字列テーブル(string table)がファイルの最後の部分に配置されることを期待していました。しかし、Goリンカーは異なる順序でデータを配置しており、これがcode signingの失敗を引き起こしていました。

このコミットは、Goバイナリに対してAppleのcode signing機能を適用できるようにするための重要な修正でした。特に、DWARFデバッグ情報を含まないバイナリ(-wフラグを使用してコンパイルされたもの)において、正常にcode signingができるようになりました。

前提知識の解説

Mach-Oバイナリ形式

Mach-Oは、macOSで使用されるオブジェクトファイル形式です。この形式は、実行可能ファイル、オブジェクトファイル、共有ライブラリ、動的ライブラリなどに使用されます。Mach-Oファイルは複数のセグメントから構成され、各セグメントは特定の種類のデータを格納します。

__LINKEDITセグメント

__LINKEDITセグメントは、Mach-Oファイルの重要な部分で、以下のような情報を格納します:

  • 動的シンボルテーブル(dynamic symbol table)
  • 文字列テーブル(string table)
  • 再配置情報(relocation information)
  • 間接シンボルテーブル(indirect symbol table)
  • コード署名データ(code signature data)

このセグメントは、実行時にダイナミックリンカー(dyld)がバイナリを読み込む際に必要な情報を提供します。

Appleのcode signing

Appleのcode signing機能は、ソフトウェアの整合性と認証を保証するためのセキュリティ機能です。code signingにより、以下が実現されます:

  • バイナリが改ざんされていないことの検証
  • 開発者の身元確認
  • macOSのセキュリティポリシーの適用

codesignユーティリティは、Mach-Oバイナリの構造に対して厳格な検証を行い、特定のレイアウト要件を満たしていない場合は署名を拒否します。

Goリンカーの歴史的背景

2011年当時、Goのツールチェーンは以下のような構成でした:

  • 6l: amd64アーキテクチャ用のリンカー
  • 8l: 386アーキテクチャ用のリンカー
  • 5l: ARMアーキテクチャ用のリンカー

これらのリンカーは、Plan 9オペレーティングシステムのツールチェーンを基に設計されており、各アーキテクチャに特化した処理を行っていました。

DWARFデバッグ情報

DWARFは、デバッグ情報を格納するための標準的な形式です。Goコンパイラは、デバッグ情報を含むバイナリを生成する際にDWARF形式を使用します。しかし、このデバッグ情報がMach-Oバイナリの構造に影響を与え、code signingの問題を引き起こすことがありました。

-wフラグを使用することで、リンカーはDWARFデバッグ情報を除去し、より軽量なバイナリを生成することができます。

技術的詳細

__LINKEDITセグメントの内部構造

__LINKEDITセグメントは、以下のような固定された順序でデータを配置する必要があります:

  1. 動的シンボルテーブル(.dynsym: 動的リンクに必要なシンボル情報
  2. PLT/GOT関連データ(.linkedit.plt, .linkedit.got: 手続き連結テーブルとグローバルオフセットテーブル
  3. 文字列テーブル(.dynstr: シンボル名などの文字列データ
  4. コード署名データ: Apple固有の署名情報(署名時に追加)

Appleのcode signing要件

Appleのcodesignユーティリティは、以下の要件を満たすMach-Oバイナリのみを受け入れます:

  1. __LINKEDITセグメントがファイルの最後に配置されている
  2. 文字列テーブルが__LINKEDITセグメント内の最後のデータ構造である
  3. セグメント内のデータが連続して配置されている(隙間がない)
  4. ファイルサイズが__LINKEDITセグメントの終端と一致している

LC_SYMTAB と LC_DYSYMTAB ロードコマンド

Mach-Oファイルには、各データ構造の位置とサイズを指定するロードコマンドが含まれています:

  • LC_SYMTAB: シンボルテーブルと文字列テーブルの位置を指定
  • LC_DYSYMTAB: 動的シンボルテーブルと間接シンボルテーブルの位置を指定

これらのロードコマンドは、データ構造の新しい配置に合わせて正確に更新される必要があります。

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

asmbmacho()関数の変更

// 変更前
s1 = lookup(".dynsym", 0);
s2 = lookup(".dynstr", 0);
s3 = lookup(".linkedit.plt", 0);
s4 = lookup(".linkedit.got", 0);

// 変更後
s1 = lookup(".dynsym", 0);
s2 = lookup(".linkedit.plt", 0);
s3 = lookup(".linkedit.got", 0);
s4 = lookup(".dynstr", 0);

LC_SYMTABロードコマンドの更新

// 変更前
ml->data[2] = linkoff + s1->size;        /* stroff */
ml->data[3] = s2->size;                  /* strsize */

// 変更後
ml->data[2] = linkoff + s1->size + s2->size + s3->size; /* stroff */
ml->data[3] = s4->size;                  /* strsize */

LC_DYSYMTABロードコマンドの更新

// 変更前
ml->data[12] = linkoff + s1->size + s2->size;    /* indirectsymoff */
ml->data[13] = (s3->size + s4->size) / 4;        /* nindirectsyms */

// 変更後
ml->data[12] = linkoff + s1->size;               /* indirectsymoff */
ml->data[13] = (s2->size + s3->size) / 4;        /* nindirectsyms */

domacholink()関数の変更

// 変更前の順序
s1 = lookup(".dynsym", 0);
s2 = lookup(".dynstr", 0);
s3 = lookup(".linkedit.plt", 0);
s4 = lookup(".linkedit.got", 0);

// 変更後の順序
s1 = lookup(".dynsym", 0);
s2 = lookup(".linkedit.plt", 0);
s3 = lookup(".linkedit.got", 0);
s4 = lookup(".dynstr", 0);

文字列テーブルの4バイト境界整列

// 変更前
while(s2->size%4)
    adduint8(s2, 0);

// 変更後
while(s4->size%4)
    adduint8(s4, 0);

コアとなるコードの解説

1. データ構造の再配置

このコミットの核心は、__LINKEDITセグメント内のデータ構造の順序を変更することです。変更により、以下のような新しい配置が実現されます:

__LINKEDIT セグメント:
+-------------------+
| .dynsym           |  <- 動的シンボルテーブル
+-------------------+
| .linkedit.plt     |  <- PLT関連データ
+-------------------+
| .linkedit.got     |  <- GOT関連データ
+-------------------+
| .dynstr           |  <- 文字列テーブル(最後に移動)
+-------------------+

2. ロードコマンドの調整

データ構造の位置が変更されたため、それぞれのロードコマンドで指定されるオフセットも調整する必要があります:

  • 文字列テーブルオフセット(stroff): 他のすべてのデータ構造のサイズを加えた位置に移動
  • 間接シンボルオフセット(indirectsymoff): 動的シンボルテーブルの直後に配置
  • 各サイズフィールド: 新しい変数割り当てに対応

3. 4バイト境界整列

Mach-Oファイルの各データ構造は、4バイト境界に整列される必要があります。文字列テーブルが最後に移動したため、この整列処理も対応する変数(s4)に変更されています。

4. 一貫性の確保

asmbmacho()関数とdomacholink()関数の両方で同じ順序を使用することで、ファイル構造の一貫性を確保しています。コメント「must match domacholink below」は、この重要な要件を明記しています。

関連リンク

参考にした情報源リンク