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

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

このコミットは、Go言語のリンカ (ld) におけるMach-O形式のバイナリに対するコード署名に関するバグ修正です。具体的には、Cgoを使用しない(純粋なGoの)バイナリにおいて、codesign_allocate ユーティリティがコード署名時に適切にパディングを処理できない問題を解決します。

コミット

commit 280d85a80bd14e732dee7e9991c35da80f62bed7
Author: Mikkel Krautz <mikkel@krautz.dk>
Date:   Mon Jan 23 09:42:09 2012 -0500

    ld: fix Mach-O code signing for non-cgo binaries
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5561060

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

https://github.com/golang/go/commit/280d85a80bd14e732dee7e9991c35da80f62bed7

元コミット内容

Go言語のリンカ (ld) において、Mach-O形式のバイナリ(macOSなどで使用される実行ファイル形式)のコード署名に関する修正。特に、Cgo(GoとC言語の相互運用機能)を使用しない純粋なGoバイナリのコード署名が正しく行われるように修正された。

変更の背景

この変更の背景には、macOSにおける実行ファイルのコード署名プロセスと、Go言語のリンカが生成するMach-Oバイナリの特性との間の不整合がありました。

macOSでは、アプリケーションの整合性と信頼性を保証するためにコード署名が広く利用されています。コード署名プロセスでは、バイナリの特定のセクション(特に __LINKEDIT セクション)に署名情報が追加されます。この署名情報を追加する際に、Appleが提供する codesign_allocate というユーティリティが使用されます。

問題は、codesign_allocate が、特に純粋なGoバイナリ(Cgoを使用しないバイナリ)に対して、__LINKEDIT セクションの末尾に LC_CODE_SIGNATURE ロードコマンドを追加する際に、必要なパディングの計算を誤ることにありました。LC_CODE_SIGNATURE ロードコマンドは16バイト境界にアラインされている必要があります。動的なMach-Oバイナリ(例えば、Cgoを使用して外部ライブラリにリンクしているGoバイナリや、一般的なC/C++バイナリ)の場合、codesign_allocate はこのアラインメントを適切に処理できます。しかし、純粋なGoバイナリでは __LINKEDIT セクションがほとんど空であるため、codesign_allocate が自身で追加するパディングを考慮に入れず、結果としてコード署名が失敗するか、不正なバイナリが生成される可能性がありました。

このコミットは、Goリンカ側で __LINKEDIT セクションの末尾を事前に16バイト境界にアラインさせることで、codesign_allocate が追加のパディングを適用する必要がないようにし、この問題を回避することを目的としています。

前提知識の解説

Mach-O (Mach Object)

Mach-Oは、macOS、iOS、watchOS、tvOSなどのAppleのオペレーティングシステムで使用される実行可能ファイル、オブジェクトコード、共有ライブラリ、ダイナミックロード可能バンドル、およびコアダンプのファイル形式です。WindowsのPE (Portable Executable) やLinuxのELF (Executable and Linkable Format) に相当します。

Mach-Oファイルは、ヘッダ、ロードコマンド、セグメント、セクションなどで構成されます。

  • ヘッダ: ファイルの基本的な情報(CPUタイプ、ファイルタイプなど)を含みます。
  • ロードコマンド: カーネルがバイナリをメモリにロードする方法を記述します。これには、セグメントの定義、共有ライブラリの依存関係、エントリポイントなどが含まれます。
  • セグメント: 実行可能コードやデータを含むメモリ領域の論理的なまとまりです。各セグメントは1つ以上のセクションを含みます。
  • セクション: セグメント内のより細かいデータ単位です。例えば、コードセクション (__text)、データセクション (__data)、読み取り専用データセクション (__rodata) などがあります。

コード署名 (Code Signing)

コード署名とは、ソフトウェアの実行可能ファイルやスクリプトにデジタル署名を付与するプロセスです。これにより、以下の目的が達成されます。

  1. 整合性の保証: ソフトウェアが署名後に改ざんされていないことを確認できます。
  2. 信頼性の保証: ソフトウェアが特定の開発元によって作成されたことを証明できます。 macOSでは、Gatekeeperなどのセキュリティ機能がコード署名を利用して、信頼できないソースからのソフトウェアの実行をブロックします。

__LINKEDIT セクション

Mach-Oバイナリにおける __LINKEDIT セクションは、動的リンカが実行時に必要とする情報(シンボルテーブル、文字列テーブル、リロケーション情報など)を格納するセクションです。コード署名情報もこのセクションの末尾に追加されることが一般的です。

LC_CODE_SIGNATURE ロードコマンド

これはMach-Oのロードコマンドの一つで、バイナリのコード署名に関する情報(署名データのオフセットとサイズ)を記述します。このロードコマンドは、Mach-Oファイル内の特定のオフセットに配置され、その配置には16バイト境界のアラインメントが要求されます。

Cgo (Go and C interoperability)

Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。Cgoを使用すると、Goプログラムは既存のCライブラリを利用したり、パフォーマンスが重要な部分をCで記述したりすることができます。

  • Cgoバイナリ: Cgoを使用しているGoプログラムは、コンパイル時にCコンパイラ(通常はGCCやClang)によってCコードがコンパイルされ、Goのコードとリンクされます。これにより、生成されるバイナリはCライブラリへの依存性を持つことが多く、動的リンカが必要とする情報が増えるため、__LINKEDIT セクションも大きくなる傾向があります。
  • 非Cgoバイナリ (純粋なGoバイナリ): Cgoを使用しないGoプログラムは、完全にGo言語で記述されており、Goランタイムと標準ライブラリのみに依存します。これらのバイナリは通常、自己完結型であり、外部のCライブラリへの依存性がありません。そのため、__LINKEDIT セクションが非常に小さくなることがあります。

