[インデックス 14431] ファイルの概要
このコミットは、Go言語のランタイムにおけるLinux ARMアーキテクチャ向けのシグナルハンドリング部分の修正です。具体的には、AT_RANDOM
補助ベクターから取得するランダムデータのアドレスが、常に4バイトアラインされているとは限らないという問題に対処しています。これにより、アラインメント要件が厳しいARMプロセッサ上でのクラッシュや未定義動作を防ぎ、ランダムデータの安全な読み込みを保証します。
コミット
- コミットハッシュ:
d1e06dab7c23109be4b24912212fedf3b155bfc7
- 作者: Shenghou Ma minux.ma@gmail.com
- コミット日時: 2012年11月18日 (日) 02:47:17 +0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d1e06dab7c23109be4b24912212fedf3b155bfc7
元コミット内容
runtime: don't assume AT_RANDOM provide 4-byte aligned ptr
R=dave, remyoudompheng
CC=golang-dev
https://golang.org/cl/6854056
変更の背景
この変更の背景には、Linuxシステムがプロセス起動時に提供する「補助ベクター (Auxiliary Vector)」という仕組みと、ARMアーキテクチャにおけるメモリのアラインメント要件があります。
Goランタイムは、セキュリティ上の理由から、起動時にカーネルから提供されるランダムな値を利用して、アドレス空間配置のランダム化 (ASLR) などに役立てています。このランダムな値は、ELF補助ベクターの AT_RANDOM
エントリを通じて取得されます。
従来のコードでは、AT_RANDOM
が指すアドレスが常に4バイト境界にアラインされていると仮定し、そのアドレスから直接 uint32
(4バイト整数) を読み込んでいました。しかし、特定のLinuxカーネルのバージョンやARMプロセッサの構成によっては、AT_RANDOM
が提供するポインタが4バイトアラインされていない場合があります。
ARMアーキテクチャの多くのプロセッサでは、特定のデータ型(特に整数やポインタ)をメモリから読み書きする際に、そのデータ型のサイズに応じたアラインメント(例えば、4バイト整数なら4バイト境界)が要求されます。アラインされていないアドレスから直接マルチバイトデータを読み込もうとすると、SIGBUS
シグナルによるクラッシュや、パフォーマンスの低下、あるいは予期せぬ不正なデータ読み込みといった未定義動作を引き起こす可能性があります。
このコミットは、このようなアラインメントの仮定が誤りであったために発生する可能性のある問題を修正し、Goランタイムの堅牢性を向上させることを目的としています。
前提知識の解説
ELF補助ベクター (Auxiliary Vector)
ELF (Executable and Linkable Format) は、Unix系システムで実行可能ファイル、オブジェクトファイル、共有ライブラリなどに用いられる標準的なファイル形式です。プロセスが起動される際、カーネルはプログラムの main
関数に引数 (argc
, argv
, envp
) を渡しますが、それに加えて「補助ベクター (Auxiliary Vector)」と呼ばれる情報も提供します。
補助ベクターは、AT_
で始まる定数(例: AT_PHDR
, AT_BASE
, AT_RANDOM
など)とそれに対応する値のペアのリストで構成されます。これらの情報は、プログラムが実行環境に関する特定の詳細(例えば、プログラムヘッダの場所、ベースアドレス、システムページサイズ、ランダムデータなど)を動的に取得するために使用されます。リストの終端は AT_NULL
で示されます。
AT_RANDOM
AT_RANDOM
は補助ベクターの一種で、カーネルが提供するランダムなバイト列へのポインタとその長さを示します。このランダムデータは、主にセキュリティ目的(例: ASLRのためのスタックやヒープのアドレスランダム化、ハッシュ関数のシードなど)で使用されます。通常、16バイトのランダムデータが提供されます。
メモリのアラインメント (Memory Alignment)
メモリのアラインメントとは、コンピュータのメモリ上でデータが特定の境界に配置されることを指します。例えば、「4バイトアラインメント」とは、データが4の倍数のアドレス(0x0, 0x4, 0x8, ...)に配置されることを意味します。
- なぜアラインメントが必要か:
- パフォーマンス: 多くのCPUアーキテクチャでは、アラインされたデータへのアクセスが最も効率的です。アラインされていないデータへのアクセスは、追加のCPUサイクルを必要としたり、複数回のメモリトランザクションを発生させたりするため、パフォーマンスが低下します。
- ハードウェア要件: 特に組み込みシステムや特定のRISCアーキテクチャ(ARMなど)では、アラインされていないアクセスがハードウェアレベルで許可されていない場合があります。このような場合、アラインされていないアクセスは「アラインメントフォルト」と呼ばれる例外(Linuxでは
SIGBUS
シグナル)を発生させ、プログラムをクラッシュさせます。 - アトミック操作: マルチスレッド環境でのアトミック操作(不可分操作)は、通常、アラインされたデータに対してのみ保証されます。
ARMアーキテクチャにおけるアラインメント
ARMアーキテクチャは、歴史的に厳格なアラインメント要件を持つことで知られています。特にARMv5以前のアーキテクチャでは、アラインされていないメモリアクセスはSIGBUS
シグナルを発生させ、プログラムを終了させることが一般的でした。ARMv6以降では、アラインされていないアクセスがサポートされるようになりましたが、パフォーマンスのペナルティが伴うため、可能な限りアラインされたアクセスを行うことが推奨されます。
Goランタイムは、様々なアーキテクチャで動作するため、このようなアーキテクチャ固有の制約を考慮する必要があります。
技術的詳細
このコミットが修正している問題は、runtime·setup_auxv
関数内で AT_RANDOM
からランダムデータを読み込む際に発生していました。
元のコードでは、auxv[1]
が AT_RANDOM
の値(ランダムデータへのポインタ)を保持しており、このポインタに直接オフセット + 4
を加算し、その結果のアドレスを uint32*
にキャストして間接参照していました (*(uint32*)(auxv[1] + 4)
)。これは、ランダムデータの先頭から4バイト目以降の4バイトを uint32
として読み込むことを意図しています。
しかし、もし auxv[1]
が指すアドレスが4バイトアラインされていなかった場合、auxv[1] + 4
のアドレスも4バイトアラインされない可能性があります。例えば、auxv[1]
が 0x1001
のようなアドレスを指していた場合、auxv[1] + 4
は 0x1005
となり、これは4バイトアラインされていません。このようなアラインされていないアドレスから uint32
(4バイト) を直接読み込もうとすると、ARMプロセッサではアラインメントフォルトが発生し、プログラムがクラッシュする原因となります。
修正後のコードでは、このアラインメントの問題を回避するために、バイト単位での読み込みを行っています。auxv[1]
が指すアドレスを byte*
型のポインタ rnd
にキャストし、rnd[4]
, rnd[5]
, rnd[6]
, rnd[7]
といった個々のバイトを読み込み、それらをビットシフトとビットOR演算 (|
) を使って結合することで uint32
の値 (runtime·randomNumber
) を再構築しています。
このバイト単位の読み込みは、アラインメント要件に縛られません。個々のバイトは常に1バイトアラインされているため、どのようなアドレスからでも安全に読み込むことができます。これにより、AT_RANDOM
が提供するポインタのアラインメントに関わらず、ランダムデータを正しく、かつ安全に取得できるようになります。
コアとなるコードの変更箇所
変更は src/pkg/runtime/signal_linux_arm.c
ファイル内の runtime·setup_auxv
関数に集中しています。
--- a/src/pkg/runtime/signal_linux_arm.c
+++ b/src/pkg/runtime/signal_linux_arm.c
@@ -173,11 +173,14 @@ runtime·checkgoarm(void)
void
runtime·setup_auxv(int32 argc, void *argv_list)
{
- byte **argv = &argv_list;
+ byte **argv;
byte **envp;
+ byte *rnd;
uint32 *auxv;
uint32 t;
+ argv = &argv_list;
+
// skip envp to get to ELF auxiliary vector.
for(envp = &argv[argc+1]; *envp != nil; envp++)
;
@@ -186,8 +189,10 @@ runtime·setup_auxv(int32 argc, void *argv_list) {
for(auxv=(uint32*)envp; auxv[0] != AT_NULL; auxv += 2) {
switch(auxv[0]) {
case AT_RANDOM: // kernel provided 16-byte worth of random data
- if(auxv[1])
- runtime·randomNumber = *(uint32*)(auxv[1] + 4);
+ if(auxv[1]) {
+ rnd = (byte*)auxv[1];
+ runtime·randomNumber = rnd[4] | rnd[5]<<8 | rnd[6]<<16 | rnd[7]<<24;
+ }
break;
case AT_PLATFORM: // v5l, v6l, v7l
if(auxv[1]) {
コアとなるコードの解説
変更された主要な部分は case AT_RANDOM:
ブロック内です。
変更前:
if(auxv[1])
runtime·randomNumber = *(uint32*)(auxv[1] + 4);
auxv[1]
はAT_RANDOM
の値であり、ランダムデータが格納されているメモリ領域の先頭アドレスを指すポインタです。auxv[1] + 4
は、そのアドレスから4バイトオフセットした位置を指します。これは、カーネルが提供する16バイトのランダムデータのうち、5バイト目から8バイト目(0-indexedで[4]
から[7]
)を読み込むことを意図しています。*(uint32*)(auxv[1] + 4)
は、このオフセットされたアドレスをuint32
型のポインタにキャストし、そのポインタが指す4バイトのデータをuint32
として読み込んでいます。この操作が、アラインされていないアドレスで実行されると問題を引き起こします。
変更後:
if(auxv[1]) {
rnd = (byte*)auxv[1];
runtime·randomNumber = rnd[4] | rnd[5]<<8 | rnd[6]<<16 | rnd[7]<<24;
}
rnd = (byte*)auxv[1];
:auxv[1]
が指すアドレスをbyte*
型のポインタrnd
にキャストします。これにより、rnd
を通じてメモリをバイト単位でアクセスできるようになります。runtime·randomNumber = rnd[4] | rnd[5]<<8 | rnd[6]<<16 | rnd[7]<<24;
:rnd[4]
:ランダムデータの5バイト目(0-indexedでインデックス4)を読み込みます。これはuint32
の最下位バイトになります。rnd[5]<<8
:ランダムデータの6バイト目を読み込み、8ビット左にシフトします。rnd[6]<<16
:ランダムデータの7バイト目を読み込み、16ビット左にシフトします。rnd[7]<<24
:ランダムデータの8バイト目を読み込み、24ビット左にシフトします。- これらの4つのバイト値をビットOR演算 (
|
) で結合することで、元の4バイトのuint32
値を再構築します。この方法であれば、個々のバイトアクセスは常にアラインされているため、アラインメントフォルトの心配がありません。
この変更により、AT_RANDOM
が提供するポインタが4バイトアラインされていなくても、Goランタイムは安全かつ正確にランダムデータを読み込むことができるようになり、ARMアーキテクチャ上での安定性が向上しました。
関連リンク
- Go CL (Code Review) リンク: https://golang.org/cl/6854056
参考にした情報源リンク
- ELF Auxiliary Vectors: https://man7.org/linux/man-pages/man3/getauxval.3.html
- Memory Alignment: https://en.wikipedia.org/wiki/Data_structure_alignment
- ARM Architecture Reference Manual (for alignment details, specific version depends on ARM core): (General reference, no specific URL as it's a large document)
- Linux Kernel Source Code (for
AT_RANDOM
and auxiliary vector handling): (General reference, no specific URL) - Go Source Code (for
runtime/signal_linux_arm.c
): (General reference, no specific URL)