[インデックス 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つの要因があります。
- セキュリティの強化: 実行可能メモリ領域は、悪意のあるコードが注入され実行されるリスク(コードインジェクション攻撃など)を高めます。必要のないメモリ領域を実行可能としてマークしないことで、攻撃対象領域を減らし、システムのセキュリティを向上させることができます。
- Goランタイムの進化: コミットメッセージにある「windows callbacks are not generated during runtime」という記述は、Goランタイムの内部的な変更を示しています。以前のGoランタイムでは、Windowsのコールバックメカニズムを利用するために、動的に生成されるコードをメモリに配置し、それを実行可能としてマークする必要があった可能性があります。しかし、このコミットが再提出された時点では、その必要がなくなったため、より厳格なメモリ保護設定が可能になったと考えられます。
- Issue #5494の修正: コミットメッセージに
Fixes #5494
とあることから、この変更はGoのIssue 5494を解決するために行われました。このIssueは、Windows上でのメモリ保護に関する問題、特にVirtualAlloc
がPAGE_EXECUTE_READWRITE
を使用していることに対する懸念を提起していたと考えられます。
前提知識の解説
Windowsのメモリ管理とVirtualAlloc
Windowsオペレーティングシステムでは、アプリケーションがメモリを直接操作するのではなく、仮想メモリシステムを介してメモリを管理します。VirtualAlloc
は、Windows APIが提供する関数の一つで、仮想アドレス空間内でメモリ領域を予約、コミット、または解放するために使用されます。
-
予約 (MEM_RESERVE): 仮想アドレス空間内に、物理メモリを割り当てずにアドレス範囲を確保します。この時点では、メモリは使用できません。
-
コミット (MEM_COMMIT): 予約された仮想アドレス空間に物理メモリを割り当て、実際に使用可能にします。
-
メモリ保護フラグ:
VirtualAlloc
関数には、割り当てるメモリ領域の保護属性を指定するためのフラグがあります。これらは、メモリ領域に対してどのような操作(読み取り、書き込み、実行)が許可されるかを定義します。PAGE_READWRITE
(0x0004): メモリ領域が読み取りおよび書き込み可能であることを示します。実行は許可されません。データ領域やヒープ領域など、コードを含まないメモリに適しています。PAGE_EXECUTE_READWRITE
(0x40): メモリ領域が読み取り、書き込み、および実行可能であることを示します。これは、動的にコードを生成して実行する必要がある場合(JITコンパイラなど)にのみ使用されるべきであり、セキュリティ上のリスクが高いため、慎重に扱う必要があります。
Goランタイムのメモリ管理
Goランタイムは、独自のメモリ管理システム(ガベージコレクタを含む)を持っています。Goプログラムがメモリを要求すると、ランタイムはオペレーティングシステムから大きなメモリチャンクをSysAlloc
やSysReserve
、SysMap
といった内部関数を通じて取得し、それを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·SysAlloc
、runtime·SysReserve
、runtime·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");
}
コアとなるコードの解説
-
enum
定義の変更:PAGE_EXECUTE_READWRITE
の定義が削除され、代わりにPAGE_READWRITE
が0x0004
として定義されています。これは、Windows APIのVirtualAlloc
関数に渡すメモリ保護フラグの定数を変更するための準備です。- PAGE_EXECUTE_READWRITE = 0x40, + PAGE_READWRITE = 0x0004,
-
runtime·SysAlloc
の変更:runtime·SysAlloc
は、Goランタイムが新しいメモリ領域をコミット(物理メモリを割り当て)し、予約(アドレス空間を確保)するために使用する関数です。この関数内のruntime·stdcall
(VirtualAlloc
を呼び出すためのラッパー)の最後の引数(メモリ保護フラグ)が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);
-
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);
-
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のセキュリティ機能との互換性が高まります。
関連リンク
- Go Issue 5494: https://github.com/golang/go/issues/5494 (このコミットによって修正されたIssue)
- Go CL 10487043: https://golang.org/cl/10487043 (このコミットのGerritレビューページ)
参考にした情報源リンク
VirtualAlloc
function (memoryapi.h): https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc- Memory Protection Constants: https://learn.microsoft.com/en-us/windows/win32/memory/memory-protection-constants
- Data Execution Prevention: https://learn.microsoft.com/en-us/windows/win32/memory/data-execution-prevention
- Go runtime memory management (general concepts): https://go.dev/doc/articles/go_memory_management.html (Goのメモリ管理に関する一般的な情報源)
- Go source code (for context on
runtime·stdcall
and other runtime functions): https://github.com/golang/go