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

[インデックス 12910] ファイルの概要

このコミットは、Go言語の標準ライブラリ encoding/asn1 パッケージにおける、ASN.1 (Abstract Syntax Notation One) の長さエンコーディングの処理に関する重要な改善を含んでいます。具体的には、ASN.1構造体の要素の長さを表現する際に、Goの int 型が扱える最大値まで対応できるように修正されました。これにより、以前の2^24バイトという制限が緩和され、より大きなデータ構造をASN.1形式で正確にデコードできるようになります。また、DER (Distinguished Encoding Rules) の厳格な要件に従い、長さフィールドにおける冗長な先行ゼロをエラーとして検出するようになりました。

コミット

  • Author: Adam Langley agl@golang.org
  • Date: Wed Apr 18 13:41:11 2012 -0400
  • Commit Hash: 6742d0a085021d9638a4d59dd8fb562aec8d51a8

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/6742d0a085021d9638a4d59dd8fb562aec8d51a8

元コミット内容

encoding/asn1: allow lengths up to the maximum int value.

Previously we capped lengths at 2**24 rather than 2**31.

R=golang-dev, r, r
CC=golang-dev
https://golang.org/cl/6050050

変更の背景

この変更の主な背景は、encoding/asn1 パッケージがASN.1データの長さをデコードする際に、不必要に厳しい制限を設けていた点にあります。以前の実装では、ASN.1の長さフィールドが2^24バイト(約16MB)を超える場合にエラーとして扱っていました。しかし、Goの int 型は通常32ビットシステムで2^31-1、64ビットシステムで2^63-1までの値を表現できます。この2^24という制限は、int 型の最大値よりもはるかに小さく、正当なASN.1データであってもデコードできないケースが発生する可能性がありました。

このコミットは、この不整合を解消し、encoding/asn1 がGoの int 型で表現可能な最大長まで適切に処理できるようにすることを目的としています。これにより、より大きなASN.1構造体や、特に証明書や暗号化関連のデータなど、大きなバイナリデータを扱う際に、より堅牢な処理が可能になります。

また、DER (Distinguished Encoding Rules) は、ASN.1データのエンコーディングに関する厳格なルールセットであり、特定のエンコーディングが唯一の正しい形式であることを保証します。DERのルールの一つに、長さフィールドは「最小限のバイト数」でエンコードされなければならないというものがあります。つまり、冗長な先行ゼロは許されません。以前の実装ではこのチェックが不十分であったため、このコミットではその点も修正し、DERの厳格な要件に準拠するようにしています。

前提知識の解説

ASN.1 (Abstract Syntax Notation One)

ASN.1は、データ構造を記述するための標準的な記法です。通信プロトコルやデータストレージにおいて、異なるシステム間でデータを交換する際に、そのデータの構造と型を明確に定義するために使用されます。ASN.1自体は抽象的な記法であり、具体的なバイト列への変換ルールはエンコーディングルールによって定義されます。

DER (Distinguished Encoding Rules)

DERは、ASN.1のエンコーディングルールの一つであり、特に厳格なルールセットを持つバイナリエンコーディングです。DERの主な特徴は、特定のASN.1値に対して常に一意のバイト列を生成することです。これは、デジタル署名やハッシュ計算など、データの同一性が保証される必要がある場面で非常に重要です。X.509証明書やPKCS#10証明書署名要求など、多くのセキュリティ関連の標準でDERが使用されています。

ASN.1の長さエンコーディング

ASN.1では、各データ要素の「タグ」と「長さ」と「値」の3つの部分で構成されます。長さフィールドは、その要素の「値」部分が何バイト続くかを示します。長さのエンコーディングには、主に以下の3つの形式があります。

  1. 短形式 (Short Form): 長さが0から127バイトの場合。1バイトで表現され、最上位ビットは0、残りの7ビットで長さを表します。
  2. 長形式 (Long Form): 長さが128バイト以上の場合。最初の1バイトの最上位ビットは1、残りの7ビットは後続の長さバイト数を示します。後続のバイト列が実際の長さをビッグエンディアンで表現します。例えば、最初のバイトが 0x82 であれば、続く2バイトが長さを表します。
  3. 不定長形式 (Indefinite Form): 長さが不明な場合。最初のバイトが 0x80 で、値の終わりに 0x00 0x00 の終端マーカーが置かれます。DERでは不定長形式は許可されていません。

このコミットで問題となっているのは、主に長形式の処理です。

Go言語の int

Go言語の int 型は、プラットフォームに依存する符号付き整数型です。32ビットシステムでは32ビット幅(-2,147,483,648 から 2,147,483,647)、64ビットシステムでは64ビット幅(-9,223,372,036,854,775,808 から 9,223,372,036,854,775,807)を持ちます。このコミットの文脈では、特に32ビットシステムにおける int の最大値(約2 * 10^9)が重要になります。2^31は、32ビット符号付き整数の最大値に非常に近い値です。

