[インデックス 12820] ファイルの概要
このコミットは、Go言語の標準ライブラリencoding/base64
パッケージにおいて、Base64デコード時に入力データの長さが4の倍数でない場合に発生するパニック(panic)を修正するものです。具体的には、パディング文字(=
)の処理が不適切であったために発生していた問題を解決し、不正な入力に対する堅牢性を向上させています。また、この修正を検証するための新しいテストケースも追加されています。
コミット
commit 951a97e42ff6ce8a2656180d90eca112d795ea0b
Author: Dave Cheney <dave@cheney.net>
Date: Tue Apr 3 12:14:02 2012 +1000
encoding/base64: fix panic when input len is not a multiple of 4
Fixes #3442.
R=for.go.yong, dsymonds, sougou, minux.ma, rsc
CC=golang-dev
https://golang.org/cl/5975052
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/951a97e42ff6ce8a2656180d90eca112d795ea0b
元コミット内容
encoding/base64: fix panic when input len is not a multiple of 4
Fixes #3442.
変更の背景
このコミットの背景には、Go言語のencoding/base64
パッケージのデコーダが、特定の不正なBase64入力に対してパニックを引き起こすという問題がありました。具体的には、入力文字列の長さが4の倍数ではない場合、特にパディング文字(=
)の処理が不適切であると、デコーダが予期せぬ動作(パニック)を起こす可能性がありました。
Base64エンコーディングでは、元のバイナリデータが3バイトのブロックに分割され、それぞれが4文字のBase64文字に変換されます。元のデータの長さが3の倍数でない場合、エンコードされた出力はパディング文字=
を使用して4の倍数の長さに調整されます。例えば、1バイトのデータはXX==
、2バイトのデータはXXX=
のようにエンコードされます。
問題は、デコード時にこのパディングが正しく処理されない場合に発生しました。入力文字列の長さが4の倍数でない、かつパディングが期待される形式と異なる場合に、デコーダが配列の範囲外アクセスなどを行い、パニックに至ることが報告されていました(Issue #3442)。この修正は、このような不正な入力に対してもデコーダがパニックを起こすことなく、適切なエラー(CorruptInputError
)を返すようにするためのものです。
前提知識の解説
Base64エンコーディング
Base64は、バイナリデータをASCII文字列形式に変換するエンコーディング方式です。主に、バイナリデータをテキストベースのプロトコル(例: メール、HTTP)で安全に転送するために使用されます。
- 原理: 3バイト(24ビット)のバイナリデータを4つの6ビットグループに分割し、それぞれの6ビット値をBase64アルファベット(A-Z, a-z, 0-9, +, /)のいずれかの文字にマッピングします。
- パディング: 元のバイナリデータの長さが3の倍数でない場合、エンコードされた出力の長さが4の倍数になるように、末尾にパディング文字
=
が追加されます。- 元のデータが1バイト余る場合: 2つの
=
が追加されます(例:XX==
)。 - 元のデータが2バイト余る場合: 1つの
=
が追加されます(例:XXX=
)。
- 元のデータが1バイト余る場合: 2つの
- デコード: Base64文字列を元のバイナリデータに戻すプロセスです。デコーダは4文字のブロックを読み込み、それを3バイトのバイナリデータに変換します。パディング文字は、元のデータの長さを判断するために使用されます。
Go言語のencoding/base64
パッケージ
Go言語の標準ライブラリには、Base64エンコーディングとデコーディングを扱うencoding/base64
パッケージが含まれています。このパッケージは、標準のBase64(RFC 4648)とURL-safeなBase64をサポートしています。
パニック(Panic)とエラーハンドリング
Go言語では、プログラムの異常終了を示すために「パニック」というメカニズムがあります。これは、回復不可能なエラーやプログラマの論理的な誤りを示す場合に使用されます。通常、ライブラリ関数はパニックではなく、エラー値を返すことで問題を示します。このコミットで修正された問題は、本来エラーとして処理されるべき不正な入力が、デコーダの内部ロジックの欠陥によりパニックを引き起こしていたという点にあります。
CorruptInputError
は、encoding/base64
パッケージが返すエラー型の一つで、入力されたBase64文字列が不正な形式である場合に発生します。
技術的詳細
このコミットは、encoding/base64
パッケージ内のdecode
メソッド、特にパディング文字の処理ロジックに焦点を当てています。
Base64デコードの一般的なロジックでは、入力文字列を4文字のブロックとして処理します。パディング文字=
は、ブロックの末尾に現れ、元のデータが何バイトであったかを示します。
問題のコードは、decode
メソッド内でパディング文字=
を検出した際の条件分岐にありました。
元のコードでは、in == '=' && j >= 2 && len(src) < 4
という条件でパディングを検出していました。ここでj
は現在の4文字ブロック内で処理された文字数、len(src)
はまだ処理されていない残りの入力バイト数です。
パニックが発生するシナリオは、例えば入力がA=
のような場合です。
A
が処理され、j
が1になる。=
が検出される。in == '='
は真。j >= 2
は偽(j
は1)。 この場合、パディングとして認識されず、次の文字を期待して処理が続行される可能性があります。しかし、入力はすでに終了しているため、src
から読み取ろうとするとインデックスが範囲外になり、パニックが発生していました。
このコミットでは、以下の2つの条件を追加することで、この問題を解決しています。
-
if len(src) == 0 && j == 2
:- これは、例えば入力が
AA=
のような場合を想定しています。AA
が処理され、j
が2になった後、=
が検出されます。この時点でlen(src)
は0(残りの入力がない)です。 - この条件は、「パディング文字が検出されたが、残りの入力が全くなく、かつ現在のブロックで2文字しか処理されていない(つまり、
AA=
のような形式で、本来はAA==
であるべき)ため、パディングが不足している」という状況を捕捉します。 - このような場合、
CorruptInputError
を返すことで、パニックを回避し、不正な入力であることを明示します。
- これは、例えば入力が
-
if len(src) > 0 && src[0] != '='
:- これは、例えば入力が
AAAAAA=
のような場合を想定しています。AAAA
が処理され、次のブロックでAA
が処理され、=
が検出されます。この時点でlen(src)
は0ではない(まだ=
の後の文字がある)が、その次の文字が=
ではない場合です。 - この条件は、「パディング文字が検出されたが、その後に続く文字が
=
ではない(つまり、パディングが不正であるか、余分な文字がある)」という状況を捕捉します。 - このような場合も、
CorruptInputError
を返すことで、パニックを回避し、不正な入力であることを明示します。
- これは、例えば入力が
これらの変更により、デコーダは不正なパディングや不完全な入力に対しても、パニックを起こすことなく、適切なエラーを返すようになりました。
コアとなるコードの変更箇所
src/pkg/encoding/base64/base64.go
--- a/src/pkg/encoding/base64/base64.go
+++ b/src/pkg/encoding/base64/base64.go
@@ -230,7 +230,12 @@ func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
if in == '=' && j >= 2 && len(src) < 4 {
// We've reached the end and there's
// padding
+ if len(src) == 0 && j == 2 {
+ // not enough padding
+ return n, false, CorruptInputError(len(osrc))
+ }
if len(src) > 0 && src[0] != '=' {
+ // incorrect padding
return n, false, CorruptInputError(len(osrc) - len(src) - 1)
}
dlen = j
src/pkg/encoding/base64/base64_test.go
--- a/src/pkg/encoding/base64/base64_test.go
+++ b/src/pkg/encoding/base64/base64_test.go
@@ -151,6 +151,9 @@ func TestDecodeCorrupt(t *testing.T) {
{"AAA=AAAA", 3},
{"AAAAA", 4},
{"AAAAAA", 4},
+ {"A=", 1},
+ {"AA=", 3},
+ {"AAAAAA=", 7},
}
for _, e := range examples {
コアとなるコードの解説
src/pkg/encoding/base64/base64.go
の変更
decode
関数のパディング処理部分に、2つの新しい条件分岐が追加されました。
-
if len(src) == 0 && j == 2
:- この条件は、デコード中の4文字ブロックにおいて、既に2文字(
j == 2
)を処理し、次の文字がパディング文字=
であった場合を想定しています。 len(src) == 0
は、残りの入力がもうないことを意味します。- 例えば、入力が
AA=
の場合、A
、A
と処理され、j
が2になります。次に=
が来ますが、その後に続く文字はありません。Base64のルールでは、2バイトのデータはXXX=
ではなくXX==
のように2つのパディング文字が必要です。したがって、AA=
は不正な形式です。 - この条件が真の場合、「パディングが足りない」と判断し、
CorruptInputError
を返して処理を終了します。これにより、不正な入力に対するパニックを防ぎます。
- この条件は、デコード中の4文字ブロックにおいて、既に2文字(
-
if len(src) > 0 && src[0] != '='
:- この条件は、デコード中の4文字ブロックにおいて、パディング文字
=
が検出された後、まだ入力が残っている(len(src) > 0
)にもかかわらず、その次の文字が=
ではない場合を想定しています。 - Base64のパディングルールでは、
=
の後に続く文字は、もし存在すればそれも=
であるべきです(例:XX==
)。=
の後にBase64アルファベットの文字が続くことはありません。 - 例えば、入力が
AAAAAA=
の場合、最初のAAAA
は正常にデコードされます。次のブロックでAA
が処理され、=
が来ます。この時点でlen(src)
は0ではありません(まだ入力が残っている)。もし=
の次の文字が=
でなければ、それは不正なパディングと見なされます。 - この条件が真の場合、「パディングが不正である」と判断し、
CorruptInputError
を返して処理を終了します。これにより、不正な入力に対するパニックを防ぎます。
- この条件は、デコード中の4文字ブロックにおいて、パディング文字
これらの変更により、decode
関数はより堅牢になり、様々な形式の不正なBase64入力に対しても、パニックではなく適切なエラーを返すようになりました。
src/pkg/encoding/base64/base64_test.go
の変更
TestDecodeCorrupt
関数に、パニックを引き起こす可能性があった新しいテストケースが追加されました。
{"A=", 1}
: 1文字のデータに1つのパディング文字。これは不正な形式です。{"AA=", 3}
: 2文字のデータに1つのパディング文字。これも不正な形式です(本来はAA==
)。{"AAAAAA=", 7}
: 6文字のデータに1つのパディング文字。これも不正な形式です。
これらのテストケースは、変更前のコードではパニックを引き起こす可能性がありましたが、修正後のコードではCorruptInputError
を返すことを期待して追加されました。これにより、修正が正しく機能していることを検証できます。
関連リンク
- Go Issue #3442: https://code.google.com/p/go/issues/detail?id=3442 (古いGooglesourceのリンクですが、当時のIssueトラッカーのものです)
- Gerrit Change-ID:
https://golang.org/cl/5975052
(GoのコードレビューシステムGerritのリンク)
参考にした情報源リンク
- RFC 4648 - The Base16, Base32, and Base64 Data Encodings: https://datatracker.ietf.org/doc/html/rfc4648
- Go言語の
encoding/base64
パッケージのドキュメント: https://pkg.go.dev/encoding/base64 - Go言語のエラーハンドリングに関する公式ドキュメントやブログ記事 (一般的な知識として)