[インデックス 18381] ファイルの概要
このコミットは、Go言語の encoding/gob
パッケージにおける、破損したデータに対するデコード処理中のクラッシュ(パニック)を修正するものです。具体的には、不正な入力データが与えられた際に発生する可能性のある2つのクラッシュを解消し、デコーダの堅牢性を向上させています。
コミット
commit 7a73f32725ff8b13a4cca703972fa76e598f4436
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Thu Jan 30 07:54:57 2014 +0100
encoding/gob: fix two crashes on corrupted data.
Fixes #6323.
LGTM=r
R=r
CC=golang-codereviews
https://golang.org/cl/56870043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7a73f32725ff8b13a4cca703972fa76e598f4436
元コミット内容
encoding/gob
パッケージにおいて、破損したデータが入力された際に発生する2つのクラッシュを修正します。Issue #6323を解決します。
変更の背景
encoding/gob
はGoプログラム間で構造化されたデータを効率的にシリアライズ・デシリアライズするためのパッケージです。しかし、ネットワーク経由でのデータ転送やファイルからの読み込みなど、外部からの入力は常に信頼できるとは限りません。悪意のある、あるいは単に破損したデータが gob
デコーダに渡された場合、デコーダが予期せぬ状態に陥り、プログラムがクラッシュ(パニック)する可能性があります。
このコミットは、そのような状況下でのデコーダの脆弱性に対処するために導入されました。特に、型情報の長さが不正であったり、構造体のフィールド情報が欠落している場合に発生するパニックを特定し、それらを安全に処理するように修正することが目的です。コミットメッセージにある "Fixes #6323" は、この問題が特定のバグ報告(Issue 6323)に関連していることを示唆しています。このIssueは、おそらく不正な gob
ストリームがデコードされた際に発生するパニックを報告していたものと推測されます。
前提知識の解説
encoding/gob
パッケージ
encoding/gob
はGo言語に特化したバイナリシリアライゼーションフォーマットです。主な特徴は以下の通りです。
- Go言語間での利用: 主にGoプログラム間でのデータ交換(RPCなど)や、Goプログラム内での永続化に適しています。他の言語との相互運用性はありません。
- 自己記述型:
gob
ストリームは自己記述型であり、各データ項目の前にその型情報が含まれます。これにより、デコーダは事前に型を知らなくてもデータをデコードできます。 - 効率性: バイナリ形式であるため、JSONやXMLのようなテキストベースのフォーマットと比較して、データサイズが小さく、エンコード・デコードが高速です。特に、同じ
Encoder
を使って複数の値をストリームとして送信する場合、型情報の送信コストが償却されるため効率的です。 - リフレクションの利用: Goのリフレクション機能を利用して、任意のGoデータ構造をシリアライズ・デシリアライズします。
- 型情報の伝達: シリアライズ時に型情報も一緒に送信されるため、エンコーダとデコーダ間で型定義が多少異なっていても(フィールド名が一致していれば)デコードが可能です。
パニックとリカバリ (Panic and Recover)
Go言語における「パニック」は、プログラムの実行を停止させるランタイムエラーの一種です。これは通常、回復不可能なエラー(例: nilポインタ参照、配列の範囲外アクセス)が発生した場合に引き起こされます。パニックが発生すると、現在のゴルーチンは実行を停止し、遅延関数(defer
)が実行され、コールスタックを遡っていきます。
recover
は、パニックから回復するための組み込み関数です。defer
関数内で recover()
を呼び出すことで、パニックの発生を検知し、パニックによって停止したゴルーチンの実行を再開させることができます。これにより、プログラム全体がクラッシュするのを防ぎ、エラーを適切に処理する機会を得られます。このコミットのテストコードでは、recover
を使用して、不正な入力に対するデコード処理がパニックを引き起こさないことを検証しています。
データ破損とセキュリティ
シリアライゼーション/デシリアライゼーションの文脈では、データ破損はセキュリティ上の脆弱性につながる可能性があります。不正なデータがデコーダに渡された場合、デコーダがメモリを不正に読み書きしたり、無限ループに陥ったり、クラッシュしたりすることがあります。これはサービス拒否(DoS)攻撃や、場合によっては任意のコード実行につながる可能性もあります。そのため、デコーダは不正な入力に対して堅牢である必要があります。
技術的詳細
このコミットは、encoding/gob
デコーダが不正な入力データに遭遇した際に発生する2つの特定のクラッシュを修正します。
-
不正な型名長さによるクラッシュ:
gob
ストリームでは、カスタム型が初めて出現する際に、その型の名前が送信されます。この名前の長さは、データストリーム内にエンコードされています。もしこの長さが不正な値(例えば、非常に大きな値や負の値)であった場合、デコーダは不正なメモリ領域を読み込もうとしたり、make([]byte, nr)
のような操作で巨大なスライスを確保しようとしてメモリ不足に陥ったり、あるいは単に範囲外アクセスでパニックを引き起こす可能性がありました。 修正前は、decodeInterface
関数内で型名の長さを表すnr
がuint64(state.b.Len())
(入力バッファの残りサイズ) を超える場合に、state.b.Read(b)
がパニックを引き起こす可能性がありました。これは、make([]byte, nr)
で確保されたスライスb
のサイズが、実際に読み込めるデータ量よりもはるかに大きくなるためです。 -
構造体デコーダのコンパイル失敗によるクラッシュ:
gob
デコーダは、効率的なデコードのために、受信する型に基づいて内部的に「デコードエンジン」をコンパイルします。このエンジンは、特定の型をデコードするための命令のシーケンスです。構造体をデコードする際、デコーダは受信したgob
ストリーム内の構造体のフィールド情報と、Goプログラム側の構造体のフィールドを比較し、マッピングを行います。 修正前は、decodeValue
関数内で、受信したgob
ストリームの型情報(dec.wireType[wireId]
)がnil
であるにもかかわらず、デコーダがそのフィールドにアクセスしようとするとパニックが発生する可能性がありました。これは、dec.wireType[wireId].StructT.Field
にアクセスする前にdec.wireType[wireId]
が有効であるかどうかのチェックが不足していたためです。このような状況は、特に不正なgob
ストリームが与えられた場合に発生しやすくなります。
これらの問題は、デコーダが入力データの整合性を十分に検証せずに処理を進めてしまうことに起因していました。
コアとなるコードの変更箇所
src/pkg/encoding/gob/codec_test.go
newDT()
関数が追加され、テスト用のDT
構造体のインスタンスを生成するロジックが分離されました。これにより、テストコードの重複が減り、可読性が向上しています。TestFuzzOneByte
という新しいテスト関数が追加されました。このテストは、エンコードされたgob
データストリームの各バイトをランダムに(または特定のパターンで)変更し、その破損したデータをデコードしようとすることで、デコーダがパニックを起こさないことを検証します。defer
とrecover
を使用して、デコード中にパニックが発生した場合にそれを捕捉し、テストを失敗させることなくエラーを報告します。このテストは、デコーダの堅牢性を確認するための重要な追加です。
src/pkg/encoding/gob/decode.go
-
decodeInterface
関数に以下のチェックが追加されました。if nr < 0 || nr > 1<<31 { // zero is permissible for anonymous types errorf("invalid type name length %d", nr) } if nr > uint64(state.b.Len()) { errorf("invalid type name length %d: exceeds input size", nr) }
ここで
nr
は型名の長さを表します。最初のチェックはnr
が負の値でないこと、および非常に大きな値でないことを確認します。2番目のチェックは、nr
が入力バッファに残っているバイト数state.b.Len()
を超えていないことを確認します。これにより、デコーダが実際に存在しない量のデータを読み込もうとしてパニックを起こすのを防ぎます。 -
decodeValue
関数内の条件式が変更されました。if engine.numInstr == 0 && st.NumField() > 0 && dec.wireType[wireId] != nil && len(dec.wireType[wireId].StructT.Field) > 0 { name := base.Name() errorf("type mismatch: no fields matched compiling decoder for %s", name) }
dec.wireType[wireId] != nil
という条件が追加されました。これにより、dec.wireType[wireId]
がnil
である場合に、その後のlen(dec.wireType[wireId].StructT.Field)
へのアクセスがパニックを引き起こすのを防ぎます。
src/pkg/encoding/gob/encoder_test.go
TestBadData
関数に新しいテストケースが追加されました。
これは、特定の不正なバイトシーケンス// issue 6323. corruptDataCheck("\x04\x24foo", errRange, t)
"\x04\x24foo"
が与えられた場合に、errRange
エラー(範囲エラー)が返されることを期待するテストです。これはIssue 6323で報告された具体的なクラッシュケースの一つを再現し、その修正を検証するためのものです。
コアとなるコードの解説
decode.go
の変更点
-
decodeInterface
関数の修正: この関数は、gob
ストリームからインターフェース型をデコードする際に、そのインターフェースが保持する具体的な型の名前を読み込みます。型名の長さnr
は、ストリームから読み込まれる最初の情報の一つです。 追加されたif nr > uint64(state.b.Len())
のチェックは非常に重要です。これは、デコードしようとしている型名の長さが、現在デコード可能なバッファの残りのサイズを超えていないかを検証します。もしnr
がバッファの残りサイズよりも大きい場合、それは不正なデータであり、state.b.Read(b)
が要求されたバイト数を読み込めずにパニックを引き起こす可能性があります。このチェックにより、デコーダは不正な長さを検出した時点で安全にエラーを返し、クラッシュを防ぎます。 -
decodeValue
関数の修正: この関数は、gob
ストリームから具体的な値をデコードする際に、その値の型情報(wireId
で識別される)に基づいてデコードエンジンを適用します。dec.wireType[wireId] != nil
の追加は、デコードしようとしているwireId
に対応する型情報がdec.wireType
マップに存在するかどうかを確認します。もし存在しない(nil
である)場合、それは不正なgob
ストリームを示しており、その後のdec.wireType[wireId].StructT.Field
へのアクセスはnil
ポインタ参照によるパニックを引き起こします。このチェックにより、デコーダはnil
アクセスを未然に防ぎ、エラーを適切に処理できるようになります。
これらの変更は、デコーダが入力データの整合性をより厳密にチェックし、不正なデータパターンに対して早期にエラーを返すことで、パニックを回避し、堅牢性を高めることを目的としています。
codec_test.go
の TestFuzzOneByte
この新しいテストは、ファジング(fuzzing)テストの一種です。ファジングとは、プログラムにランダムまたは半ランダムなデータを大量に与え、予期せぬ動作(クラッシュ、メモリリークなど)を検出するテスト手法です。
TestFuzzOneByte
は、まず正常な gob
データをエンコードし、そのバイナリ表現を取得します。次に、そのバイナリデータの各バイトを1つずつ変更(フリップ)し、その破損したデータを gob
デコーダに渡してデコードを試みます。
func() {
defer func() {
if p := recover(); p != nil {
t.Errorf("crash for b[%d] ^= 0x%x", i, j)
panic(p) // 再パニックさせてテストを失敗させる
}
}()
err := NewDecoder(bytes.NewReader(b)).Decode(&e)
_ = err
}()
この defer
と recover
を使ったブロックは、デコード処理中にパニックが発生した場合にそれを捕捉します。パニックが捕捉された場合、t.Errorf
でエラーメッセージを記録し、その後 panic(p)
で再度パニックを発生させます。これにより、テストフレームワークはパニックを検出し、テストを失敗としてマークすることができます。このテストの目的は、デコーダがどのような不正な入力に対してもパニックを起こさず、エラーを返すか、あるいは安全に処理を終了することを確認することです。
TestFuzzOneByte
は、特に「1バイトの変更」という限定的なファジングを行いますが、これにより、デコーダが入力データのわずかな破損に対しても堅牢であることを保証します。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/7a73f32725ff8b13a4cca703972fa76e598f4436
- Gerrit Code Review: https://golang.org/cl/56870043
参考にした情報源リンク
- Go言語
encoding/gob
パッケージ公式ドキュメント: https://pkg.go.dev/encoding/gob - Go言語におけるパニックとリカバリに関するドキュメント(Go公式ブログなど)
- Go言語のテストに関するドキュメント(ファジングテストの概念など)
encoding/gob
の内部実装に関する情報(Goのソースコードや関連する設計ドキュメント)- Web search results for "Go encoding/gob" (provided by the tool)