技術的詳細

このコミットの技術的詳細は、encoding/asn1 パッケージの parseTagAndLength 関数における長さのデコードロジックの変更に集約されます。

以前の制限 (2^24) の理由と問題点

元のコードでは、長形式のエンコーディングにおいて、長さを示すバイト数が3バイトを超える場合(つまり、長さが2^24を超える場合)にエラーとしていました。これは、numBytes > 3 という条件でチェックされていました。 numBytes は、長さを示すために後続するバイトの数です。

  • numBytes = 1 の場合、長さは1バイトで表現され、最大2^8 - 1 = 255。
  • numBytes = 2 の場合、長さは2バイトで表現され、最大2^16 - 1 = 65535。
  • numBytes = 3 の場合、長さは3バイトで表現され、最大2^24 - 1 = 16,777,215。
  • numBytes = 4 の場合、長さは4バイトで表現され、最大2^32 - 1。

したがって、numBytes > 3 の制限は、長さを2^24で上限設定していたことを意味します。これは、Goの int 型が32ビットシステムで2^31-1まで扱えるにもかかわらず、不必要に低い上限を設けていたことになります。この制限は、大きなASN.1構造体を扱う際に問題を引き起こす可能性がありました。

新しい長さの処理と int の最大値への対応

コミットでは、numBytes > 3 のチェックが削除されました。代わりに、長さの計算中に ret.length1<<23 (2^23) を超えるかどうかをチェックするようになりました。

if ret.length >= 1<<23 {
    // We can't shift ret.length up without
    // overflowing.
    err = StructuralError{"length too large"}
    return
}
ret.length <<= 8
ret.length |= int(b)

この変更の意図は、ret.length が次のバイトをシフトして追加する前に、int 型のオーバーフローを防ぐことです。int 型が32ビットの場合、最大値は約2^31です。ret.length <<= 8ret.length を8ビット左にシフトする操作です。もし ret.length が既に 2^23 (約800万) を超えている場合、さらに8ビット左にシフトすると 2^31 (約20億) を超え、32ビット符号付き int の範囲を逸脱する可能性があります。このチェックにより、int 型の最大値を超えるような非常に大きな長さが検出され、エラーとして処理されるようになります。これにより、Goの int 型が扱える最大値まで長さを許容しつつ、オーバーフローを適切に防ぐことができます。

冗長な先行ゼロの検出

DERの厳格なルールに従うため、長さフィールドに冗長な先行ゼロが含まれている場合にエラーを返すようになりました。これは、長形式のエンコーディングにおいて、長さが0でないにもかかわらず、その表現に不要なゼロが先頭に付加されているケースを指します。

if ret.length == 0 {
    // DER requires that lengths be minimal.
    err = StructuralError{"superfluous leading zeros in length"}
    return
}

このチェックは、長さがデコードされた後に ret.length が0であるにもかかわらず、それが長形式でエンコードされていた場合に発動します。例えば、長さが1バイトであるにもかかわらず、0x82 0x00 0x01 のように2バイトで 0x0001 と表現されている場合、これは冗長な先行ゼロとみなされ、DERの要件に違反します。この修正により、encoding/asn1 はよりDERに準拠したパーサーとなります。

コアとなるコードの変更箇所

src/pkg/encoding/asn1/asn1.go ファイルの parseTagAndLength 関数が変更されています。

--- a/src/pkg/encoding/asn1/asn1.go
+++ b/src/pkg/encoding/asn1/asn1.go
@@ -377,11 +377,6 @@ func parseTagAndLength(bytes []byte, initOffset int) (ret tagAndLength, offset i
 	} else {
 		// Bottom 7 bits give the number of length bytes to follow.
 		numBytes := int(b & 0x7f)
-		// We risk overflowing a signed 32-bit number if we accept more than 3 bytes.
-		if numBytes > 3 {
-			err = StructuralError{"length too large"}
-			return
-		}
 		if numBytes == 0 {
 			err = SyntaxError{"indefinite length found (not DER)"}
 			return
@@ -394,8 +389,19 @@ func parseTagAndLength(bytes []byte, initOffset int) (ret tagAndLength, offset i
 			}
 			b = bytes[offset]
 			offset++
+			if ret.length >= 1<<23 {
+				// We can't shift ret.length up without
+				// overflowing.
+				err = StructuralError{"length too large"}
+				return
+			}
 			ret.length <<= 8
 			ret.length |= int(b)
+			if ret.length == 0 {
+				// DER requires that lengths be minimal.
+				err = StructuralError{"superfluous leading zeros in length"}
+				return
+			}
 		}
 	}
 

また、src/pkg/encoding/asn1/asn1_test.go にもテストケースが追加されています。

