[インデックス 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:57ab
は 2001: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アドレスの表記を簡略化するためのルールがいくつか定義されています。
- 先行ゼロの省略: 各16ビットグループの先行するゼロは省略できます。
例:
0db8
はdb8
と書けます。 - ゼロ圧縮(
::
): 1つ以上の連続する16ビットのゼロのグループは、::
(ダブルコロン)で置き換えることができます。この::
はアドレス内で一度だけ使用できます。 例:2001:0db8:0000:0000:0000:0000:1428:57ab
は2001: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.go
と src/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
)を返すことを意味します。
このテストケースの追加により、修正が正しく機能していることが保証され、将来のリグレッションを防ぐことができます。
関連リンク
- Go Issue 6628: https://github.com/golang/go/issues/6628
- Go CL 15990043: https://golang.org/cl/15990043
参考にした情報源リンク
- RFC 4291: IP Version 6 Addressing Architecture: https://datatracker.ietf.org/doc/html/rfc4291
- IPv6アドレスの表記 - Wikipedia: https://ja.wikipedia.org/wiki/IPv6%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9#%E8%A8%98%E8%BF%B0
- Go言語
net
パッケージのドキュメント: https://pkg.go.dev/net - Go言語
net.ParseIP
関数のドキュメント: https://pkg.go.dev/net#ParseIP