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

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

このコミットは、Go言語の標準ライブラリ net パッケージ内のIPv6アドレス解析ロジックに関するバグ修正です。具体的には、ParseIP 関数がIPv6アドレスの省略記法(::)を誤って解釈し、RFC 4291の規定に反するアドレス形式に対しても有効なIPアドレスを返してしまう問題を修正しています。

コミット

commit 487dff18521634c7589a9a65640dce930eb9715a
Author: Alex A Skinner <alex@lx.lc>
Date:   Fri Dec 20 21:29:28 2013 +0900

    net: ParseIP should return nil if :: doesn't expand in an IPv6 address.
    
    Per RFC 4291, 'The use of "::" indicates one or more groups of 16 bits of zeros.'
    Fixes #6628
    
    R=golang-dev, rsc, minux.ma, mikioh.mikioh
    CC=golang-dev
    https://golang.org/cl/15990043

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

https://github.com/golang/go/commit/487dff18521634c7589a9a65640dce930eb9715a

元コミット内容

net: ParseIP should return nil if :: doesn't expand in an IPv6 address.

Per RFC 4291, 'The use of "::" indicates one or more groups of 16 bits of zeros.'
Fixes #6628

変更の背景

この変更は、Go言語の net パッケージにおけるIPv6アドレスのパース処理の厳密性を向上させるために行われました。具体的には、Issue 6628で報告された問題に対応しています。

IPv6アドレスは128ビット長であり、通常は8つの16ビットグループをコロンで区切って表記されます(例: 2001:0db8:85a3:0000:0000:8a2e:0370:7334)。しかし、連続する0のグループがある場合、::(ダブルコロン)を使用してそれらを省略する「ゼロ圧縮」という表記法がRFC 4291で定義されています。例えば、2001:0db8:0000:0000:0000:0000:1428:57ab2001:db8::1428:57ab と短縮できます。

RFC 4291の重要なルールの一つは、:: は「1つ以上の16ビットのゼロのグループ」を表すという点です。つまり、:: が展開された結果、少なくとも1つのゼロのグループが存在しなければなりません。もし :: が展開されてもゼロのグループが一つも存在しないような形式(例えば、a1:a2:a3:a4::b1:b2:b3:b4 のように、:: の前後にすでに8つのグループが揃っている場合)は、RFC 4291に準拠しない不正なIPv6アドレス形式となります。

Goの net パッケージの ParseIP 関数は、このRFCの規定を完全に満たしておらず、不正な :: の使用を含むIPv6アドレス文字列に対しても、誤って有効なIPアドレスとしてパースしてしまうバグがありました。このバグは、不正な入力がシステムに受け入れられ、予期せぬ動作やセキュリティ上の問題を引き起こす可能性があったため、修正が必要とされました。

前提知識の解説

IPv6アドレスの表記とゼロ圧縮(RFC 4291)

IPv6アドレスは128ビットの数値であり、通常は16ビットごとに区切られた8つの16進数グループで表現されます。各グループはコロン(:)で区切られます。

例: 2001:0db8:85a3:0000:0000:8a2e:0370:7334

RFC 4291では、IPv6アドレスの表記を簡略化するためのルールがいくつか定義されています。

  1. 先行ゼロの省略: 各16ビットグループの先行するゼロは省略できます。 例: 0db8db8 と書けます。
  2. ゼロ圧縮(::: 1つ以上の連続する16ビットのゼロのグループは、::(ダブルコロン)で置き換えることができます。この :: はアドレス内で一度だけ使用できます。 例: 2001:0db8:0000:0000:0000:0000:1428:57ab2001:db8::1428:57ab となります。 0000:0000:0000:0000:0000:0000:0000:0001 (ループバックアドレス) は ::1 となります。 0000:0000:0000:0000:0000:0000:0000:0000 (未指定アドレス) は :: となります。

この :: の使用に関する重要な制約は、「1つ以上のゼロのグループ」を省略するという点です。これは、:: が展開されたときに、少なくとも1つの16ビットのゼロのグループが生成されることを意味します。もし :: の前後にすでに8つのグループが揃っている場合、:: はゼロのグループを省略する役割を果たせず、そのアドレスはRFC 4291に準拠しない不正な形式となります。

例えば、a1:a2:a3:a4::b1:b2:b3:b4 という形式を考えてみましょう。 a1:a2:a3:a4 で4グループ、b1:b2:b3:b4 で4グループ、合計8グループです。 この場合、:: が省略できるゼロのグループは存在しません。したがって、この形式はRFC 4291に違反します。

Go言語の net パッケージと ParseIP 関数

Go言語の net パッケージは、ネットワークプログラミングのための基本的なインターフェースを提供します。net.ParseIP 関数は、文字列形式のIPアドレス(IPv4またはIPv6)を net.IP 型に変換するために使用されます。入力文字列が有効なIPアドレス形式でない場合、この関数は nil を返します。

技術的詳細

このコミットの技術的な核心は、net パッケージ内の parseIPv6 関数における :: (ellipsis) の処理ロジックの修正です。

parseIPv6 関数は、IPv6アドレス文字列を解析し、net.IP 型のバイトスライスに変換します。この関数は、:: が含まれているかどうかを ellipsis 変数で追跡します。ellipsis:: が見つかった位置を示します。

元の実装では、:: が見つかった場合、その位置にゼロを挿入してアドレスを拡張していました。しかし、:: の前後にすでに8つのグループが揃っている場合(つまり、:: がゼロのグループを省略する役割を果たせない場合)のチェックが不十分でした。

修正前は、:: が存在し、かつ n (解析されたグループ数) が8未満の場合にのみゼロの挿入が行われていました。しかし、n が8の場合でも ellipsis が設定されている(つまり :: が存在している)ケースが考慮されていませんでした。

このコミットでは、以下の条件を追加することでこの問題を解決しています。

	} else if ellipsis >= 0 {
		// Ellipsis must represent at least one 0 group.
		return nil, zone
	}