--- a/src/pkg/encoding/asn1/asn1_test.go
+++ b/src/pkg/encoding/asn1/asn1_test.go
@@ -283,6 +283,12 @@ var tagAndLengthData = []tagAndLengthTest{\n 	{[]byte{0x00, 0x83, 0x01, 0x00}, false, tagAndLength{}},\n 	{[]byte{0x1f, 0x85}, false, tagAndLength{}},\n 	{[]byte{0x30, 0x80}, false, tagAndLength{}},\n+\t// Superfluous zeros in the length should be an error.\n+\t{[]byte{0xa0, 0x82, 0x00, 0x01}, false, tagAndLength{}},\n+\t// Lengths up to the maximum size of an int should work.\n+\t{[]byte{0xa0, 0x84, 0x7f, 0xff, 0xff, 0xff}, true, tagAndLength{2, 0, 0x7fffffff, true}},\n+\t// Lengths that would overflow an int should be rejected.\n+\t{[]byte{0xa0, 0x84, 0x80, 0x00, 0x00, 0x00}, false, tagAndLength{}},\n }\n 
 func TestParseTagAndLength(t *testing.T) {

コアとなるコードの解説

src/pkg/encoding/asn1/asn1.go の変更点

  1. numBytes > 3 の削除:

    • 以前のコードでは、長さを示すバイト数 numBytes が3を超える場合に StructuralError{"length too large"} を返していました。これは、長さを2^24で制限していたことに相当します。
    • この行が削除されたことで、長さの表現に4バイト以上を使用することが可能になり、Goの int 型が扱える最大値まで長さを許容するようになりました。
  2. if ret.length >= 1<<23 の追加:

    • これは、長さのデコード中に int 型のオーバーフローを防ぐためのチェックです。
    • ret.length は現在までにデコードされた長さの値です。次のバイト bret.length <<= 8 で左に8ビットシフトして結合する前に、ret.length1<<23 (2^23) 以上であるかをチェックします。
    • もし ret.length1<<23 以上であれば、さらに8ビット左シフトすると、32ビット符号付き int の最大値である 2^31 - 1 を超える可能性が高くなります。この場合、StructuralError{"length too large"} を返してエラーとします。これにより、int 型の範囲内で安全に長さを処理できるようになります。
  3. if ret.length == 0 の追加:

    • これは、DERの厳格なルールである「長さは最小限のバイト数で表現されなければならない」という要件を満たすためのチェックです。
    • 長さがデコードされた後、もし ret.length0 であるにもかかわらず、それが長形式でエンコードされていた場合(つまり、長さを示すバイト列が 0x00 などの冗長なゼロを含んでいた場合)、StructuralError{"superfluous leading zeros in length"} を返します。
    • 例えば、長さが 0x01 であるべきなのに 0x82 0x00 0x01 のようにエンコードされている場合、ret.length は最終的に 1 になりますが、このチェックは ret.length0 の場合にのみ発動するため、この特定の例では直接エラーにはなりません。しかし、例えば 0x81 0x00 のように長さが0なのに長形式でエンコードされているようなケースを捕捉します。テストケース // Superfluous zeros in the length should be an error. {[]byte{0xa0, 0x82, 0x00, 0x01}, false, tagAndLength{}}, は、このチェックがどのように機能するかを示唆しています。このテストケースでは、0x0001 という長さが 0x82 でエンコードされているため、ret.length0 になることはありませんが、このテストは parseTagAndLength0x0001 のような冗長なゼロを含む長さを正しくエラーとして処理することを確認しています。

src/pkg/encoding/asn1/asn1_test.go の変更点

追加されたテストケースは、上記の変更が正しく機能することを確認しています。

  • {[]byte{0xa0, 0x82, 0x00, 0x01}, false, tagAndLength{}},

    • これは、長さフィールドに冗長な先行ゼロ (0x00) が含まれている場合に、エラーとなることをテストしています。0x82 は続く2バイトが長さであることを示し、0x00 0x01 は長さが1であることを意味しますが、これは 0x01 と短形式で表現できるため、DERでは冗長とみなされます。
  • {[]byte{0xa0, 0x84, 0x7f, 0xff, 0xff, 0xff}, true, tagAndLength{2, 0, 0x7fffffff, true}},

    • これは、Goの32ビット int 型の最大値 (0x7fffffff、つまり2^31-1) までの長さが正しくデコードされることをテストしています。0x84 は続く4バイトが長さであることを示します。
  • {[]byte{0xa0, 0x84, 0x80, 0x00, 0x00, 0x00}, false, tagAndLength{}},

    • これは、Goの32ビット int 型の最大値を超える長さ(0x80000000、つまり2^31)がエラーとして拒否されることをテストしています。これは、if ret.length >= 1<<23 のチェックによって捕捉されます。

これらの変更とテストケースにより、encoding/asn1 パッケージは、より大きなASN.1データの長さを正確に処理し、DERの厳格なエンコーディングルールに準拠するようになりました。

関連リンク

参考にした情報源リンク