[インデックス 18199] ファイルの概要
このコミットは、GoランタイムにおけるerrorCString
型の定義を変更し、セキュリティ上の脆弱性を修正するものです。具体的には、errorCString
をuintptr
のエイリアスから、uintptr
をフィールドとして持つ構造体へと変更することで、リフレクションを用いた任意のメモリ読み取りを防いでいます。
コミット
commit 7e639c0229b96c398e04de5acaf3010252b98d2c
Author: Ian Lance Taylor <iant@golang.org>
Date: Wed Jan 8 21:40:33 2014 -0800
runtime: change errorCString to a struct
This prevents callers from using reflect to create a new
instance of errorCString with an arbitrary value and calling
the Error method to examine arbitrary memory.
Fixes #7084.
R=golang-codereviews, minux.ma, bradfitz
CC=golang-codereviews
https://golang.org/cl/49600043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7e639c0229b96c398e04de5acaf3010252b98d2c
元コミット内容
GoランタイムのerrorCString
型を、uintptr
のエイリアスから、uintptr
を内部フィールドとして持つ構造体に変更します。これにより、リフレクションを悪用して任意のメモリを読み取ることができなくなるようにします。
変更の背景
この変更の背景には、Goのリフレクション機能が悪用される可能性があったというセキュリティ上の懸念があります。以前のerrorCString
型はuintptr
のエイリアスとして定義されていました。uintptr
はポインタを整数として表現する型であり、GoのランタイムエラーメッセージをC言語の文字列(C string)として扱うために使用されていました。
問題は、Goのリフレクション機能を使うと、プログラムが実行時に型情報を検査・操作できる点にありました。悪意のあるユーザーやプログラムが、リフレクションを用いてerrorCString
型の新しいインスタンスを任意のuintptr
値(つまり、任意のメモリアドレス)で作成し、そのインスタンスのError()
メソッドを呼び出すことが可能でした。Error()
メソッドは、そのuintptr
値をC文字列として解釈し、Goの文字列に変換して返します。これにより、攻撃者はGoプログラムのメモリ空間内の任意の場所を読み取り、機密情報などを取得できる可能性がありました。
この脆弱性は、Goのランタイムエラー処理の内部実装に起因するものであり、Fixes #7084
という記述から、この問題がGoのIssueトラッカーで報告・追跡されていたことが伺えます。このコミットは、この脆弱性を修正し、Goプログラムのセキュリティを向上させることを目的としています。
前提知識の解説
Goのリフレクション (reflectパッケージ)
Go言語のreflect
パッケージは、実行時にプログラムの構造を検査・操作するための機能を提供します。これにより、型情報、フィールド、メソッドなどを動的に取得したり、新しいインスタンスを作成したり、メソッドを呼び出したりすることが可能になります。
reflect.TypeOf(i interface{}) Type
: インターフェース値i
の動的な型を返します。reflect.ValueOf(i interface{}) Value
: インターフェース値i
の動的な値を返します。Value.CanSet()
: 値が設定可能かどうかを返します。Value.Set(x Value)
: 値を設定します。reflect.New(typ Type) Value
: 指定された型の新しいポインタ値を返します。Value.Elem()
: ポインタが指す要素のValue
を返します。
リフレクションは強力な機能ですが、その性質上、誤用するとセキュリティ上の問題を引き起こす可能性があります。特に、型安全性を迂回して任意のメモリを操作できるような状況は、細心の注意を払う必要があります。
uintptr
型
uintptr
は、ポインタを保持するのに十分な大きさの符号なし整数型です。これは、GoのポインタとC言語のポインタの間で変換を行う際や、メモリのアドレスを直接操作するような低レベルな処理で利用されます。uintptr
自体はポインタではなく、ガベージコレクションの対象外であるため、誤った使い方をするとメモリリークや不正なメモリアクセスを引き起こす可能性があります。
C言語の文字列 (C string)
C言語では、文字列はヌル終端文字(\0
)で終わる文字の配列として表現されます。GoのランタイムがC言語のライブラリやシステムコールと連携する際、Goの文字列とC言語の文字列の間で変換が必要になることがあります。cstringToGo
関数は、C言語の文字列ポインタ(uintptr
として渡される)を受け取り、Goの文字列に変換する役割を担っています。
技術的詳細
このコミットの核心は、errorCString
型の定義変更にあります。
変更前:
type errorCString uintptr
変更前は、errorCString
は単にuintptr
のエイリアスでした。これは、errorCString
型の値が、実質的にuintptr
型の値と全く同じように扱えることを意味します。Goのリフレクション機能を使用すると、reflect.TypeOf(uintptr(0))
でuintptr
の型情報を取得し、その型を使って任意のuintptr
値を持つerrorCString
のインスタンスを生成することが可能でした。
例えば、以下のようなコードが考えられます(擬似コード):
import "reflect"
import "unsafe" // unsafeパッケージは通常避けるべきですが、説明のために使用
// 攻撃者が任意のメモリアドレスをuintptrとしてerrorCStringにキャスト
addr := uintptr(0x12345678) // 任意のメモリアドレス
var e errorCString = errorCString(addr)
// リフレクションを使ってerrorCStringのインスタンスを生成し、任意の値を設定
// これは、reflect.New(reflect.TypeOf(e)).Elem().Set(reflect.ValueOf(addr)) のような操作で可能だった
// その後、生成されたインスタンスのError()メソッドを呼び出す
// e.Error() が呼び出されると、cstringToGo(uintptr(e)) が実行され、
// 0x12345678番地をC文字列として読み取ろうとする
この問題は、errorCString
がuintptr
のエイリアスであるため、リフレクションがerrorCString
をuintptr
として認識し、その基底型であるuintptr
の値を直接操作できてしまう点にありました。
変更後:
type errorCString struct{ cstr uintptr }
変更後、errorCString
はuintptr
をcstr
という名前のフィールドとして持つ構造体になりました。この変更により、errorCString
はもはやuintptr
のエイリアスではなく、独立した構造体型となります。
この構造体への変更が、リフレクションによる攻撃を防ぐ上で非常に重要です。
- 型情報の分離:
errorCString
は独自の型となり、その内部構造(cstr
フィールド)はカプセル化されます。リフレクションを使ってerrorCString
のインスタンスを作成しようとしても、直接uintptr
の値を設定することはできません。構造体のフィールドにアクセスするには、フィールド名(cstr
)を指定する必要があります。 - リフレクションによる操作の制限: 攻撃者がリフレクションを使って
errorCString
のインスタンスを作成しようとしても、そのインスタンスのcstr
フィールドに直接任意のuintptr
値を設定することは、以前よりも困難になります。特に、reflect.New(reflect.TypeOf(errorCString{})).Elem()
で得られるValue
は、errorCString
構造体自体を表し、そのcstr
フィールドにアクセスするには、さらにFieldByName("cstr")
のような操作が必要になります。これにより、意図しないuintptr
値の注入が難しくなります。 Error()
メソッドの変更:Error()
メソッドの実装も、uintptr(e)
からe.cstr
へと変更されました。これにより、Error()
メソッドは構造体の内部フィールドであるcstr
に明示的にアクセスするようになります。
この変更によって、リフレクションを使って任意のuintptr
値をerrorCString
に「注入」し、そのError()
メソッドを呼び出すことで任意のメモリを読み取るという攻撃経路が遮断されます。errorCString
が構造体になったことで、その内部表現がより明確になり、外部からの不正な操作に対する防御が強化されました。
コアとなるコードの変更箇所
src/pkg/runtime/error.go
ファイルが変更されています。
--- a/src/pkg/runtime/error.go
+++ b/src/pkg/runtime/error.go
@@ -75,19 +75,20 @@ func newErrorString(s string, ret *interface{}) {
}
// An errorCString represents a runtime error described by a single C string.
-type errorCString uintptr
+// Not "type errorCString uintptr" because of http://golang.org/issue/7084.
+type errorCString struct{ cstr uintptr }
func (e errorCString) RuntimeError() {}
func cstringToGo(uintptr) string
func (e errorCString) Error() string {
- return "runtime error: " + cstringToGo(uintptr(e))
+ return "runtime error: " + cstringToGo(e.cstr)
}
// For calling from C.
func newErrorCString(s uintptr, ret *interface{}) {
- *ret = errorCString(s)
+ *ret = errorCString{s}
}
type stringer interface {
コアとなるコードの解説
errorCString
型の定義変更
// An errorCString represents a runtime error described by a single C string.
// Not "type errorCString uintptr" because of http://golang.org/issue/7084.
type errorCString struct{ cstr uintptr }
この行が最も重要な変更点です。以前はtype errorCString uintptr
と定義されていましたが、これがtype errorCString struct{ cstr uintptr }
に変更されました。
コメント// Not "type errorCString uintptr" because of http://golang.org/issue/7084.
は、この変更がIssue 7084で報告された問題(リフレクションによる任意のメモリ読み取り)を解決するためであることを明確に示しています。
Error()
メソッドの変更
func (e errorCString) Error() string {
- return "runtime error: " + cstringToGo(uintptr(e))
+ return "runtime error: " + cstringToGo(e.cstr)
}
errorCString
が構造体になったことに伴い、そのError()
メソッドの実装も変更されました。
変更前は、uintptr(e)
としてerrorCString
型の値を直接uintptr
にキャストしていましたが、これはerrorCString
がuintptr
のエイリアスであったため可能でした。
変更後は、e.cstr
として、構造体の内部フィールドであるcstr
に明示的にアクセスするようになりました。これにより、cstringToGo
関数には、構造体内にカプセル化された正しいuintptr
値が渡されることが保証されます。
newErrorCString
関数の変更
// For calling from C.
func newErrorCString(s uintptr, ret *interface{}) {
- *ret = errorCString(s)
+ *ret = errorCString{s}
}
newErrorCString
関数は、C言語から呼び出されることを想定しており、C文字列のポインタ(uintptr s
)を受け取ってerrorCString
のインスタンスを作成し、それをret
インターフェースポインタに設定します。
変更前はerrorCString(s)
と直接キャストしていましたが、変更後はerrorCString{s}
という構造体リテラルを使ってerrorCString
のインスタンスを作成するようになりました。これは、errorCString
が構造体になったことによる自然な変更であり、s
がcstr
フィールドに正しく割り当てられることを保証します。
これらの変更により、errorCString
はより堅牢な型となり、リフレクションを悪用した不正なメモリアクセスを防ぐことができるようになりました。
関連リンク
- Go言語の
reflect
パッケージに関する公式ドキュメント: https://pkg.go.dev/reflect - Go言語の
unsafe
パッケージに関する公式ドキュメント: https://pkg.go.dev/unsafe
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード
- GitHubのGoリポジトリのコミット履歴
- Go言語のIssueトラッカー (Issue #7084に関する詳細情報があれば、より深い理解が得られますが、今回の検索では直接的な情報は見つかりませんでした。しかし、コミットメッセージとコード変更から問題の性質は十分に推測できます。)
- Go言語におけるリフレクションとセキュリティに関する一般的な議論や記事。