このコードは、ellipsis >= 0、つまり :: がアドレス文字列中に存在する場合に実行されます。 もし、:: が存在し、かつ :: が展開されることでゼロのグループが追加されない(つまり、:: の前後にすでに8つのグループが揃っている)場合、この条件に合致します。 この場合、RFC 4291の「:: は1つ以上の16ビットのゼロのグループを表す」という規定に違反するため、parseIPv6 関数は nil を返し、不正なIPアドレスとして扱われるようになります。

これにより、a1:a2:a3:a4::b1:b2:b3:b4 のような不正なIPv6アドレス形式が ParseIP によって有効と判断されることがなくなりました。

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

変更は src/pkg/net/ip.gosrc/pkg/net/ip_test.go の2つのファイルにわたります。

src/pkg/net/ip.go

--- a/src/pkg/net/ip.go
+++ b/src/pkg/net/ip.go
@@ -623,6 +623,9 @@ func parseIPv6(s string, zoneAllowed bool) (ip IP, zone string) {
 		for k := ellipsis + n - 1; k >= ellipsis; k-- {
 			ip[k] = 0
 		}
+	} else if ellipsis >= 0 {
+		// Ellipsis must represent at least one 0 group.
+		return nil, zone
 	}
 	return ip, zone
 }

src/pkg/net/ip_test.go

--- a/src/pkg/net/ip_test.go
+++ b/src/pkg/net/ip_test.go
@@ -25,6 +25,7 @@ var parseIPTests = []struct {
 	{"fe80::1%lo0", nil},
 	{"fe80::1%911", nil},
 	{"", nil},
+	{"a1:a2:a3:a4::b1:b2:b3:b4", nil}, // Issue 6628
 }
 
 func TestParseIP(t *testing.T) {

コアとなるコードの解説

src/pkg/net/ip.go の変更

parseIPv6 関数内の変更は、:: (ellipsis) の処理ロジックに新しい条件を追加しています。

	} else if ellipsis >= 0 {
		// Ellipsis must represent at least one 0 group.
		return nil, zone
	}
  • ellipsis >= 0: これは、入力文字列中に :: が存在したことを示します。ellipsis 変数は :: が見つかったインデックスを保持します。
  • この else if ブロックは、直前の if ブロック(ellipsis >= 0 && n < 8)が実行されなかった場合に評価されます。直前の if ブロックは、:: が存在し、かつアドレスがまだ完全に埋まっていない(つまり、:: がゼロを省略する余地がある)場合に、ゼロを挿入してアドレスを拡張する処理を行います。
  • したがって、この else if ブロックに到達するということは、:: は存在するものの、n < 8 の条件が満たされない、つまり n がすでに8である(アドレスがすでに8つのグループで構成されている)ことを意味します。
  • この状況で :: が存在するということは、:: が「1つ以上のゼロのグループ」を省略するというRFC 4291の規定に違反していることを意味します。なぜなら、すでに8つのグループが存在するため、:: が省略できるゼロのグループがないからです。
  • そのため、return nil, zone が実行され、このIPv6アドレス文字列は不正な形式として扱われ、ParseIP 関数は nil を返すようになります。

src/pkg/net/ip_test.go の変更

テストファイルには、新しいテストケースが追加されています。

+	{"a1:a2:a3:a4::b1:b2:b3:b4", nil}, // Issue 6628

このテストケースは、まさにこのコミットで修正された不正なIPv6アドレス形式を検証するためのものです。

  • "a1:a2:a3:a4::b1:b2:b3:b4": この文字列は、:: の前後にすでに8つのグループ(a1からa4で4グループ、b1からb4で4グループ)が存在するため、RFC 4291の規定に違反します。
  • nil: 期待される結果は nil です。これは、ParseIP 関数がこの不正な文字列を有効なIPアドレスとしてパースせず、エラー(nil)を返すことを意味します。

このテストケースの追加により、修正が正しく機能していることが保証され、将来のリグレッションを防ぐことができます。

関連リンク

参考にした情報源リンク