[インデックス 15045] ファイルの概要
このコミットは、Go言語の標準ライブラリ crypto/cipher
パッケージにおける CBC
(Cipher Block Chaining) モードの実装において、CryptBlocks
メソッドが不正な入力(ブロックサイズの倍数でない入力や、出力バッファが入力バッファよりも小さい場合)を受け取った際に発生しうる「out of bounds error」(範囲外アクセスエラー)を回避するための修正です。具体的には、入力と出力のバッファサイズを検証し、不正な場合は panic
を発生させるように変更されています。また、この変更を検証するためのテストケースも追加されています。
コミット
- コミットハッシュ:
fa2acad60379f59712cc15ea1c4192b982e69a35
- Author: Russ Cox rsc@golang.org
- Date: Wed Jan 30 12:45:13 2013 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fa2acad60379f59712cc15ea1c4192b982e69a35
元コミット内容
crypto/cipher: avoid out of bounds error in CryptBlocks
Fixes #4699.
R=golang-dev, agl
CC=golang-dev
https://golang.org/cl/7231065
変更の背景
このコミットは、Go言語のIssue #4699 で報告されたバグを修正するために行われました。Issue #4699 は、crypto/cipher
パッケージの BlockMode
インターフェースを実装する CryptBlocks
メソッドにおいて、入力データがブロックサイズの倍数でない場合や、出力バッファが入力バッファよりも小さい場合に、内部で配列の範囲外アクセスが発生する可能性があるという問題点を指摘していました。
暗号化処理において、ブロック暗号のモード(CBCなど)は通常、固定長のブロック単位でデータを処理します。そのため、入力データはブロックサイズの厳密な倍数である必要があり、また出力バッファは入力データを格納するのに十分なサイズがなければなりません。これらの前提が満たされない場合、予期せぬ動作やセキュリティ上の脆弱性につながる可能性があります。
このコミットでは、これらの不正な入力条件を早期に検出し、panic
を発生させることで、プログラムのクラッシュやデータ破損を防ぎ、より堅牢な暗号化処理を提供することを目的としています。
前提知識の解説
crypto/cipher
パッケージ
Go言語の crypto/cipher
パッケージは、ブロック暗号の操作モード(CBC, CTR, GCMなど)を実装するための共通インターフェースとユーティリティを提供します。このパッケージ自体は具体的なブロック暗号アルゴリズム(AESなど)を実装しているわけではなく、それらのアルゴリズムと組み合わせて使用されます。
BlockMode
インターフェース
crypto/cipher
パッケージの中心的なインターフェースの一つに BlockMode
があります。これは、ブロック暗号の操作モードを抽象化するためのインターフェースで、以下のメソッドを持ちます。
type BlockMode interface {
BlockSize() int
CryptBlocks(dst, src []byte)
}
BlockSize()
: このモードが処理するブロックのサイズ(バイト単位)を返します。CryptBlocks(dst, src []byte)
:src
からデータを読み込み、暗号化または復号化してdst
に書き込みます。このメソッドは、src
とdst
の長さがブロックサイズの倍数であり、かつlen(dst) >= len(src)
であることを前提としています。
CBC (Cipher Block Chaining) モード
CBCは、ブロック暗号の代表的な操作モードの一つです。各ブロックの暗号化は、前のブロックの暗号文と現在の平文ブロックをXOR演算で結合してから行われます。これにより、同じ平文ブロックが複数回出現しても、異なる暗号文ブロックが生成されるため、パターン分析に対する耐性が向上します。CBCモードでは、最初のブロックを暗号化するために初期化ベクトル(IV: Initialization Vector)が必要です。
panic
と Go言語のエラーハンドリング
Go言語では、通常のエラーは戻り値として error
型を返すことで処理されます。しかし、panic
は回復不可能なエラーや、プログラムの実行を継続できないような深刻な状況で発生させられます。panic
が発生すると、現在のゴルーチンは実行を停止し、遅延関数(defer
)が実行され、最終的にプログラム全体がクラッシュします。このコミットでは、CryptBlocks
メソッドがその前提条件(入力/出力バッファのサイズ)を満たさない場合に panic
を発生させることで、不正な状態での処理続行を防ぎ、開発者に問題の存在を明確に通知しています。これは、ライブラリの契約違反に対する適切な対応と見なされます。
Out of Bounds Error (範囲外アクセスエラー)
配列やスライスなどのデータ構造にアクセスする際に、その範囲外のインデックスを指定すると発生するエラーです。Go言語では、実行時にこのようなアクセスが検出されると panic
が発生します。暗号化処理において、入力バッファや出力バッファのサイズが適切でない場合、内部でブロック単位の処理を行う際に範囲外アクセスが発生し、プログラムがクラッシュしたり、メモリが破損したりする可能性があります。
技術的詳細
このコミットの主要な変更点は、crypto/cipher/cbc.go
ファイル内の cbcEncrypter
と cbcDecrypter
の CryptBlocks
メソッドに、入力および出力バッファのサイズに関するチェックを追加したことです。
具体的には、以下の2つの条件が追加されました。
-
入力バッファの長さがブロックサイズの倍数であることの確認:
len(src)%x.blockSize != 0
CBCモードを含む多くのブロック暗号モードでは、データは固定長のブロック単位で処理されます。したがって、入力データはブロックサイズの厳密な倍数である必要があります。そうでない場合、最後のブロックが不完全になり、暗号化/復号化処理が正しく行えません。 -
出力バッファの長さが入力バッファ以上であることの確認:
len(dst) < len(src)
CryptBlocks
メソッドはsrc
の内容をdst
に暗号化/復号化して書き込むため、dst
はsrc
の内容をすべて格納できるだけの十分なサイズが必要です。dst
がsrc
よりも小さい場合、書き込み時に範囲外アクセスが発生する可能性があります。
これらの条件のいずれかが満たされない場合、panic
が発生し、それぞれ以下のメッセージが出力されます。
"crypto/cipher: input not full blocks"
"crypto/cipher: output smaller than input"
これらのチェックは、CryptBlocks
メソッドの冒頭に追加されており、実際の暗号化/復号化ループが始まる前に不正な状態を捕捉します。これにより、不正な入力によって引き起こされる潜在的なランタイムエラー(特に範囲外アクセス)を未然に防ぎ、ライブラリの堅牢性を向上させています。
また、src/pkg/crypto/cipher/cipher_test.go
という新しいテストファイルが追加され、これらの panic
条件が正しく機能するかどうかを検証しています。TestCryptBlocks
関数内で、意図的に不正なサイズの入力バッファや出力バッファを CryptBlocks
に渡し、期待通りに panic
が発生するかどうかを mustPanic
ヘルパー関数を使って確認しています。
コアとなるコードの変更箇所
diff --git a/src/pkg/crypto/cipher/cbc.go b/src/pkg/crypto/cipher/cbc.go
index 6fab9b4213..913a5643f2 100644
--- a/src/pkg/crypto/cipher/cbc.go
+++ b/src/pkg/crypto/cipher/cbc.go
@@ -42,6 +42,12 @@ func NewCBCEncrypter(b Block, iv []byte) BlockMode {
func (x *cbcEncrypter) BlockSize() int { return x.blockSize }
func (x *cbcEncrypter) CryptBlocks(dst, src []byte) {
+ if len(src)%x.blockSize != 0 {
+ panic("crypto/cipher: input not full blocks")
+ }
+ if len(dst) < len(src) {
+ panic("crypto/cipher: output smaller than input")
+ }
for len(src) > 0 {
for i := 0; i < x.blockSize; i++ {
x.iv[i] ^= src[i]
@@ -70,6 +76,12 @@ func NewCBCDecrypter(b Block, iv []byte) BlockMode {
func (x *cbcDecrypter) BlockSize() int { return x.blockSize }
func (x *cbcDecrypter) CryptBlocks(dst, src []byte) {
+ if len(src)%x.blockSize != 0 {
+ panic("crypto/cipher: input not full blocks")
+ }
+ if len(dst) < len(src) {
+ panic("crypto/cipher: output smaller than input")
+ }
for len(src) > 0 {
x.b.Decrypt(x.tmp, src[:x.blockSize])
for i := 0; i < x.blockSize; i++ {
diff --git a/src/pkg/crypto/cipher/cipher_test.go b/src/pkg/crypto/cipher/cipher_test.go
new file mode 100644
index 0000000000..8da5bce93f
--- /dev/null
+++ b/src/pkg/crypto/cipher/cipher_test.go
@@ -0,0 +1,36 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cipher_test
+
+import (
+ "crypto/aes"
+ "crypto/cipher"
+ "testing"
+)
+
+func TestCryptBlocks(t *testing.T) {
+ buf := make([]byte, 16)
+ block, _ := aes.NewCipher(buf)
+
+ mode := cipher.NewCBCDecrypter(block, buf)
+ mustPanic(t, "crypto/cipher: input not full blocks", func() { mode.CryptBlocks(buf, buf[:3]) })
+ mustPanic(t, "crypto/cipher: output smaller than input", func() { mode.CryptBlocks(buf[:3], buf) })
+
+ mode = cipher.NewCBCEncrypter(block, buf)
+ mustPanic(t, "crypto/cipher: input not full blocks", func() { mode.CryptBlocks(buf, buf[:3]) })
+ mustPanic(t, "crypto/cipher: output smaller than input", func() { mode.CryptBlocks(buf[:3], buf) })
+}
+
+func mustPanic(t *testing.T, msg string, f func()) {
+ defer func() {
+ err := recover()
+ if err == nil {
+ t.Errorf("function did not panic, wanted %q", msg)
+ } else if err != msg {
+ t.Errorf("got panic %v, wanted %q", err, msg)
+ }
+ }()
+ f()
+}
コアとなるコードの解説
src/pkg/crypto/cipher/cbc.go
の変更
cbcEncrypter
と cbcDecrypter
の両方の CryptBlocks
メソッドの先頭に、以下のガード節が追加されました。
if len(src)%x.blockSize != 0 {
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
len(src)%x.blockSize != 0
: これは、入力スライスsrc
の長さが、現在のブロックモードのブロックサイズx.blockSize
の倍数でない場合に真となります。CBCモードでは、完全なブロック単位で処理が行われるため、入力が不完全なブロックを含むことは許容されません。この条件が満たされない場合、"crypto/cipher: input not full blocks"
というメッセージと共にpanic
が発生します。len(dst) < len(src)
: これは、出力スライスdst
の長さが入力スライスsrc
の長さよりも小さい場合に真となります。CryptBlocks
はsrc
の内容をdst
に書き込むため、dst
はsrc
と同等かそれ以上のサイズが必要です。この条件が満たされない場合、"crypto/cipher: output smaller than input"
というメッセージと共にpanic
が発生します。
これらのチェックにより、CryptBlocks
メソッドが不正な引数で呼び出された際に、内部で範囲外アクセスなどのランタイムエラーが発生するのを防ぎ、代わりに明確なエラーメッセージと共に panic
を発生させることで、開発者が問題を早期に特定できるようにしています。
src/pkg/crypto/cipher/cipher_test.go
の追加
この新しいテストファイルは、上記の panic
条件が正しく機能することを検証するために作成されました。
-
TestCryptBlocks
関数:aes.NewCipher
を使用してAESブロック暗号のインスタンスを作成します。cipher.NewCBCDecrypter
とcipher.NewCBCEncrypter
を使用して、CBCデクリプタとエンクリプタのインスタンスを作成します。mustPanic
ヘルパー関数を呼び出し、意図的に不正な引数(例:buf[:3]
のようにブロックサイズに満たない入力や、出力バッファが入力より小さい場合)をCryptBlocks
に渡し、期待されるpanic
メッセージが返されることを確認します。
-
mustPanic
ヘルパー関数:defer
とrecover()
を使用して、引数として渡された関数f()
がpanic
を発生させるかどうか、そしてそのpanic
メッセージが期待されるmsg
と一致するかどうかを検証します。panic
が発生しなかった場合、またはpanic
メッセージが期待と異なる場合にt.Errorf
を呼び出してテストを失敗させます。
このテストの追加により、CryptBlocks
メソッドの堅牢性が保証され、将来の変更によってこれらの重要な入力検証が誤って削除されたり、機能しなくなったりするのを防ぐことができます。
関連リンク
- Go Issue #4699: https://code.google.com/p/go/issues/detail?id=4699 (古いGoogle Codeのリンクですが、当時のIssueトラッカーです)
- Go Change List (CL) 7231065: https://golang.org/cl/7231065 (Goのコードレビューシステムにおける変更セットのリンク)
参考にした情報源リンク
- 上記のGo Issue #4699 および Go Change List (CL) 7231065 の内容
- Go言語の
crypto/cipher
パッケージのドキュメント (GoDoc) - ブロック暗号の操作モードに関する一般的な情報 (CBCモードなど)
- Go言語の
panic
とrecover
に関するドキュメント - Go言語のテストに関するドキュメント