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

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

このコミットは、GoランタイムがWindows上でOSメモリを割り当てる際に、実行可能としてマークしないように変更するものです。具体的には、VirtualAllocシステムコールを使用する際のメモリ保護フラグをPAGE_EXECUTE_READWRITEからPAGE_READWRITEに変更しています。これにより、セキュリティの向上と、場合によってはパフォーマンスの最適化が期待されます。

コミット

commit 28f74608b5ab87cce5e19d91de915649bf4d0865
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Fri May 17 13:37:30 2013 +1000

    runtime: do not mark os memory as executable on windows
    
    R=golang-dev, bradfitz, khr
    CC=golang-dev
    https://golang.org/cl/9235046

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

https://github.com/golang/go/commit/28f74608b5ab87cce5e19d91de915649bf4d0865

元コミット内容

    runtime: do not mark os memory as executable on windows
    
    R=golang-dev, bradfitz, khr
    CC=golang-dev
    https://golang.org/cl/9235046

変更の背景

この変更の背景には、主にセキュリティの強化と、不要な実行権限の付与による潜在的なリスクの排除があります。Goランタイムは、プログラムの実行に必要なメモリをOSから動的に確保します。以前はWindows環境において、この確保されたメモリ領域にデフォルトで「読み取り」「書き込み」「実行」の権限(PAGE_EXECUTE_READWRITE)を付与していました。

しかし、データ領域として使用されるメモリに実行権限が付与されていると、悪意のあるコードがそのデータ領域に注入され、実行されるという攻撃(バッファオーバーフローなどによるコードインジェクション攻撃)のリスクが高まります。これはData Execution Prevention (DEP)などのセキュリティ機能が防ごうとする種類の攻撃です。

このコミットは、Goランタイムが確保するメモリのうち、コードの実行に直接関わらないデータ領域については、明示的に実行権限を剥奪し、「読み取り」と「書き込み」の権限(PAGE_READWRITE)のみを付与するように変更することで、このような攻撃に対する耐性を向上させることを目的としています。これにより、Goプログラムのセキュリティが強化されます。

前提知識の解説

1. 仮想メモリとメモリ保護

現代のオペレーティングシステム(OS)は、仮想メモリという概念を使用しています。これは、各プロセスが物理メモリを直接操作するのではなく、仮想アドレス空間を介してメモリにアクセスする仕組みです。OSは、この仮想アドレスを物理メモリのアドレスにマッピングし、メモリの分離と保護を実現します。

メモリ保護は、OSが各メモリページに対して設定するアクセス権限のことです。これにより、あるプロセスが他のプロセスのメモリ領域にアクセスしたり、許可されていない操作(例えば、データ領域の実行)を行ったりすることを防ぎます。

2. Windowsのメモリ管理API: VirtualAlloc

Windows APIには、仮想メモリを管理するための関数群が提供されています。その中でもVirtualAlloc関数は、プロセスのアドレス空間内でメモリ領域を予約、コミット、または解放するために使用されます。

VirtualAllocの重要な引数の一つに、flProtect(メモリ保護フラグ)があります。これは、割り当てられるメモリ領域にどのようなアクセス権限を付与するかを指定します。

3. メモリ保護フラグ

  • PAGE_READWRITE (0x0004): このフラグは、メモリ領域が読み取りと書き込みの両方でアクセス可能であることを示します。データ領域やヒープメモリなど、コードの実行を意図しないメモリ領域に通常使用されます。

  • PAGE_EXECUTE_READWRITE (0x40): このフラグは、メモリ領域が読み取り、書き込み、および実行のすべてでアクセス可能であることを示します。通常、動的に生成されるコード(JITコンパイラなど)や、実行可能ファイルがロードされるメモリ領域に使用されます。しかし、データ領域にこのフラグが付与されていると、セキュリティ上のリスクが生じます。

4. Data Execution Prevention (DEP)

DEPは、Windowsのセキュリティ機能の一つで、データ領域としてマークされたメモリ領域からのコード実行を防ぐことを目的としています。これにより、バッファオーバーフローなどの脆弱性を悪用した攻撃(例えば、スタックに注入された悪意のあるコードの実行)を軽減します。DEPが有効なシステムでは、PAGE_EXECUTE_READWRITEのような実行権限を持つメモリ領域は厳しく監視されます。

5. Goランタイムのメモリ管理

Goランタイムは、独自のメモリ管理(ガベージコレクションを含む)を行います。OSから大きなメモリブロックをSysAlloc(システムアロケーション)を通じて取得し、それをGoのヒープとして管理します。このコミットは、GoランタイムがOSからメモリを取得する際の初期のメモリ保護設定に関するものです。

技術的詳細

このコミットは、src/pkg/runtime/mem_windows.cファイル内のGoランタイムのWindows固有のメモリ管理コードを変更しています。具体的には、以下の3つの関数におけるVirtualAllocシステムコールの呼び出しで渡されるメモリ保護フラグが変更されています。

  1. runtime·SysAlloc: Goランタイムが新しいメモリ領域をコミット(物理メモリを割り当て、仮想アドレス空間にマッピング)する際に使用されます。以前はPAGE_EXECUTE_READWRITEを使用していましたが、PAGE_READWRITEに変更されました。

  2. runtime·SysReserve: Goランタイムがアドレス空間内のメモリ領域を予約する際に使用されます。この予約された領域は、後でコミットされる可能性があります。ここでもPAGE_EXECUTE_READWRITEからPAGE_READWRITEへの変更が行われました。

  3. runtime·SysMap: 予約されたメモリ領域をコミットする際に使用されます。ここでも同様にPAGE_EXECUTE_READWRITEからPAGE_READWRITEへの変更が行われました。