codesign_allocate ユーティリティ

codesign_allocate は、Appleの開発者ツールに含まれるユーティリティで、Mach-Oバイナリにコード署名情報を追加するために使用されます。このツールは、バイナリの __LINKEDIT セクションの末尾に LC_CODE_SIGNATURE ロードコマンドと実際の署名データを挿入します。この際、LC_CODE_SIGNATURE ロードコマンドが16バイト境界にアラインされるように、必要に応じてパディングを追加する役割も担います。

技術的詳細

このコミットの技術的な核心は、Mach-Oバイナリの __LINKEDIT セクションの末尾のアラインメントを修正することにあります。

元のコードでは、s4->size%4 という条件で __LINKEDIT セクションの末尾(具体的には .dynstr セクションのサイズ)が4バイト境界にアラインされるようにパディング(0バイト)を追加していました。これは、一部のMach-Oの要件や、古いシステムでのアラインメント要件に合致していた可能性があります。

しかし、macOSのコード署名プロセスにおいて、codesign_allocate ユーティリティが LC_CODE_SIGNATURE ロードコマンドを挿入する際には、このロードコマンドが16バイト境界にアラインされている必要があります。

純粋なGoバイナリの場合、__LINKEDIT セクションは非常に小さく、特に .dynstr セクションのサイズが小さいことが多いため、4バイト境界のアラインメントでは16バイト境界のアラインメントが保証されません。

コミットのコメントによると、codesign_allocate は動的なMach-Oバイナリ(Cgoを使用するGoバイナリなど)に対しては、自身が追加するパディングを考慮して16バイトアラインメントを適切に処理できます。しかし、純粋なGoバイナリのように __LINKEDIT セクションがほとんど空の場合、codesign_allocate は自身が追加するパディングを正しく計算できず、結果として LC_CODE_SIGNATURE ロードコマンドが16バイト境界にアラインされないというバグがありました。

この修正では、while(s4->size%4)while(s4->size%16) に変更することで、Goリンカが生成するMach-Oバイナリの __LINKEDIT セクションの末尾を、最初から16バイト境界に強制的にアラインするようにしました。これにより、codesign_allocate が追加のパディングを適用する必要がなくなり、そのバグを回避できるようになります。結果として、純粋なGoバイナリもmacOS上で正しくコード署名できるようになります。

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

変更は src/cmd/ld/macho.c ファイルの domacholink 関数内で行われています。

--- a/src/cmd/ld/macho.c
+++ b/src/cmd/ld/macho.c
@@ -499,7 +499,24 @@ domacholink(void)
 	s3 = lookup(".linkedit.got", 0);
 	s4 = lookup(".dynstr", 0);

-	while(s4->size%4)
+	// Force the linkedit section to end on a 16-byte
+	// boundary.  This allows pure (non-cgo) Go binaries
+	// to be code signed correctly.
+	//
+	// Apple's codesign_allocate (a helper utility for
+	// the codesign utility) can do this fine itself if
+	// it is run on a dynamic Mach-O binary.  However,
+	// when it is run on a pure (non-cgo) Go binary, where
+	// the linkedit section is mostly empty, it fails to
+	// account for the extra padding that it itself adds
+	// when adding the LC_CODE_SIGNATURE load command
+	// (which must be aligned on a 16-byte boundary).
+	//
+	// By forcing the linkedit section to end on a 16-byte
+	// boundary, codesign_allocate will not need to apply
+	// any alignment padding itself, working around the
+	// issue.
+	while(s4->size%16)
 		adduint8(s4, 0);

 	size = s1->size + s2->size + s3->size + s4->size;

コアとなるコードの解説

変更された行は以下の部分です。

-	while(s4->size%4)
+	while(s4->size%16)
  • s4.dynstr セクション(動的文字列テーブル)を表すシンボルです。Mach-Oの __LINKEDIT セクションは、この .dynstr セクションの後に続く形で構成されることが多いため、.dynstr のサイズを調整することで __LINKEDIT セクション全体の末尾のアラインメントを制御できます。
  • s4->size.dynstr セクションの現在のサイズです。
  • % は剰余演算子です。
  • adduint8(s4, 0) は、s4 が指すセクションに1バイトのゼロパディングを追加する関数です。

元のコード while(s4->size%4) は、.dynstr セクションのサイズが4の倍数になるまでゼロパディングを追加していました。これは、セクションのサイズを4バイト境界にアラインするための処理です。

修正後のコード while(s4->size%16) は、このアラインメントの要件を4バイトから16バイトに引き上げました。これにより、.dynstr セクションのサイズが16の倍数になるまでゼロパディングが追加されます。結果として、__LINKEDIT セクション全体の末尾が16バイト境界にアラインされることが保証されます。

この変更により、codesign_allocate ユーティリティが LC_CODE_SIGNATURE ロードコマンドを挿入する際に、すでに適切なアラインメントが確保されているため、codesign_allocate 自身がパディングを計算して追加する必要がなくなります。これにより、純粋なGoバイナリのコード署名が正しく行われるようになります。

関連リンク

参考にした情報源リンク

  • 上記の「関連リンク」セクションに記載されている公式ドキュメントやGoの変更リスト。
  • Mach-Oファイル形式、コード署名、Cgoに関する一般的な技術記事やブログ投稿(具体的なURLは省略しますが、これらの概念を理解するために広く参照される情報源です)。
  • codesign_allocate ユーティリティの動作に関する情報(Appleのドキュメントや関連する開発者フォーラムなど)。
  • Go言語のリンカのソースコード(src/cmd/ld/macho.c)の分析。
  • Go言語のコンパイルとリンクに関する一般的な知識。