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

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

このコミットは、Go言語の標準ライブラリ net/textproto パッケージにおける CanonicalMIMEHeaderKey 関数が、特定の入力(スペースを含む文字列)に対してパニックを引き起こすバグを修正するものです。具体的には、MIMEヘッダーキーを正規化する際に、スペース文字の処理が不適切であったために発生するランタイムエラーを解消します。

コミット

commit 692a14787f4a539142f1f7fefa5e610e4e3dc8b5
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Mon Nov 4 12:35:11 2013 -0500

    net/textproto: fix CanonicalMIMEHeaderKey panic
    
    Fixes #6712
    
    R=golang-dev, adg, rsc
    CC=golang-dev
    https://golang.org/cl/21450043

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

https://github.com/golang/go/commit/692a14787f4a539142f1f7fefa5e610e4e3dc8b5

元コミット内容

diff --git a/src/pkg/net/textproto/reader.go b/src/pkg/net/textproto/reader.go
index 56ece5b087..b0c07413c1 100644
--- a/src/pkg/net/textproto/reader.go
+++ b/src/pkg/net/textproto/reader.go
@@ -574,13 +574,10 @@ func canonicalMIMEHeaderKey(a []byte) string {
 		// and upper case after each dash.
 		// (Host, User-Agent, If-Modified-Since).
 		// MIME headers are ASCII only, so no Unicode issues.
-\t\tif a[i] == ' ' {
-\t\t\ta[i] = '-'
-\t\t\tupper = true
-\t\t\tcontinue
-\t\t}\n \t\tc := a[i]
-\t\tif upper && 'a' <= c && c <= 'z' {\n+\t\tif c == ' ' {\n+\t\t\tc = '-'\n+\t\t} else if upper && 'a' <= c && c <= 'z' {\n \t\t\tc -= toLower
 \t\t} else if !upper && 'A' <= c && c <= 'Z' {\n \t\t\tc += toLower
diff --git a/src/pkg/net/textproto/reader_test.go b/src/pkg/net/textproto/reader_test.go
index f27042d4e9..cc12912b63 100644
--- a/src/pkg/net/textproto/reader_test.go
+++ b/src/pkg/net/textproto/reader_test.go
@@ -25,6 +25,10 @@ var canonicalHeaderKeyTests = []canonicalHeaderKeyTest{
 	{"user-agent", "User-Agent"},\n 	{"USER-AGENT", "User-Agent"},\n 	{"üser-agenT", "üser-Agent"}, // non-ASCII unchanged
+\n+\t// This caused a panic due to mishandling of a space:
+\t{\"C Ontent-Transfer-Encoding\", \"C-Ontent-Transfer-Encoding\"},\n+\t{\"foo bar\", \"Foo-Bar\"},\n }\n \n func TestCanonicalMIMEHeaderKey(t *testing.T) {

変更の背景

このコミットは、Go言語の net/textproto パッケージ内の CanonicalMIMEHeaderKey 関数が、スペースを含むMIMEヘッダーキーを処理する際にパニック(プログラムの異常終了)を引き起こすというバグ(Issue #6712)を修正するために導入されました。

CanonicalMIMEHeaderKey 関数は、HTTPやMIMEメッセージのヘッダーキーを標準的な形式(例: "content-type" を "Content-Type" に)変換することを目的としています。しかし、元の実装では、入力文字列にスペースが含まれている場合に、そのスペースをハイフンに変換するロジックが、文字のケース変換ロジックと適切に連携していませんでした。これにより、特定の条件下で配列のインデックスアクセスが不正になり、パニックが発生していました。

この問題は、特に外部からの入力(例えば、不正な形式のHTTPヘッダー)を処理する際に、Goアプリケーションが予期せずクラッシュする可能性を秘めており、堅牢性の観点から修正が急務でした。

前提知識の解説

net/textproto パッケージ

net/textproto パッケージは、Go言語の標準ライブラリの一部であり、HTTP、NNTP、SMTPなどのインターネットテキストベースプロトコルで共通のテキストプロトコル操作を実装するための低レベルな機能を提供します。これには、ヘッダーの読み書き、MIMEヘッダーの正規化などのユーティリティが含まれます。

MIMEヘッダーと正規化 (Canonical Form)

MIME (Multipurpose Internet Mail Extensions) ヘッダーは、電子メールやHTTPなどのインターネットプロトコルにおいて、メッセージのメタデータ(送信者、受信者、コンテンツの種類など)を記述するために使用されます。ヘッダーは通常、「Key: Value」の形式で記述されます。

MIMEヘッダーキーの正規化とは、ヘッダーキーの表記揺れを吸収し、一貫した形式に変換することです。例えば、HTTPヘッダーの Content-Type は、content-typeCONTENT-TYPECoNtEnT-TyPe など、様々な大文字・小文字の組み合わせで送られてくる可能性があります。正規化関数は、これらをすべて Content-Type のような標準的な形式に変換することで、ヘッダーの比較や処理を容易にします。一般的な正規化ルールは以下の通りです。

  • 最初の文字を大文字にする。
  • ハイフン (-) の後の文字を大文字にする。
  • それ以外の文字を小文字にする。
  • MIMEヘッダーはASCII文字のみで構成されると仮定されます。

Goにおけるパニック (Panic)

Go言語におけるパニックは、プログラムの実行中に回復不可能なエラーが発生したことを示すメカニズムです。パニックが発生すると、通常のプログラムフローは中断され、遅延関数(defer)が実行された後、プログラムは終了します。パニックは、プログラマーが予期しない、または処理できないような重大なエラー(例: nilポインタ参照、配列の範囲外アクセス)が発生した場合に用いられます。このコミットで修正されたバグは、まさにこのような配列の範囲外アクセスによるパニックでした。

技術的詳細

CanonicalMIMEHeaderKey 関数は、入力されたバイトスライス a をMIMEヘッダーキーの正規形式に変換します。この関数は、ヘッダーキーがASCII文字のみで構成されていることを前提としています。

元のコードでは、ループ内で各文字を処理する際に、以下のロジックがありました。

		if a[i] == ' ' {
			a[i] = '-'
			upper = true
			continue
		}
		c := a[i]
		if upper && 'a' <= c && c <= 'z' {
			c -= toLower
		} else if !upper && 'A' <= c && c <= 'Z' {
			c += toLower
		}

このコードの問題点は、if a[i] == ' ' のブロック内で continue が実行されると、その後の c := a[i] やケース変換ロジックがスキップされてしまうことです。しかし、a[i] = '-' でスペースがハイフンに置き換えられた後、upper = true が設定されるため、次のループイテレーションで upper フラグが true のままになります。

もし、スペースの直後に小文字のASCII文字が続く場合、例えば "foo bar" のような入力では、' ''-' に変換され、uppertrue になります。次の文字 'b' が処理される際、uppertrue なので 'b' は大文字の 'B' に変換されることが期待されます。しかし、元のコードでは、スペースの処理が continue で終わるため、c := a[i] の行が実行されず、a[i] の値が正しく c に代入されません。これにより、後続のケース変換ロジックが意図しないバイトに対して実行される可能性があり、結果として配列の範囲外アクセスや不正なメモリ操作を引き起こし、パニックにつながっていました。

修正は、スペースの処理を他の文字のケース変換ロジックと統合することで、この問題を解決しています。

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

src/pkg/net/textproto/reader.go ファイルの canonicalMIMEHeaderKey 関数内の以下の部分が変更されました。

--- a/src/pkg/net/textproto/reader.go
+++ b/src/pkg/net/textproto/reader.go
@@ -574,13 +574,10 @@ func canonicalMIMEHeaderKey(a []byte) string {
 		// and upper case after each dash.
 		// (Host, User-Agent, If-Modified-Since).
 		// MIME headers are ASCII only, so no Unicode issues.
-\t\tif a[i] == ' ' {
-\t\t\ta[i] = '-'
-\t\t\tupper = true
-\t\t\tcontinue
-\t\t}\n \t\tc := a[i]
-\t\tif upper && 'a' <= c && c <= 'z' {\n+\t\tif c == ' ' {\n+\t\t\tc = '-'\n+\t\t} else if upper && 'a' <= c && c <= 'z' {\n \t\t\tc -= toLower
 \t\t} else if !upper && 'A' <= c && c <= 'Z' {\n \t\t\tc += toLower

また、src/pkg/net/textproto/reader_test.go に以下のテストケースが追加されました。

--- a/src/pkg/net/textproto/reader_test.go
+++ b/src/pkg/net/textproto/reader_test.go
@@ -25,6 +25,10 @@ var canonicalHeaderKeyTests = []canonicalHeaderKeyTest{
 	{"user-agent", "User-Agent"},\n 	{"USER-AGENT", "User-Agent"},\n 	{"üser-agenT", "üser-Agent"}, // non-ASCII unchanged
+\n+\t// This caused a panic due to mishandling of a space:
+\t{\"C Ontent-Transfer-Encoding\", \"C-Ontent-Transfer-Encoding\"},\n+\t{\"foo bar\", \"Foo-Bar\"},\n }\n \n func TestCanonicalMIMEHeaderKey(t *testing.T) {

コアとなるコードの解説

変更されたコードは、スペース文字の処理方法を根本的に改善しています。

変更前:

		if a[i] == ' ' {
			a[i] = '-'
			upper = true
			continue // ここで次のループへスキップ
		}
		c := a[i] // スペースの場合は実行されない
		// ... ケース変換ロジック ...

このロジックでは、a[i] がスペースの場合、それをハイフンに変換し、upper フラグを true に設定した後、continue でループの次のイテレーションに進んでいました。これにより、スペースの直後の文字に対するケース変換ロジックが、upper フラグが true であるにもかかわらず、適切に適用されない可能性がありました。特に、c := a[i] の行がスキップされることで、c の値が不定になり、その後の c を使用した演算が不正なメモリにアクセスする原因となっていました。

変更後:

		c := a[i] // 常に現在の文字を取得
		if c == ' ' {
			c = '-' // スペースをハイフンに変換
		} else if upper && 'a' <= c && c <= 'z' {
			c -= toLower // 大文字に変換
		} else if !upper && 'A' <= c && c <= 'Z' {
			c += toLower // 小文字に変換
		}

修正後のコードでは、まず現在の文字 a[i]c に代入します。そして、if c == ' ' の条件でスペースをチェックし、スペースであれば c をハイフンに変換します。この処理は continue を含まないため、c の値が更新された後も、その後の else if ブロックでケース変換ロジックが引き続き評価されます。

これにより、スペースがハイフンに変換された後、upper フラグの状態に応じて、次の文字が正しく大文字または小文字に変換されるようになります。例えば、"foo bar"' ''-' に変換された後、upper フラグが true に設定され、次の 'b'B に変換されるという期待通りの動作が保証されます。

追加されたテストケース {"C Ontent-Transfer-Encoding", "C-Ontent-Transfer-Encoding"}{"foo bar", "Foo-Bar"} は、この修正がスペースを含む文字列を正しく処理し、パニックを引き起こさないことを検証するために重要です。

関連リンク

参考にした情報源リンク