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

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

このコミットは、Go言語の標準ライブラリ crypto/x509 パッケージにおける CertPool 型の findVerifiedParents メソッドが、nil レシーバで呼び出された際にクラッシュする可能性があったバグを修正するものです。具体的には、メソッドの冒頭でレシーバが nil であるかどうかのチェックを追加し、nil の場合は早期に処理を終了するように変更されています。また、この修正が正しく機能することを確認するための新しいテストケースも追加されています。

コミット

commit 71f0fb77602701bf3e3f6efd3aa1be5d42a64458
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Wed Dec 21 10:49:35 2011 -0800

    crypto/x509: don't crash with nil receiver in accessor method
    
    Fixes #2600
    
    R=golang-dev, agl, rsc
    CC=golang-dev
    https://golang.org/cl/5500064

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

https://github.com/golang/go/commit/71f0fb77602701bf3e3f6efd3aa1be5d42a64458

元コミット内容

crypto/x509: don't crash with nil receiver in accessor method

このコミットは、crypto/x509 パッケージ内のアクセサメソッドが nil レシーバで呼び出された際にクラッシュしないように修正します。

変更の背景

Go言語では、メソッドはポインタレシーバ (*T) または値レシーバ (T) を持つことができます。ポインタレシーバを持つメソッドは、レシーバが nil である場合でも呼び出すことが可能です。しかし、メソッド内で nil レシーバのフィールドにアクセスしようとすると、ランタイムパニック(nil ポインタデリファレンス)が発生し、プログラムがクラッシュします。

このコミット以前の crypto/x509 パッケージの CertPool 型の findVerifiedParents メソッドは、ポインタレシーバ *CertPool を持っていました。しかし、メソッドの内部でレシーバ snil である可能性を考慮していなかったため、もし snil の状態でこのメソッドが呼び出されると、内部で nil レシーバのフィールドにアクセスしようとしてパニックが発生する脆弱性がありました。

この問題は、GoのIssue #2600として報告されており、このコミットはその問題を解決するために作成されました。特に、証明書検証の際に CertPoolnil になるようなエッジケースで、予期せぬクラッシュを引き起こす可能性がありました。

前提知識の解説

Go言語のレシーバとメソッド

Go言語において、メソッドは特定の型に関連付けられた関数です。メソッドはレシーバ(receiver)と呼ばれる特別な引数を持ち、これによりそのメソッドがどの型の値に対して操作を行うかを指定します。レシーバには「値レシーバ」と「ポインタレシーバ」の2種類があります。

  • 値レシーバ (func (t T) MethodName(...)): メソッドが呼び出されると、レシーバの値のコピーが作成されます。メソッド内でレシーバの値を変更しても、元の値には影響しません。
  • ポインタレシーバ (func (t *T) MethodName(...)): メソッドが呼び出されると、レシーバのポインタのコピーが作成されます。メソッド内でポインタを通じてレシーバの値を変更すると、元の値も変更されます。また、ポインタレシーバを持つメソッドは、レシーバが nil であるポインタに対しても呼び出すことができます。これはGoの設計上の特徴であり、nil レシーバに対する振る舞いを定義することで、より柔軟なAPI設計が可能になります。

nil の概念とGoにおけるnilポインタ

nil はGo言語におけるゼロ値の一つで、ポインタ、インターフェース、マップ、スライス、チャネルなどの参照型が何も指していない状態を表します。ポインタが nil である場合、それは有効なメモリアドレスを指していません。nil ポインタをデリファレンス(つまり、nil ポインタが指す先の値にアクセスしようとすること)しようとすると、ランタイムパニックが発生し、プログラムが異常終了します。

crypto/x509 パッケージ

crypto/x509 パッケージは、Go言語の標準ライブラリの一部であり、X.509証明書とPKIX(Public Key Infrastructure for X.509)に関する機能を提供します。これには、証明書の解析、検証、証明書署名要求(CSR)の生成などが含まれます。ウェブサイトのHTTPS通信や、セキュアな通信プロトコルにおいて、サーバーやクライアントの身元を確認するためにX.509証明書が広く利用されています。

CertPool

CertPoolcrypto/x509 パッケージ内で定義されている型で、信頼されたルート証明書や中間証明書を格納するためのコレクションです。証明書の検証プロセスにおいて、与えられた証明書がこのプール内のいずれかの証明書によって署名されているか、または信頼されたルート証明書に連なるチェーンの一部であるかを確認するために使用されます。

findVerifiedParents メソッド

CertPool 型の findVerifiedParents メソッドは、特定の証明書 (cert) の親となる可能性のある証明書を CertPool 内から探し出す役割を担います。これは、証明書チェーンを構築し、最終的にルート証明書まで辿ることで、証明書の信頼性を検証するプロセスの一部です。

技術的詳細

このコミットの技術的な核心は、crypto/x509/cert_pool.go ファイル内の findVerifiedParents メソッドに nil レシーバチェックを追加した点にあります。

元のコードでは、findVerifiedParents メソッドは *CertPool 型のレシーバ s を受け取ります。Goの仕様上、ポインタレシーバを持つメソッドは、そのポインタが nil であっても呼び出すことができます。しかし、メソッドの内部で snil であるにもかかわらず、s のフィールド(例えば s.certss.bySubject など)にアクセスしようとすると、nil ポインタデリファレンスが発生し、プログラムがクラッシュします。

このコミットでは、findVerifiedParents メソッドの冒頭に以下のコードが追加されました。

if s == nil {
    return
}

このシンプルな nil チェックにより、snil の場合は、メソッドがそれ以上処理を進めることなく、空の parents スライスを返して安全に終了するようになります。これにより、nil レシーバによるクラッシュが防止されます。

