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

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

このコミットは、GoランタイムがWindows上でメモリを確保する際に、そのメモリを実行可能としてマークしないように変更するものです。具体的には、VirtualAllocシステムコールを使用する際のメモリ保護フラグをPAGE_EXECUTE_READWRITEからPAGE_READWRITEに変更しています。これにより、セキュリティの向上と、不要な実行可能メモリ領域の削減が図られます。

コミット

commit 3d513faa6f41cb6a02a69a0729beef6ccc594d0e
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Tue Jun 25 17:20:14 2013 +1000

    runtime: do not mark os memory as executable on windows
    
    Resubmit 3c2cddfbdaec now that windows callbacks
    are not generated during runtime.
    
    Fixes #5494
    
    R=golang-dev, minux.ma, rsc
    CC=golang-dev
    https://golang.org/cl/10487043

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

https://github.com/golang/go/commit/3d513faa6f41cb6a02a69a0729beef6ccc594d0e

元コミット内容

このコミットは、以前のコミット 3c2cddfbdaec の再提出です。元のコミットメッセージは提供されていませんが、このコミットのメッセージから「windows callbacks are not generated during runtime」という変更が先行して行われた後に、このメモリ保護フラグの変更が再提出されたことが示唆されます。

変更の背景

この変更の背景には、主に以下の2つの要因があります。

  1. セキュリティの強化: 実行可能メモリ領域は、悪意のあるコードが注入され実行されるリスク(コードインジェクション攻撃など)を高めます。必要のないメモリ領域を実行可能としてマークしないことで、攻撃対象領域を減らし、システムのセキュリティを向上させることができます。
  2. Goランタイムの進化: コミットメッセージにある「windows callbacks are not generated during runtime」という記述は、Goランタイムの内部的な変更を示しています。以前のGoランタイムでは、Windowsのコールバックメカニズムを利用するために、動的に生成されるコードをメモリに配置し、それを実行可能としてマークする必要があった可能性があります。しかし、このコミットが再提出された時点では、その必要がなくなったため、より厳格なメモリ保護設定が可能になったと考えられます。
  3. Issue #5494の修正: コミットメッセージにFixes #5494とあることから、この変更はGoのIssue 5494を解決するために行われました。このIssueは、Windows上でのメモリ保護に関する問題、特にVirtualAllocPAGE_EXECUTE_READWRITEを使用していることに対する懸念を提起していたと考えられます。

前提知識の解説

Windowsのメモリ管理とVirtualAlloc

Windowsオペレーティングシステムでは、アプリケーションがメモリを直接操作するのではなく、仮想メモリシステムを介してメモリを管理します。VirtualAllocは、Windows APIが提供する関数の一つで、仮想アドレス空間内でメモリ領域を予約、コミット、または解放するために使用されます。

  • 予約 (MEM_RESERVE): 仮想アドレス空間内に、物理メモリを割り当てずにアドレス範囲を確保します。この時点では、メモリは使用できません。

  • コミット (MEM_COMMIT): 予約された仮想アドレス空間に物理メモリを割り当て、実際に使用可能にします。

  • メモリ保護フラグ: VirtualAlloc関数には、割り当てるメモリ領域の保護属性を指定するためのフラグがあります。これらは、メモリ領域に対してどのような操作(読み取り、書き込み、実行)が許可されるかを定義します。

    • PAGE_READWRITE (0x0004): メモリ領域が読み取りおよび書き込み可能であることを示します。実行は許可されません。データ領域やヒープ領域など、コードを含まないメモリに適しています。
    • PAGE_EXECUTE_READWRITE (0x40): メモリ領域が読み取り、書き込み、および実行可能であることを示します。これは、動的にコードを生成して実行する必要がある場合(JITコンパイラなど)にのみ使用されるべきであり、セキュリティ上のリスクが高いため、慎重に扱う必要があります。

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

Goランタイムは、独自のメモリ管理システム(ガベージコレクタを含む)を持っています。Goプログラムがメモリを要求すると、ランタイムはオペレーティングシステムから大きなメモリチャンクをSysAllocSysReserveSysMapといった内部関数を通じて取得し、それをGoのヒープとして管理します。このヒープから、Goのガベージコレクタがオブジェクトの割り当てと解放を行います。

GoランタイムがOSからメモリを取得する際には、OS固有のシステムコール(WindowsではVirtualAlloc)が使用されます。このコミットは、そのVirtualAlloc呼び出しにおけるメモリ保護フラグの指定を変更するものです。

Windowsコールバック

