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

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

このコミットは、Goランタイムとリンカにおける変更で、特にGoogle Native Client (NaCl) 環境で実行されるGoバイナリの互換性問題を解決することを目的としています。具体的には、NaClが特定の命令(AES命令)とメモリアクセスパターン(%gs:0x8)を拒否するために、GoバイナリがNaCl上で有効と見なされない問題を修正します。

変更されたファイルは以下の通りです。

  • src/liblink/sym.c: リンカの一部で、スレッドローカルストレージ (TLS) のオフセット計算を扱います。
  • src/pkg/runtime/alg.goc: Goランタイムの一部で、ハッシュアルゴリズム(特にaeshash)の利用を制御します。

コミット

commit 88f84b3e415ad226f9724dc486be0f1b363b9f05
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Fri Mar 14 21:33:55 2014 +0100

    liblink, runtime: make nacl/386 binaries valid.
    
    They were rejected by NaCl due to AES instructions and
    accesses to %gs:0x8, caused by wrong tlsoffset value.
    
    LGTM=iant
    R=rsc, dave, iant
    CC=golang-codereviews
    https://golang.org/cl/76050044

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

https://github.com/golang/go/commit/88f84b3e415ad226f9724dc486be0f1b363b9f05

元コミット内容

diff --git a/src/liblink/sym.c b/src/liblink/sym.c
index 0c7aae00a1..3990f7200e 100644
--- a/src/liblink/sym.c
+++ b/src/liblink/sym.c
@@ -139,9 +139,18 @@ linknew(LinkArch *arch)
  		 */
  		ctxt->tlsoffset = -2*ctxt->arch->ptrsize;
  		break;
-	
+
 	case Hnacl:
-\t\tctxt->tlsoffset = 0;\n+\t\tswitch(ctxt->arch->thechar) {\n+\t\tdefault:\n+\t\t\tsysfatal(\"unknown thread-local storage offset for nacl/%s\", ctxt->arch->name);\n+\t\tcase \'6\':\n+\t\t\tctxt->tlsoffset = 0;\n+\t\t\tbreak;\n+\t\tcase \'8\':\n+\t\t\tctxt->tlsoffset = -8;\n+\t\t\tbreak;\n+\t\t}\n \t\tbreak;\
 \n \tcase Hdarwin:
 diff --git a/src/pkg/runtime/alg.goc b/src/pkg/runtime/alg.goc
 index 81eb1664a9..9fb54cac3f 100644
 --- a/src/pkg/runtime/alg.goc
 +++ b/src/pkg/runtime/alg.goc
 @@ -21,7 +21,7 @@ runtime·memhash(uintptr *h, uintptr s, void *a)
  {
  	byte *b;
  	uintptr hash;
-\tif(use_aeshash) {\n+\tif(!NaCl && use_aeshash) {\
  	\truntime·aeshash(h, s, a);\
  	\treturn;\
  	}\
 @@ -470,6 +470,9 @@ byte runtime·aeskeysched[HashRandomBytes];
  void
  runtime·hashinit(void)
  {
+        if(NaCl)
+                return;
+\
  	// Install aes hash algorithm if we have the instructions we need
  	if((runtime·cpuid_ecx & (1 << 25)) != 0 &&  // aes (aesenc)
  	   (runtime·cpuid_ecx & (1 << 9)) != 0 &&   // sse3 (pshufb)

変更の背景

このコミットは、Goが生成するバイナリがGoogle Native Client (NaCl) 環境で実行できないという問題に対処しています。具体的には、NaClのセキュリティモデルが、特定の命令(AES命令)の使用と、%gs:0x8へのアクセスを拒否していました。これらの問題は、Goランタイムがスレッドローカルストレージ (TLS) のオフセットを誤って計算していたこと、およびハッシュ関数にAES命令を無条件で使用しようとしていたことに起因していました。

NaClは、Webブラウザ内でネイティブコードを安全に実行するためのサンドボックス環境を提供します。そのため、非常に厳格なセキュリティチェックと、許可される命令セットの制限があります。Goランタイムが生成するコードがこれらの制限に違反すると、NaClはバイナリのロードを拒否します。

前提知識の解説

Google Native Client (NaCl)

Google Native Client (NaCl) は、Webブラウザ内でネイティブコードを安全に実行するためのオープンソース技術です。CPUアーキテクチャに依存しないポータブルな実行形式(PNaCl)も存在します。NaClの主な目的は、WebアプリケーションにC/C++などのネイティブコードのパフォーマンスと機能をもたらしつつ、セキュリティ上のリスクを最小限に抑えることです。これを実現するために、NaClは厳格なサンドボックス化、命令セットの制限、メモリ保護などのメカニズムを採用しています。

AES命令

AES (Advanced Encryption Standard) 命令は、IntelやAMDなどの最新のCPUに搭載されている、AES暗号化アルゴリズムをハードウェアレベルで高速化するための特殊な命令セットです。これには、AESENC (AES暗号化ラウンド)、AESDEC (AES復号化ラウンド) などが含まれます。これらの命令は、ソフトウェアによる実装よりもはるかに高速に暗号化・復号化処理を実行できます。しかし、NaClのようなサンドボックス環境では、セキュリティ上の理由から、特定の特権命令や、サンドボックスの外部と相互作用する可能性のある命令の使用が制限されることがあります。

%gs:0x8tlsoffset (Thread Local Storage)

%gs:0x8 は、x86-64アーキテクチャにおけるアセンブリレベルのメモリ参照です。

  • %gs はセグメントレジスタの一つで、スレッドローカルストレージ (TLS) のベースアドレスを指すために使用されることがあります。TLSは、各スレッドが独自のデータを持つことを可能にするメカニズムです。
  • 0x8 は、%gsが指すベースアドレスからのオフセット(8バイト)を示します。

Goランタイムでは、現在のゴルーチン(g構造体)へのポインタを、OSスレッドのTLSに格納することで、高速にアクセスできるようにしています。このアクセスは通常、%gsレジスタと特定のオフセットを介して行われます。

tlsoffset は、リンカがTLS変数のオフセットを計算するために使用する値です。この値が正しくないと、GoランタイムがTLSにアクセスしようとした際に、誤ったメモリ位置を参照したり、NaClのセキュリティポリシーに違反するアクセスパターンを生成したりする可能性があります。

aeshashuse_aeshash

aeshash はGoランタイム内部で使用される非暗号学的なハッシュ関数です。主にmapデータ構造の高速化のために利用されます。このハッシュ関数は、利用可能な場合にCPUのAES命令(AESENCなど)をハードウェアアクセラレーションとして利用します。これにより、ソフトウェアのみで実装されたハッシュ関数よりもはるかに高速なハッシュ計算が可能になります。

use_aeshash は、Goランタイムがaeshashを使用するかどうかを決定するための内部フラグまたはロジックです。通常、CPUがAES命令をサポートしている場合にtrueとなり、aeshashが使用されます。

技術的詳細

Goランタイムは、パフォーマンス最適化のために、利用可能なハードウェア機能(例えばAES命令)を積極的に利用しようとします。しかし、NaClのような制約の厳しい環境では、これらの最適化がセキュリティポリシーに抵触する可能性があります。

  1. AES命令の使用: Goのaeshash関数は、CPUがAES命令をサポートしている場合、それを利用してハッシュ計算を高速化します。しかし、NaClはサンドボックス内で実行されるコードに対して、特定の命令の使用を制限しています。AES命令がNaClの許可リストに含まれていない場合、Goバイナリがこれらの命令を使用しようとすると、NaClによって拒否されます。

  2. %gs:0x8へのアクセスとtlsoffsetの誤り: Goランタイムは、現在のゴルーチンの状態を管理するために、スレッドローカルストレージ (TLS) を利用します。x86-64アーキテクチャでは、%gsセグメントレジスタがTLSのベースアドレスを指し、そこから特定のオフセット(例: 0x8)を加えてゴルーチンポインタにアクセスします。 このコミット以前は、NaCl/386アーキテクチャにおけるtlsoffsetの値が誤って0に設定されていました。これは、%gs:0x0へのアクセスを意味します。しかし、NaCl環境では、%gs:0x8へのアクセスが期待されるか、あるいは%gs:0x0へのアクセスが特定の理由で許可されない場合があります。このオフセットの不一致が、NaClがバイナリを拒否する原因となっていました。

このコミットは、これらの2つの主要な問題に対処することで、Goが生成するNaCl/386バイナリがNaCl環境で正しく実行されるようにします。

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

src/liblink/sym.c

--- a/src/liblink/sym.c
+++ b/src/liblink/sym.c
@@ -139,9 +139,18 @@ linknew(LinkArch *arch)
  		 */
  		ctxt->tlsoffset = -2*ctxt->arch->ptrsize;
  		break;
-	
+
 	case Hnacl:
-\t\tctxt->tlsoffset = 0;\n+\t\tswitch(ctxt->arch->thechar) {\n+\t\tdefault:\n+\t\t\tsysfatal(\"unknown thread-local storage offset for nacl/%s\", ctxt->arch->name);\n+\t\tcase \'6\':\n+\t\t\tctxt->tlsoffset = 0;\n+\t\t\tbreak;\n+\t\tcase \'8\':\n+\t\t\tctxt->tlsoffset = -8;\n+\t\t\tbreak;\n+\t\t}\n \t\tbreak;\

この変更は、NaClターゲットのtlsoffsetの計算ロジックを修正しています。以前はNaClの場合、tlsoffsetは無条件に0に設定されていました。変更後、ctxt->arch->thechar(アーキテクチャの文字表現、例えばx86-32は'8'、x86-64は'6')に基づいて、tlsoffsetが設定されます。

  • case '6': x86-64 (amd64) の場合、tlsoffset0のままです。
  • case '8': x86-32 (386) の場合、tlsoffset-8に設定されます。これは、%gs:0x8へのアクセスを正しく処理するための変更です。

src/pkg/runtime/alg.goc

--- a/src/pkg/runtime/alg.goc
+++ b/src/pkg/runtime/alg.goc
@@ -21,7 +21,7 @@ runtime·memhash(uintptr *h, uintptr s, void *a)
  {
  	byte *b;
  	uintptr hash;
-\tif(use_aeshash) {\n+\tif(!NaCl && use_aeshash) {\
  	\truntime·aeshash(h, s, a);\
  	\treturn;\
  	}\
 @@ -470,6 +470,9 @@ byte runtime·aeskeysched[HashRandomBytes];
  void
  runtime·hashinit(void)
  {
+        if(NaCl)
+                return;
+\
  	// Install aes hash algorithm if we have the instructions we need
  	if((runtime·cpuid_ecx & (1 << 25)) != 0 &&  // aes (aesenc)
  	   (runtime·cpuid_ecx & (1 << 9)) != 0 &&   // sse3 (pshufb)

この変更は、aeshashの使用と初期化に関するものです。

  • runtime·memhash関数内で、use_aeshashtrueの場合にruntime·aeshashを呼び出す条件に、!NaClが追加されました。これにより、NaCl環境ではaeshashが使用されなくなります。
  • runtime·hashinit関数(ハッシュアルゴリズムの初期化を行う関数)の冒頭に、if(NaCl) return;が追加されました。これにより、NaCl環境ではハッシュアルゴリズムの初期化(特にAES命令のチェックと利用)がスキップされます。

コアとなるコードの解説

src/liblink/sym.cの変更

この変更は、NaCl/386アーキテクチャにおけるTLSオフセットの計算を修正します。以前は、NaClターゲットに対してtlsoffsetが常に0に設定されていました。しかし、NaCl/386環境では、Goランタイムがゴルーチンポインタにアクセスするために、%gs:0x8のような特定のオフセットを必要とします。tlsoffset0であると、生成されるコードが%gs:0x0にアクセスしようとし、これがNaClのセキュリティポリシーに違反するか、あるいは単に誤ったメモリ位置を参照することになります。

新しいロジックでは、アーキテクチャがx86-32 ('8') のNaClの場合、tlsoffset-8に設定します。これにより、リンカはTLS変数へのアクセスを生成する際に、%gs:0x8のような正しいオフセットを使用するようになります。この修正により、NaClがGoバイナリを拒否する原因となっていたTLSアクセスに関する問題が解決されます。

src/pkg/runtime/alg.gocの変更

この変更は、NaCl環境でAES命令の使用を無効にすることで、NaClがGoバイナリを拒否するもう一つの原因に対処します。

  1. runtime·memhashの変更: runtime·memhashは、Goのmapなどで使用される主要なハッシュ関数です。この関数は、CPUがAES命令をサポートしている場合にaeshashを呼び出して高速化を図ります。しかし、NaClはAES命令の使用を許可しないため、NaCl環境でaeshashが呼び出されるとバイナリが拒否されます。 if(!NaCl && use_aeshash)という条件を追加することで、NaCl環境ではuse_aeshashtrueであってもaeshashが呼び出されなくなります。これにより、NaCl環境で実行されるGoバイナリがAES命令を使用しようとすることを防ぎます。

  2. runtime·hashinitの変更: runtime·hashinitは、ランタイム起動時にハッシュアルゴリズムを初期化し、CPUがAES命令をサポートしているかどうかをチェックしてuse_aeshashフラグを設定します。 if(NaCl) return;という行を追加することで、NaCl環境ではこの初期化プロセス全体がスキップされます。これにより、NaCl環境でAES命令の存在チェックやaeshashの有効化が行われなくなり、結果としてAES命令がGoバイナリに含まれることを防ぎます。

これらの変更により、Goが生成するNaCl/386バイナリは、NaClのセキュリティ要件に準拠し、正しく実行できるようになります。

関連リンク

  • Go Native Client (NaCl) の公式ドキュメント (もし存在すれば)
  • GoのTLS実装に関する詳細なドキュメントやブログ記事
  • Goのハッシュ関数に関する詳細なドキュメントやブログ記事

参考にした情報源リンク