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

[インデックス 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 に書き込みます。このメソッドは、srcdst の長さがブロックサイズの倍数であり、かつ 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 ファイル内の cbcEncryptercbcDecrypterCryptBlocks メソッドに、入力および出力バッファのサイズに関するチェックを追加したことです。

具体的には、以下の2つの条件が追加されました。

  1. 入力バッファの長さがブロックサイズの倍数であることの確認: len(src)%x.blockSize != 0 CBCモードを含む多くのブロック暗号モードでは、データは固定長のブロック単位で処理されます。したがって、入力データはブロックサイズの厳密な倍数である必要があります。そうでない場合、最後のブロックが不完全になり、暗号化/復号化処理が正しく行えません。

  2. 出力バッファの長さが入力バッファ以上であることの確認: len(dst) < len(src) CryptBlocks メソッドは src の内容を dst に暗号化/復号化して書き込むため、dstsrc の内容をすべて格納できるだけの十分なサイズが必要です。dstsrc よりも小さい場合、書き込み時に範囲外アクセスが発生する可能性があります。

これらの条件のいずれかが満たされない場合、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 の変更

cbcEncryptercbcDecrypter の両方の 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 の長さよりも小さい場合に真となります。CryptBlockssrc の内容を dst に書き込むため、dstsrc と同等かそれ以上のサイズが必要です。この条件が満たされない場合、"crypto/cipher: output smaller than input" というメッセージと共に panic が発生します。

これらのチェックにより、CryptBlocks メソッドが不正な引数で呼び出された際に、内部で範囲外アクセスなどのランタイムエラーが発生するのを防ぎ、代わりに明確なエラーメッセージと共に panic を発生させることで、開発者が問題を早期に特定できるようにしています。

src/pkg/crypto/cipher/cipher_test.go の追加

この新しいテストファイルは、上記の panic 条件が正しく機能することを検証するために作成されました。

  • TestCryptBlocks 関数:

    • aes.NewCipher を使用してAESブロック暗号のインスタンスを作成します。
    • cipher.NewCBCDecryptercipher.NewCBCEncrypter を使用して、CBCデクリプタとエンクリプタのインスタンスを作成します。
    • mustPanic ヘルパー関数を呼び出し、意図的に不正な引数(例: buf[:3] のようにブロックサイズに満たない入力や、出力バッファが入力より小さい場合)を CryptBlocks に渡し、期待される panic メッセージが返されることを確認します。
  • mustPanic ヘルパー関数:

    • deferrecover() を使用して、引数として渡された関数 f()panic を発生させるかどうか、そしてその panic メッセージが期待される msg と一致するかどうかを検証します。
    • panic が発生しなかった場合、または panic メッセージが期待と異なる場合に t.Errorf を呼び出してテストを失敗させます。

このテストの追加により、CryptBlocks メソッドの堅牢性が保証され、将来の変更によってこれらの重要な入力検証が誤って削除されたり、機能しなくなったりするのを防ぐことができます。

関連リンク

参考にした情報源リンク

  • 上記のGo Issue #4699 および Go Change List (CL) 7231065 の内容
  • Go言語の crypto/cipher パッケージのドキュメント (GoDoc)
  • ブロック暗号の操作モードに関する一般的な情報 (CBCモードなど)
  • Go言語の panicrecover に関するドキュメント
  • Go言語のテストに関するドキュメント