[インデックス 15704] ファイルの概要
このコミットは、Go言語の標準ライブラリ encoding/base32
および encoding/base64
パッケージにおけるデコード処理の堅牢性を向上させるものです。特に、不正なパディングを持つ入力文字列が与えられた際に発生しうるパニック(プログラムの異常終了)を防ぎ、より適切なエラーハンドリングを行うように修正されています。
コミット
commit 79d06d7286eb5ec3089f19dbf8542803007cd4c7
Author: Nigel Tao <nigeltao@golang.org>
Date: Tue Mar 12 11:07:36 2013 +1100
encoding/base32: don't panic when decoding "AAAA==".
Edit encoding/base64's internals and tests to match encoding/base32.
Properly handling line breaks in padding is left for another CL.
R=dsymonds
CC=golang-dev
https://golang.org/cl/7693044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/79d06d7286eb5ec3089f19dbf8542803007cd4c7
元コミット内容
encoding/base32: "AAAA==" をデコードする際にパニックを起こさないようにする。
encoding/base32 の内部実装とテストを encoding/base64 に合わせて修正する。
パディング内の改行の適切な処理は、別の変更リスト(CL)に持ち越される。
変更の背景
Base32およびBase64エンコーディングは、バイナリデータをASCII文字列に変換するための一般的な手法です。これらのエンコーディングでは、元のバイナリデータの長さがエンコーディングのブロックサイズ(Base32では5バイト、Base64では3バイト)の倍数でない場合、出力文字列の末尾にパディング文字(=
)が追加されます。
このコミットが行われる前は、encoding/base32
パッケージのデコード処理において、不正なパディング(例えば、Base32エンコーディングのルールに反する数の =
が付与された文字列)が与えられた場合に、プログラムがパニックを起こす可能性がありました。具体的には、コミットメッセージに記載されている "AAAA=="
のような入力がその一例です。これは、デコーダが予期しない入力形式に遭遇した際に、適切なエラーハンドリングが行われず、プログラムがクラッシュしてしまうことを意味します。
この問題は、デコード処理の堅牢性に関わる重要なバグであり、不正な入力に対してもパニックではなく、明確なエラー(CorruptInputError
)を返すように修正する必要がありました。また、Base32とBase64は類似のエンコーディングスキームであるため、Base32で発見されたこの問題の修正をBase64にも適用し、両パッケージの一貫性と信頼性を高めることが目的とされました。
前提知識の解説
Base32エンコーディング
Base32は、バイナリデータを32種類のASCII文字(A-Zと2-7)で表現するエンコーディング方式です。主に、人間が読みやすく、かつURLやファイル名に安全に含めることができる形式として利用されます。
- エンコーディングの仕組み: 5ビットのデータを1文字で表現します。元のバイナリデータが5バイト(40ビット)の場合、8文字のBase32文字列にエンコードされます。
- パディング: 元のバイナリデータのビット数が40ビットの倍数でない場合、出力の末尾にパディング文字
=
が追加されます。RFC 4648では、Base32のパディングの長さは0, 1, 3, 4, 6のいずれかであると規定されています。例えば、1バイト(8ビット)のデータは2文字のBase32と6つの=
でパディングされます(例:AE======
)。
Base64エンコーディング
Base64は、バイナリデータを64種類のASCII文字(A-Z, a-z, 0-9, +, /)で表現するエンコーディング方式です。電子メールの添付ファイルやHTTPのBasic認証など、バイナリデータをテキスト形式で安全に転送する際によく用いられます。
- エンコーディングの仕組み: 6ビットのデータを1文字で表現します。元のバイナリデータが3バイト(24ビット)の場合、4文字のBase64文字列にエンコードされます。
- パディング: 元のバイナリデータのビット数が24ビットの倍数でない場合、出力の末尾にパディング文字
=
が追加されます。Base64のパディングの長さは0, 1, 2のいずれかです。
パニックとエラーハンドリング
Go言語において、パニック (panic) はプログラムの回復不可能なエラーを示すメカニズムです。パニックが発生すると、通常のプログラムフローは中断され、スタックトレースが出力されてプログラムが終了します。これは、予期せぬ状態やプログラマの想定外の事態が発生した場合に用いられます。
一方、エラーハンドリング (error handling) は、予期されるが正常な処理ではない状況(例: ファイルが見つからない、ネットワーク接続が切れた、不正な入力)を適切に処理するための仕組みです。Goでは、関数がエラーを返すことで、呼び出し元がそのエラーを検知し、適切な回復処理を行うことができます。
このコミットの目的は、不正な入力(特にパディング)に対してパニックではなく、CorruptInputError
という特定のエラーを返すようにすることで、ライブラリの利用者がエラーを捕捉し、適切に処理できるようにすることです。
RFC 4648
RFC 4648は、Base16, Base32, Base64エンコーディングの標準を定義したIETFの文書です。このRFCには、各エンコーディング方式のアルファベット、エンコード/デコードのアルゴリズム、そしてパディングのルールが詳細に記述されています。このコミットでは、特にBase32のパディングルール(セクション6および9)に厳密に従うようにデコードロジックが修正されています。
技術的詳細
このコミットの主要な変更点は、encoding/base32
および encoding/base64
パッケージのデコード関数 (decode
メソッド) におけるパディング処理のロジック強化です。
Base32の変更点
src/pkg/encoding/base32/base32.go
の decode
関数において、パディング文字 (=
) の処理がより厳密になりました。
- パディングの長さチェックの強化:
- 以前は、パディング文字が見つかった際に、残りの入力がすべて
=
であることを確認するだけでした。 - 修正後は、
if len(src)+j < 8-1
という条件が追加され、パディング文字の前に十分なデータが存在するかどうかをチェックします。これは「パディングが足りない」状況を検出します。 - また、
for k := 0; k < 8-1-j; k++
ループ内でif len(src) > k && src[k] != '='
というチェックが追加され、パディング文字の後に不正な文字が含まれていないか(「不正なパディング」)を確認します。
- 以前は、パディング文字が見つかった際に、残りの入力がすべて
- 不正なパディング長の検出:
- 最も重要な変更点の一つは、
dlen == 1 || dlen == 3 || dlen == 6
という条件が追加されたことです。 - Base32エンコーディングの仕様(RFC 4648 Section 6)では、パディングによってデコードされるデータブロックの長さ (
dlen
) が1、3、6になることはありません。これらの値は、入力が不正であることを示します。 - この修正により、例えば
"A="
(dlen=1),"AAA="
(dlen=3),"AAAAAA="
(dlen=6) のような不正なパディングを持つ入力に対して、即座にCorruptInputError
を返すようになりました。これにより、デコード処理が途中でパニックを起こすことを防ぎます。
- 最も重要な変更点の一つは、
- デコード量子(quantum)処理の修正:
switch dlen
のケースが調整されました。例えば、以前はcase 7, 8:
だったものがcase 8:
に、case 6, 5:
がcase 7:
に変更されています。これは、RFC 4648で定義されているBase32のデコード量子(8文字のBase32入力が何バイトのバイナリデータに変換されるか)に厳密に合わせるための修正です。これにより、デコードされるバイト数が正確に計算されるようになります。
Base64の変更点
src/pkg/encoding/base64/base64.go
の decode
関数にも、Base32と同様のパディング処理の堅牢化が適用されました。
- パディングの長さチェックの強化:
if len(src)+j < 4-1
という条件が追加され、Base64のブロックサイズ(4文字)に基づいてパディングの不足を検出します。- Base64では、Base32のような特定の
dlen
値の不正チェックは明示的に追加されていませんが、これはBase64のパディングルールがBase32よりも単純であるためと考えられます。
テストの変更点
両パッケージのテストファイル (base32_test.go
, base64_test.go
) の TestDecodeCorrupt
関数が大幅に拡張されました。
corrupt
構造体がtestCases
スライスに変更され、input
文字列と期待されるoffset
(エラーが発生する位置)を持つようになりました。offset == -1
という新しい値が導入され、これは入力が有効であり、エラーが発生しないことを示すために使用されます。- これにより、様々な有効な入力と、意図的に不正なパディングを持つ入力(例:
"A="
,"AA="
,"AA=="
,"AAAA="
,"AAAA=="
など)が追加され、デコーダが正しくCorruptInputError
を返すか、またはエラーを返さないかを検証できるようになりました。これにより、修正が正しく機能していることが保証されます。
コアとなるコードの変更箇所
src/pkg/encoding/base32/base32.go
--- a/src/pkg/encoding/base32/base32.go
+++ b/src/pkg/encoding/base32/base32.go
@@ -236,7 +236,6 @@ func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
var dbuf [8]byte
dlen := 8
- // do the top bytes contain any data?
for j := 0; j < 8; {
if len(src) == 0 {
return n, false, CorruptInputError(len(osrc) - len(src) - j)
@@ -248,15 +247,26 @@ func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
continue
}
if in == '=' && j >= 2 && len(src) < 8 {
- // We've reached the end and there's
- // padding, the rest should be padded
- for k := 0; k < 8-j-1; k++ {
+ // We've reached the end and there's padding
+ if len(src)+j < 8-1 {
+ // not enough padding
+ return n, false, CorruptInputError(len(osrc))
+ }
+ for k := 0; k < 8-1-j; k++ {
if len(src) > k && src[k] != '=' {
+ // incorrect padding
return n, false, CorruptInputError(len(osrc) - len(src) + k - 1)
}
}
- dlen = j
- end = true
+ dlen, end = j, true
+ // 7, 5 and 2 are not valid padding lengths, and so 1, 3 and 6 are not
+ // valid dlen values. See RFC 4648 Section 6 "Base 32 Encoding" listing
+ // the five valid padding lengths, and Section 9 "Illustrations and
+ // Examples" for an illustration for how the the 1st, 3rd and 6th base32
+ // src bytes do not yield enough information to decode a dst byte.
+ if dlen == 1 || dlen == 3 || dlen == 6 {
+ return n, false, CorruptInputError(len(osrc) - len(src) - 1)
+ }
break
}
dbuf[j] = enc.decodeMap[in]
@@ -269,16 +279,16 @@ func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
// Pack 8x 5-bit source blocks into 5 byte destination
// quantum
switch dlen {
- case 7, 8:
+ case 8:
dst[4] = dbuf[6]<<5 | dbuf[7]
fallthrough
- case 6, 5:
+ case 7:
dst[3] = dbuf[4]<<7 | dbuf[5]<<2 | dbuf[6]>>3
fallthrough
- case 4:
+ case 5:
dst[2] = dbuf[3]<<4 | dbuf[4]>>1
fallthrough
- case 3:
+ case 4:
dst[1] = dbuf[1]<<6 | dbuf[2]<<1 | dbuf[3]>>4
fallthrough
case 2:
@@ -288,7 +298,7 @@ func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
switch dlen {
case 2:
n += 1
- case 3, 4:
+ case 4:
n += 2
case 5:
n += 3
- case 6, 7:
+ case 7:
n += 4
case 8:
n += 5
src/pkg/encoding/base64/base64.go
--- a/src/pkg/encoding/base64/base64.go
+++ b/src/pkg/encoding/base64/base64.go
@@ -227,9 +227,8 @@ func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
continue
}
if in == '=' && j >= 2 && len(src) < 4 {
- // We've reached the end and there's
- // padding
- if len(src) == 0 && j == 2 {
+ // We've reached the end and there's padding
+ if len(src)+j < 4-1 {
// not enough padding
return n, false, CorruptInputError(len(osrc))
}
@@ -237,8 +236,7 @@ func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
\t// incorrect padding
\treturn n, false, CorruptInputError(len(osrc) - len(src) - 1)
}
- dlen = j
- end = true
+ dlen, end = j, true
break
}
dbuf[j] = enc.decodeMap[in]
コアとなるコードの解説
上記のコード変更は、Base32およびBase64デコーダが不正なパディングを持つ入力に遭遇した際の挙動を改善するために行われました。
encoding/base32/base32.go
の変更点詳細
- パディングチェックの強化:
if in == '=' && j >= 2 && len(src) < 8
ブロックは、パディング文字 (=
) が検出された際の処理です。if len(src)+j < 8-1
は、現在の入力バッファsrc
の残りの長さと、既に処理した文字数j
を合計しても、Base32の1ブロック(8文字)を構成するのに必要な文字数(パディング文字を除く)に満たない場合に、パディングが不足していると判断し、CorruptInputError
を返します。for k := 0; k < 8-1-j; k++ { ... }
ループは、パディング文字の後に続く文字がすべて=
であることを確認します。もし=
以外の文字があれば、それは不正なパディングであり、CorruptInputError
を返します。
- 不正な
dlen
の検出:if dlen == 1 || dlen == 3 || dlen == 6
の行が追加されました。dlen
は、デコードされたバイナリデータの有効なバイト数を示します。RFC 4648のBase32仕様では、8文字のBase32入力からデコードされるバイナリデータのバイト数は、0, 2, 4, 5のいずれかになります。したがって、dlen
が1, 3, 6になることはありえず、これらは不正な入力であることを示します。このチェックにより、仕様に準拠しない入力に対して早期にエラーを返すことができます。
dlen
とend
の同時代入:dlen, end = j, true
は、Goの多値代入を利用して、dlen
とend
の両方を一度に設定しています。これは機能的な変更ではなく、コードの簡潔化です。
switch dlen
のケース修正:case 7, 8:
がcase 8:
に、case 6, 5:
がcase 7:
に、case 4:
がcase 5:
に、case 3:
がcase 4:
に、case 3, 4:
がcase 4:
に、case 6, 7:
がcase 7:
に変更されました。これらの変更は、Base32のデコードロジックがRFC 4648の仕様に厳密に準拠するように、デコードされるバイト数と入力文字数の関係を正確にマッピングするためのものです。これにより、例えば7文字のBase32入力が4バイトのバイナリデータにデコードされるといった、正しい量子変換が行われるようになります。
encoding/base64/base64.go
の変更点詳細
- パディングチェックの強化:
if in == '=' && j >= 2 && len(src) < 4
ブロックは、Base64のパディング文字 (=
) が検出された際の処理です。if len(src)+j < 4-1
は、Base64の1ブロック(4文字)を構成するのに必要な文字数に満たない場合に、パディングが不足していると判断し、CorruptInputError
を返します。dlen, end = j, true
は、Base32と同様に多値代入による簡潔化です。
これらの変更により、デコーダは不正なパディングを持つ入力に対してパニックを起こすことなく、CorruptInputError
を返すようになり、ライブラリの堅牢性と信頼性が向上しました。
関連リンク
- Go言語の
encoding/base32
パッケージ: https://pkg.go.dev/encoding/base32 - Go言語の
encoding/base64
パッケージ: https://pkg.go.dev/encoding/base64
参考にした情報源リンク
- RFC 4648 - The Base16, Base32, and Base64 Data Encodings: https://datatracker.ietf.org/doc/html/rfc4648
- Go言語のパニックと回復: https://go.dev/blog/defer-panic-and-recover (一般的なGoのエラーハンドリングとパニックに関する情報)
- Go言語の
CorruptInputError
の定義 (Goソースコード): https://cs.opensource.google/go/go/+/refs/tags/go1.22.4:src/encoding/base64/base64.go;l27 (Base64の例ですが、Base32も同様に定義されています)```markdown
[インデックス 15704] ファイルの概要
このコミットは、Go言語の標準ライブラリ encoding/base32
および encoding/base64
パッケージにおけるデコード処理の堅牢性を向上させるものです。特に、不正なパディングを持つ入力文字列が与えられた際に発生しうるパニック(プログラムの異常終了)を防ぎ、より適切なエラーハンドリングを行うように修正されています。
コミット
commit 79d06d7286eb5ec3089f19dbf8542803007cd4c7
Author: Nigel Tao <nigeltao@golang.org>
Date: Tue Mar 12 11:07:36 2013 +1100
encoding/base32: don't panic when decoding "AAAA==".
Edit encoding/base64's internals and tests to match encoding/base32.
Properly handling line breaks in padding is left for another CL.
R=dsymonds
CC=golang-dev
https://golang.org/cl/7693044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/79d06d7286eb5ec3089f19dbf8542803007cd4c7
元コミット内容
encoding/base32: "AAAA==" をデコードする際にパニックを起こさないようにする。
encoding/base32 の内部実装とテストを encoding/base64 に合わせて修正する。
パディング内の改行の適切な処理は、別の変更リスト(CL)に持ち越される。
変更の背景
Base32およびBase64エンコーディングは、バイナリデータをASCII文字列に変換するための一般的な手法です。これらのエンコーディングでは、元のバイナリデータの長さがエンコーディングのブロックサイズ(Base32では5バイト、Base64では3バイト)の倍数でない場合、出力文字列の末尾にパディング文字(=
)が追加されます。
このコミットが行われる前は、encoding/base32
パッケージのデコード処理において、不正なパディング(例えば、Base32エンコーディングのルールに反する数の =
が付与された文字列)が与えられた場合に、プログラムがパニックを起こす可能性がありました。具体的には、コミットメッセージに記載されている "AAAA=="
のような入力がその一例です。これは、デコーダが予期しない入力形式に遭遇した際に、適切なエラーハンドリングが行われず、プログラムがクラッシュしてしまうことを意味します。
この問題は、デコード処理の堅牢性に関わる重要なバグであり、不正な入力に対してもパニックではなく、明確なエラー(CorruptInputError
)を返すように修正する必要がありました。また、Base32とBase64は類似のエンコーディングスキームであるため、Base32で発見されたこの問題の修正をBase64にも適用し、両パッケージの一貫性と信頼性を高めることが目的とされました。
前提知識の解説
Base32エンコーディング
Base32は、バイナリデータを32種類のASCII文字(A-Zと2-7)で表現するエンコーディング方式です。主に、人間が読みやすく、かつURLやファイル名に安全に含めることができる形式として利用されます。
- エンコーディングの仕組み: 5ビットのデータを1文字で表現します。元のバイナリデータが5バイト(40ビット)の場合、8文字のBase32文字列にエンコードされます。
- パディング: 元のバイナリデータのビット数が40ビットの倍数でない場合、出力の末尾にパディング文字
=
が追加されます。RFC 4648では、Base32のパディングの長さは0, 1, 3, 4, 6のいずれかであると規定されています。例えば、1バイト(8ビット)のデータは2文字のBase32と6つの=
でパディングされます(例:AE======
)。
Base64エンコーディング
Base64は、バイナリデータを64種類のASCII文字(A-Z, a-z, 0-9, +, /)で表現するエンコーディング方式です。電子メールの添付ファイルやHTTPのBasic認証など、バイナリデータをテキスト形式で安全に転送する際によく用いられます。
- エンコーディングの仕組み: 6ビットのデータを1文字で表現します。元のバイナリデータが3バイト(24ビット)の場合、4文字のBase64文字列にエンコードされます。
- パディング: 元のバイナリデータのビット数が24ビットの倍数でない場合、出力の末尾にパディング文字
=
が追加されます。Base64のパディングの長さは0, 1, 2のいずれかです。
パニックとエラーハンドリング
Go言語において、パニック (panic) はプログラムの回復不可能なエラーを示すメカニズムです。パニックが発生すると、通常のプログラムフローは中断され、スタックトレースが出力されてプログラムが終了します。これは、予期せぬ状態やプログラマの想定外の事態が発生した場合に用いられます。
一方、エラーハンドリング (error handling) は、予期されるが正常な処理ではない状況(例: ファイルが見つからない、ネットワーク接続が切れた、不正な入力)を適切に処理するための仕組みです。Goでは、関数がエラーを返すことで、呼び出し元がそのエラーを検知し、適切な回復処理を行うことができます。
このコミットの目的は、不正な入力(特にパディング)に対してパニックではなく、CorruptInputError
という特定のエラーを返すようにすることで、ライブラリの利用者がエラーを捕捉し、適切に処理できるようにすることです。
RFC 4648
RFC 4648は、Base16, Base32, Base64エンコーディングの標準を定義したIETFの文書です。このRFCには、各エンコーディング方式のアルファベット、エンコード/デコードのアルゴリズム、そしてパディングのルールが詳細に記述されています。このコミットでは、特にBase32のパディングルール(セクション6および9)に厳密に従うようにデコードロジックが修正されています。
技術的詳細
このコミットの主要な変更点は、encoding/base32
および encoding/base64
パッケージのデコード関数 (decode
メソッド) におけるパディング処理のロジック強化です。
Base32の変更点
src/pkg/encoding/base32/base32.go
の decode
関数において、パディング文字 (=
) の処理がより厳密になりました。
- パディングの長さチェックの強化:
- 以前は、パディング文字が見つかった際に、残りの入力がすべて
=
であることを確認するだけでした。 - 修正後は、
if len(src)+j < 8-1
という条件が追加され、パディング文字の前に十分なデータが存在するかどうかをチェックします。これは「パディングが足りない」状況を検出します。 - また、
for k := 0; k < 8-1-j; k++
ループ内でif len(src) > k && src[k] != '='
というチェックが追加され、パディング文字の後に不正な文字が含まれていないか(「不正なパディング」)を確認します。
- 以前は、パディング文字が見つかった際に、残りの入力がすべて
- 不正なパディング長の検出:
- 最も重要な変更点の一つは、
dlen == 1 || dlen == 3 || dlen == 6
という条件が追加されたことです。 - Base32エンコーディングの仕様(RFC 4648 Section 6)では、パディングによってデコードされるデータブロックの長さ (
dlen
) が1、3、6になることはありません。これらの値は、入力が不正であることを示します。 - この修正により、例えば
"A="
(dlen=1),"AAA="
(dlen=3),"AAAAAA="
(dlen=6) のような不正なパディングを持つ入力に対して、即座にCorruptInputError
を返すようになりました。これにより、デコード処理が途中でパニックを起こすことを防ぎます。
- 最も重要な変更点の一つは、
- デコード量子(quantum)処理の修正:
switch dlen
のケースが調整されました。例えば、以前はcase 7, 8:
だったものがcase 8:
に、case 6, 5:
がcase 7:
に変更されています。これは、RFC 4648で定義されているBase32のデコード量子(8文字のBase32入力が何バイトのバイナリデータに変換されるか)に厳密に合わせるための修正です。これにより、デコードされるバイト数が正確に計算されるようになります。
Base64の変更点
src/pkg/encoding/base64/base64.go
の decode
関数にも、Base32と同様のパディング処理の堅牢化が適用されました。
- パディングの長さチェックの強化:
if len(src)+j < 4-1
という条件が追加され、Base64のブロックサイズ(4文字)に基づいてパディングの不足を検出します。- Base64では、Base32のような特定の
dlen
値の不正チェックは明示的に追加されていませんが、これはBase64のパディングルールがBase32よりも単純であるためと考えられます。
テストの変更点
両パッケージのテストファイル (base32_test.go
, base64_test.go
) の TestDecodeCorrupt
関数が大幅に拡張されました。
corrupt
構造体がtestCases
スライスに変更され、input
文字列と期待されるoffset
(エラーが発生する位置)を持つようになりました。offset == -1
という新しい値が導入され、これは入力が有効であり、エラーが発生しないことを示すために使用されます。- これにより、様々な有効な入力と、意図的に不正なパディングを持つ入力(例:
"A="
,"AA="
,"AA=="
,"AAAA="
,"AAAA=="
など)が追加され、デコーダが正しくCorruptInputError
を返すか、またはエラーを返さないかを検証できるようになりました。これにより、修正が正しく機能していることが保証されます。
コアとなるコードの変更箇所
src/pkg/encoding/base32/base32.go
--- a/src/pkg/encoding/base32/base32.go
+++ b/src/pkg/encoding/base32/base32.go
@@ -236,7 +236,6 @@ func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
var dbuf [8]byte
dlen := 8
- // do the top bytes contain any data?
for j := 0; j < 8; {
if len(src) == 0 {
return n, false, CorruptInputError(len(osrc) - len(src) - j)
@@ -248,15 +247,26 @@ func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
continue
}
if in == '=' && j >= 2 && len(src) < 8 {
- // We've reached the end and there's
- // padding, the rest should be padded
- for k := 0; k < 8-j-1; k++ {
+ // We've reached the end and there's padding
+ if len(src)+j < 8-1 {
+ // not enough padding
+ return n, false, CorruptInputError(len(osrc))
+ }
+ for k := 0; k < 8-1-j; k++ {
if len(src) > k && src[k] != '=' {
+ // incorrect padding
return n, false, CorruptInputError(len(osrc) - len(src) + k - 1)
}
}
- dlen = j
- end = true
+ dlen, end = j, true
+ // 7, 5 and 2 are not valid padding lengths, and so 1, 3 and 6 are not
+ // valid dlen values. See RFC 4648 Section 6 "Base 32 Encoding" listing
+ // the five valid padding lengths, and Section 9 "Illustrations and
+ // Examples" for an illustration for how the the 1st, 3rd and 6th base32
+ // src bytes do not yield enough information to decode a dst byte.
+ if dlen == 1 || dlen == 3 || dlen == 6 {
+ return n, false, CorruptInputError(len(osrc) - len(src) - 1)
+ }
break
}
dbuf[j] = enc.decodeMap[in]
@@ -269,16 +289,16 @@ func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
// Pack 8x 5-bit source blocks into 5 byte destination
// quantum
switch dlen {
- case 7, 8:
+ case 8:
dst[4] = dbuf[6]<<5 | dbuf[7]
fallthrough
- case 6, 5:
+ case 7:
dst[3] = dbuf[4]<<7 | dbuf[5]<<2 | dbuf[6]>>3
fallthrough
- case 4:
+ case 5:
dst[2] = dbuf[3]<<4 | dbuf[4]>>1
fallthrough
- case 3:
+ case 4:
dst[1] = dbuf[1]<<6 | dbuf[2]<<1 | dbuf[3]>>4
fallthrough
case 2:
@@ -288,7 +308,7 @@ func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
switch dlen {
case 2:
n += 1
- case 3, 4:
+ case 4:
n += 2
case 5:
n += 3
- case 6, 7:
+ case 7:
n += 4
case 8:
n += 5
src/pkg/encoding/base64/base64.go
--- a/src/pkg/encoding/base64/base64.go
+++ b/src/pkg/encoding/base64/base64.go
@@ -227,9 +227,8 @@ func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
continue
}
if in == '=' && j >= 2 && len(src) < 4 {
- // We've reached the end and there's
- // padding
- if len(src) == 0 && j == 2 {
+ // We've reached the end and there's padding
+ if len(src)+j < 4-1 {
// not enough padding
return n, false, CorruptInputError(len(osrc))
}
@@ -237,8 +236,7 @@ func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
\t// incorrect padding
\treturn n, false, CorruptInputError(len(osrc) - len(src) - 1)
}
- dlen = j
- end = true
+ dlen, end = j, true
break
}
dbuf[j] = enc.decodeMap[in]
コアとなるコードの解説
上記のコード変更は、Base32およびBase64デコーダが不正なパディングを持つ入力に遭遇した際の挙動を改善するために行われました。
encoding/base32/base32.go
の変更点詳細
- パディングチェックの強化:
if in == '=' && j >= 2 && len(src) < 8
ブロックは、パディング文字 (=
) が検出された際の処理です。if len(src)+j < 8-1
は、現在の入力バッファsrc
の残りの長さと、既に処理した文字数j
を合計しても、Base32の1ブロック(8文字)を構成するのに必要な文字数(パディング文字を除く)に満たない場合に、パディングが不足していると判断し、CorruptInputError
を返します。for k := 0; k < 8-1-j; k++ { ... }
ループは、パディング文字の後に続く文字がすべて=
であることを確認します。もし=
以外の文字があれば、それは不正なパディングであり、CorruptInputError
を返します。
- 不正な
dlen
の検出:if dlen == 1 || dlen == 3 || dlen == 6
の行が追加されました。dlen
は、デコードされたバイナリデータの有効なバイト数を示します。RFC 4648のBase32仕様では、8文字のBase32入力からデコードされるバイナリデータのバイト数は、0, 2, 4, 5のいずれかになります。したがって、dlen
が1, 3, 6になることはありえず、これらは不正な入力であることを示します。このチェックにより、仕様に準拠しない入力に対して早期にエラーを返すことができます。
dlen
とend
の同時代入:dlen, end = j, true
は、Goの多値代入を利用して、dlen
とend
の両方を一度に設定しています。これは機能的な変更ではなく、コードの簡潔化です。
switch dlen
のケース修正:case 7, 8:
がcase 8:
に、case 6, 5:
がcase 7:
に、case 4:
がcase 5:
に、case 3:
がcase 4:
に、case 3, 4:
がcase 4:
に、case 6, 7:
がcase 7:
に変更されました。これらの変更は、Base32のデコードロジックがRFC 4648の仕様に厳密に準拠するように、デコードされるバイト数と入力文字数の関係を正確にマッピングするためのものです。これにより、例えば7文字のBase32入力が4バイトのバイナリデータにデコードされるといった、正しい量子変換が行われるようになります。
encoding/base64/base64.go
の変更点詳細
- パディングチェックの強化:
if in == '=' && j >= 2 && len(src) < 4
ブロックは、Base64のパディング文字 (=
) が検出された際の処理です。if len(src)+j < 4-1
は、Base64の1ブロック(4文字)を構成するのに必要な文字数に満たない場合に、パディングが不足していると判断し、CorruptInputError
を返します。dlen, end = j, true
は、Base32と同様に多値代入による簡潔化です。
これらの変更により、デコーダは不正なパディングを持つ入力に対してパニックを起こすことなく、CorruptInputError
を返すようになり、ライブラリの堅牢性と信頼性が向上しました。
関連リンク
- Go言語の
encoding/base32
パッケージ: https://pkg.go.dev/encoding/base32 - Go言語の
encoding/base64
パッケージ: https://pkg.go.dev/encoding/base64
参考にした情報源リンク
- RFC 4648 - The Base16, Base32, and Base64 Data Encodings: https://datatracker.ietf.org/doc/html/rfc4648
- Go言語のパニックと回復: https://go.dev/blog/defer-panic-and-recover (一般的なGoのエラーハンドリングとパニックに関する情報)
- Go言語の
CorruptInputError
の定義 (Goソースコード): https://cs.opensource.google/go/go/+/refs/tags/go1.22.4:src/encoding/base64/base64.go;l27 (Base64の例ですが、Base32も同様に定義されています)