Windowsのコールバックは、特定のイベントが発生したときにシステムがアプリケーションの関数を呼び出すメカニズムです。GoプログラムがWindows APIと連携する場合、Goの関数をWindowsが呼び出せるように、Goの関数ポインタをWindowsが理解できる形式に変換し、実行可能なメモリ領域に配置する必要がある場合があります。コミットメッセージにある「windows callbacks are not generated during runtime」という記述は、Goランタイムがこのような動的なコールバック生成を必要としなくなったことを示唆しています。これは、GoランタイムがWindows APIとの連携方法を変更したか、あるいは特定のWindows APIの利用方法を見直した結果である可能性があります。

技術的詳細

このコミットの技術的な核心は、GoランタイムがWindows上でメモリを確保する際のVirtualAllocシステムコールの引数変更にあります。

Goランタイムは、runtime·SysAllocruntime·SysReserveruntime·SysMapといった内部関数を通じて、OSからメモリを要求します。これらの関数は、最終的にWindowsのVirtualAlloc関数を呼び出します。

変更前は、これらの関数がVirtualAllocを呼び出す際に、メモリ保護フラグとしてPAGE_EXECUTE_READWRITEを指定していました。これは、割り当てられたメモリ領域が読み取り、書き込み、実行のすべてを許可することを意味します。

変更後は、メモリ保護フラグがPAGE_READWRITEに変更されました。これにより、割り当てられたメモリ領域は読み取りと書き込みのみが許可され、実行は許可されなくなります。

この変更は、Goランタイムが動的に生成したコードを、OSから直接取得したメモリ領域に配置して実行する必要がなくなったことを示しています。もし、Goランタイムが依然として動的にコードを生成し、それを実行する必要がある場合、そのコードは別の方法(例えば、Goランタイム自身が管理する、より限定された実行可能メモリ領域)で処理されるようになったと考えられます。

この変更は、特にデータ実行防止 (DEP: Data Execution Prevention) のようなセキュリティ機能との連携において重要です。DEPは、データ領域としてマークされたメモリからのコード実行を防止するセキュリティ機能です。PAGE_READWRITEを使用することで、Goランタイムが確保するメモリ領域はDEPの保護下に置かれやすくなり、悪意のあるコードの実行を防ぐのに役立ちます。

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

変更は、src/pkg/runtime/mem_windows.cファイルに集中しています。

--- a/src/pkg/runtime/mem_windows.c
+++ b/src/pkg/runtime/mem_windows.c
@@ -13,7 +13,7 @@ enum {
  	MEM_RESERVE = 0x2000,
  	MEM_RELEASE = 0x8000,
  	
- 	PAGE_EXECUTE_READWRITE = 0x40,
+ 	PAGE_READWRITE = 0x0004,
  };
  
  #pragma dynimport runtime·VirtualAlloc VirtualAlloc "kernel32.dll"
@@ -25,7 +25,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 +51,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 +65,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");
  }

コアとなるコードの解説

  1. enum定義の変更: PAGE_EXECUTE_READWRITEの定義が削除され、代わりにPAGE_READWRITE0x0004として定義されています。これは、Windows APIのVirtualAlloc関数に渡すメモリ保護フラグの定数を変更するための準備です。

    - 	PAGE_EXECUTE_READWRITE = 0x40,
    + 	PAGE_READWRITE = 0x0004,
    
  2. runtime·SysAllocの変更: runtime·SysAllocは、Goランタイムが新しいメモリ領域をコミット(物理メモリを割り当て)し、予約(アドレス空間を確保)するために使用する関数です。この関数内のruntime·stdcallVirtualAllocを呼び出すためのラッパー)の最後の引数(メモリ保護フラグ)がPAGE_EXECUTE_READWRITEからPAGE_READWRITEに変更されています。

    - 	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);
    
  3. runtime·SysReserveの変更: runtime·SysReserveは、Goランタイムが仮想アドレス空間を予約するために使用する関数です。ここでも、VirtualAlloc呼び出しのメモリ保護フラグがPAGE_EXECUTE_READWRITEからPAGE_READWRITEに変更されています。この関数は、指定されたアドレス(v)で予約を試み、失敗した場合はカーネルにアドレス選択を任せます。どちらのケースでも、メモリ保護フラグはPAGE_READWRITEが使用されます。

    - 	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);
    // ...
    - 	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);
    
  4. runtime·SysMapの変更: runtime·SysMapは、Goランタイムが既に予約された仮想アドレス空間に物理メモリをコミットするために使用する関数です。ここでも、VirtualAlloc呼び出しのメモリ保護フラグがPAGE_EXECUTE_READWRITEからPAGE_READWRITEに変更されています。

    - 	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);
    

これらの変更により、GoランタイムがWindowsから取得するすべてのメモリ領域は、デフォルトで実行不可能としてマークされるようになります。これにより、セキュリティが向上し、データ実行防止 (DEP) などのOSのセキュリティ機能との互換性が高まります。

関連リンク

参考にした情報源リンク