さらに、src/pkg/crypto/x509/verify_test.go に新しいテストケースが追加されました。このテストケースは、CertPoolnil の状態で証明書検証プロセスが実行された場合に、クラッシュが発生しないことを保証します。具体的には、verifyTest 構造体に nilRoots フィールドが追加され、これが true の場合に opts.RootsCertPool のインスタンス)が意図的に nil に設定されるようになっています。このテストは、nil レシーバの修正が正しく機能し、将来的に同様の回帰バグが発生しないための安全網となります。

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

diff --git a/src/pkg/crypto/x509/cert_pool.go b/src/pkg/crypto/x509/cert_pool.go
index adc7f9bc6d..5a0a87678e 100644
--- a/src/pkg/crypto/x509/cert_pool.go
+++ b/src/pkg/crypto/x509/cert_pool.go
@@ -28,6 +28,9 @@ func NewCertPool() *CertPool {
 // given certificate. If no such certificate can be found or the signature
 // doesn\'t match, it returns nil.\n func (s *CertPool) findVerifiedParents(cert *Certificate) (parents []int) {\n+\tif s == nil {\n+\t\treturn\n+\t}\n \tvar candidates []int
 \n \tif len(cert.AuthorityKeyId) > 0 {\ndiff --git a/src/pkg/crypto/x509/verify_test.go b/src/pkg/crypto/x509/verify_test.go
index df5443023f..2016858307 100644
--- a/src/pkg/crypto/x509/verify_test.go
+++ b/src/pkg/crypto/x509/verify_test.go
@@ -19,6 +19,7 @@ type verifyTest struct {\n \troots         []string\n \tcurrentTime   int64\n \tdnsName       string\n+\tnilRoots      bool\n \n \terrorCallback  func(*testing.T, int, error) bool\n \texpectedChains [][]string\n@@ -45,6 +46,14 @@ var verifyTests = []verifyTest{\n \n \t\terrorCallback: expectHostnameError,\n \t},\n+\t{\n+\t\tleaf:          googleLeaf,\n+\t\tintermediates: []string{thawteIntermediate},\n+\t\tnilRoots:      true, // verifies that we don\'t crash\n+\t\tcurrentTime:   1302726541,\n+\t\tdnsName:       \"www.google.com\",\n+\t\terrorCallback: expectAuthorityUnknown,\n+\t},\n \t{\n \t\tleaf:          googleLeaf,\n \t\tintermediates: []string{thawteIntermediate},\n@@ -136,6 +145,9 @@ func TestVerify(t *testing.T) {\n \t\t\tDNSName:       test.dnsName,\n \t\t\tCurrentTime:   time.Unix(test.currentTime, 0),\n \t\t}\n+\t\tif test.nilRoots {\n+\t\t\topts.Roots = nil\n+\t\t}\n \n \t\tfor j, root := range test.roots {\n \t\t\tok := opts.Roots.AppendCertsFromPEM([]byte(root))\n```

## コアとなるコードの解説

### `src/pkg/crypto/x509/cert_pool.go` の変更

`func (s *CertPool) findVerifiedParents(cert *Certificate) (parents []int)` メソッドの冒頭に以下の3行が追加されました。

```go
if s == nil {
    return
}

これは、メソッドのレシーバ snil であるかどうかをチェックするガード句です。もし snil であれば、それ以上メソッド内の処理(nil レシーバのフィールドへのアクセスなど)を実行することなく、空の parents スライスを返して安全にメソッドを終了します。これにより、nil ポインタデリファレンスによるランタイムパニックが回避されます。

src/pkg/crypto/x509/verify_test.go の変更

  1. verifyTest 構造体に新しいフィールド nilRoots bool が追加されました。このフィールドは、テストケースで CertPoolnil に設定するかどうかを制御します。

    type verifyTest struct {
        // ...
        nilRoots      bool
        // ...
    }
    
  2. verifyTests スライスに新しいテストケースが追加されました。このテストケースは、nilRoots: true を設定することで、CertPoolnil の状態で検証が試みられた場合にクラッシュしないことを検証します。

    {
        leaf:          googleLeaf,
        intermediates: []string{thawteIntermediate},
        nilRoots:      true, // verifies that we don't crash
        currentTime:   1302726541,
        dnsName:       "www.google.com",
        errorCallback: expectAuthorityUnknown,
    },
    

    このテストケースでは、nilRootstrue に設定されており、コメントで「クラッシュしないことを検証する」と明記されています。errorCallback: expectAuthorityUnknown は、nilCertPool では当然ながら認証局が不明であるというエラーが期待されることを示しています。

  3. TestVerify 関数内で、test.nilRootstrue の場合に opts.Rootsnil に設定するロジックが追加されました。

    if test.nilRoots {
        opts.Roots = nil
    }
    

    これにより、新しいテストケースが実行される際に、実際に CertPoolnil の状態で findVerifiedParents メソッドが呼び出される状況が再現され、修正が正しく機能するかどうかが検証されます。

これらの変更により、crypto/x509 パッケージはより堅牢になり、nil レシーバのケースでも安全に動作するようになりました。

関連リンク

  • Gerrit Change-ID: https://golang.org/cl/5500064
  • Go Issue (参照): #2600 (このコミットが修正したGoの内部トラッカーのIssue番号。直接のGitHubリンクは特定できませんでした。)

参考にした情報源リンク

  • Go言語の公式ドキュメント (Go言語のレシーバ、メソッド、nil の概念について)
  • crypto/x509 パッケージのGoDoc (Go言語の標準ライブラリのドキュメント)
  • Go言語のソースコード (特に src/crypto/x509/cert_pool.go および src/crypto/x509/verify_test.go の変更履歴)