これらの変更により、GoランタイムがWindows上でOSから取得するメモリ領域は、デフォルトで実行権限を持たなくなります。これにより、データ領域が誤って実行可能として扱われるリスクが低減され、DEPなどのセキュリティ機能との連携がより適切になります。

Goランタイムは、必要に応じて特定のメモリ領域(例えば、JITで生成されるコードなど)に対して後から実行権限を付与するメカニズムを持っているため、この変更がGoプログラムの正常な動作を妨げることはありません。これは、必要な最小限の権限のみを付与するという「最小権限の原則」に則った変更と言えます。

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

diff --git a/src/pkg/runtime/mem_windows.c b/src/pkg/runtime/mem_windows.c
index 7840daa22c..7ac0c6aaf1 100644
--- a/src/pkg/runtime/mem_windows.c
+++ b/src/pkg/runtime/mem_windows.c
@@ -13,6 +13,7 @@ enum {
 	MEM_RESERVE = 0x8000,
 	
+	PAGE_READWRITE = 0x0004,
 	PAGE_EXECUTE_READWRITE = 0x40,
 };
 
@@ -25,7 +26,7 @@ void*
 runtime·SysAlloc(uintptr n)
 {
 	mstats.sys += n;
-	return runtime·stdcall(runtime·VirtualAlloc, 4, nil, n, (uintptr)(MEM_COMMIT|MEM_RESERVE), (uintptr)PAGE_EXECUTE_READWRITE);
+	return runtime·stdcall(runtime·VirtualAlloc, 4, nil, n, (uintptr)(MEM_COMMIT|MEM_RESERVE), (uintptr)PAGE_READWRITE);
 }
 
 void
@@ -51,12 +52,12 @@ runtime·SysReserve(void *v, uintptr n)
 {
 	// v is just a hint.
 	// First try at v.
-	v = runtime·stdcall(runtime·VirtualAlloc, 4, v, n, (uintptr)MEM_RESERVE, (uintptr)PAGE_EXECUTE_READWRITE);
+	v = runtime·stdcall(runtime·VirtualAlloc, 4, v, n, (uintptr)MEM_RESERVE, (uintptr)PAGE_READWRITE);
 	if(v != nil)
 		return v;
 	
 	// Next let the kernel choose the address.
-	return runtime·stdcall(runtime·VirtualAlloc, 4, nil, n, (uintptr)MEM_RESERVE, (uintptr)PAGE_EXECUTE_READWRITE);
+	return runtime·stdcall(runtime·VirtualAlloc, 4, nil, n, (uintptr)MEM_RESERVE, (uintptr)PAGE_READWRITE);
 }
 
 void
@@ -65,7 +66,7 @@ runtime·SysMap(void *v, uintptr n)
 	void *p;
 	
 	mstats.sys += n;
-	p = runtime·stdcall(runtime·VirtualAlloc, 4, v, n, (uintptr)MEM_COMMIT, (uintptr)PAGE_EXECUTE_READWRITE);
+	p = runtime·stdcall(runtime·VirtualAlloc, 4, v, n, (uintptr)MEM_COMMIT, (uintptr)PAGE_READWRITE);
 	if(p != v)
 		runtime·throw("runtime: cannot map pages in arena address space");
 }

コアとなるコードの解説

このコミットの核心は、src/pkg/runtime/mem_windows.cファイル内のVirtualAllocシステムコールへの引数変更です。

  1. PAGE_READWRITEの追加: まず、enumブロックにPAGE_READWRITE = 0x0004が新しく定義されています。これは、読み取りと書き込みの権限のみを持つメモリページを示す定数です。

  2. runtime·SysAllocの変更: runtime·SysAlloc関数は、GoランタイムがOSから新しいメモリを割り当てる際に呼び出されます。変更前はPAGE_EXECUTE_READWRITEフラグを使用していましたが、変更後はPAGE_READWRITEを使用するように修正されています。これにより、Goランタイムが確保するメモリは、デフォルトで実行権限を持たなくなります。

  3. runtime·SysReserveの変更: runtime·SysReserve関数は、メモリ領域を予約する際に呼び出されます。ここでも同様に、VirtualAllocflProtect引数がPAGE_EXECUTE_READWRITEからPAGE_READWRITEに変更されています。予約段階で実行権限を付与しないことで、より厳格なメモリ保護が実現されます。

  4. runtime·SysMapの変更: runtime·SysMap関数は、予約されたメモリ領域をコミット(実際に物理メモリを割り当てて使用可能にする)する際に呼び出されます。ここでもPAGE_EXECUTE_READWRITEからPAGE_READWRITEへの変更が行われています。

これらの変更は、GoランタイムがWindows上でメモリを確保する際のデフォルトの挙動を変更し、セキュリティを向上させることを目的としています。データとして使用されるメモリ領域に不要な実行権限を付与しないことで、悪意のあるコードの実行を防ぐためのOSのセキュリティ機能(DEPなど)がより効果的に機能するようになります。

関連リンク

参考にした情報源リンク