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

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

このコミットは、GoランタイムにおけるerrorCString型の定義を変更し、セキュリティ上の脆弱性を修正するものです。具体的には、errorCStringuintptrのエイリアスから、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文字列として読み取ろうとする

この問題は、errorCStringuintptrのエイリアスであるため、リフレクションがerrorCStringuintptrとして認識し、その基底型であるuintptrの値を直接操作できてしまう点にありました。

変更後:

type errorCString struct{ cstr uintptr }

変更後、errorCStringuintptrcstrという名前のフィールドとして持つ構造体になりました。この変更により、errorCStringはもはやuintptrのエイリアスではなく、独立した構造体型となります。

この構造体への変更が、リフレクションによる攻撃を防ぐ上で非常に重要です。

  1. 型情報の分離: errorCStringは独自の型となり、その内部構造(cstrフィールド)はカプセル化されます。リフレクションを使ってerrorCStringのインスタンスを作成しようとしても、直接uintptrの値を設定することはできません。構造体のフィールドにアクセスするには、フィールド名(cstr)を指定する必要があります。
  2. リフレクションによる操作の制限: 攻撃者がリフレクションを使ってerrorCStringのインスタンスを作成しようとしても、そのインスタンスのcstrフィールドに直接任意のuintptr値を設定することは、以前よりも困難になります。特に、reflect.New(reflect.TypeOf(errorCString{})).Elem()で得られるValueは、errorCString構造体自体を表し、そのcstrフィールドにアクセスするには、さらにFieldByName("cstr")のような操作が必要になります。これにより、意図しないuintptr値の注入が難しくなります。
  3. 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にキャストしていましたが、これはerrorCStringuintptrのエイリアスであったため可能でした。 変更後は、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が構造体になったことによる自然な変更であり、scstrフィールドに正しく割り当てられることを保証します。

これらの変更により、errorCStringはより堅牢な型となり、リフレクションを悪用した不正なメモリアクセスを防ぐことができるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード
  • GitHubのGoリポジトリのコミット履歴
  • Go言語のIssueトラッカー (Issue #7084に関する詳細情報があれば、より深い理解が得られますが、今回の検索では直接的な情報は見つかりませんでした。しかし、コミットメッセージとコード変更から問題の性質は十分に推測できます。)
  • Go言語におけるリフレクションとセキュリティに関する一般的な議論や記事。