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

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

このコミットは、Go言語の標準ライブラリである net/http パッケージ内の Client 型の Post メソッドにおける、エラー発生時のnilポインタデリファレンス(nil pointer dereference)のバグを修正するものです。具体的には、send 関数がエラーを返した場合に、http.CookieJar へのアクセスがnilポインタデリファレンスを引き起こす可能性があった問題を解決しています。

コミット

  • コミットハッシュ: ed7a8f71590bcd704335bab5c07e3164431e43e1
  • 作者: Volker Dobler dr.volker.dobler@gmail.com
  • コミット日時: 2012年1月30日 月曜日 07:57:50 -0800
  • コミットメッセージ:
    net/http: Fix nil pointer dereference in error case.
    
    R=golang-dev
    CC=bradfitz, golang-dev
    https://golang.org/cl/5598044
    

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

https://github.com/golang/go/commit/ed7a8f71590bcd704335bab5c07e3164431e43e1

元コミット内容

net/http: Fix nil pointer dereference in error case.

R=golang-dev
CC=bradfitz, golang-dev
https://golang.org/cl/5598044

変更の背景

この変更は、net/http パッケージの Client.Post メソッドにおいて、HTTPリクエストの送信中にエラーが発生した場合に、プログラムがクラッシュする可能性があったバグを修正するために行われました。

具体的には、Client.Post メソッド内で send 関数が呼び出され、この send 関数がHTTPリクエストの送信処理を行います。send 関数は *Responseerror の2つの値を返します。通常、エラーが発生しなかった場合は *Response オブジェクトが返され、エラーが発生した場合は *Responsenil となり、error オブジェクトにエラー情報が格納されます。

問題は、send 関数がエラーを返した場合(つまり rnil の場合)でも、その後のコードで r.Cookies() が呼び出される可能性があった点です。rnil であるにもかかわらず、そのメソッドである Cookies() を呼び出そうとすると、Go言語では「nilポインタデリファレンス」というランタイムパニックが発生し、プログラムが異常終了してしまいます。

このバグは、特にネットワークの問題やサーバーからの不正な応答など、HTTPリクエストが正常に完了しないシナリオで顕在化する可能性がありました。安定したHTTPクライアントを提供するためには、このようなエラーケースでの堅牢性が不可欠であり、この修正はそのための重要なステップでした。

前提知識の解説

Go言語の net/http パッケージ

net/http パッケージは、Go言語でHTTPクライアントおよびサーバーを実装するための標準ライブラリです。Webアプリケーションの構築や、外部のHTTPサービスとの連携に広く利用されます。

  • http.Client: HTTPリクエストを送信するためのクライアントを表す構造体です。タイムアウト設定、リダイレクトポリシー、Cookie管理などをカスタマイズできます。
  • http.Request: 送信するHTTPリクエストを表す構造体です。URL、メソッド(GET, POSTなど)、ヘッダー、ボディなどの情報を含みます。
  • http.Response: HTTPリクエストに対するサーバーからの応答を表す構造体です。ステータスコード、ヘッダー、ボディなどの情報を含みます。
  • io.Reader: データを読み出すためのインターフェースです。Client.Post メソッドの body 引数に利用され、リクエストボディのデータソースとして機能します。
  • http.Transport: http.Client が実際にHTTPリクエストを送信する際の低レベルな詳細(TCP接続の確立、TLSハンドシェイクなど)を処理するインターフェースです。
  • http.CookieJar: HTTPクライアントがCookieを保存・管理するためのインターフェースです。これにより、セッション管理などが可能になります。Client 構造体の Jar フィールドに設定することで利用されます。

Go言語のエラーハンドリング

Go言語では、エラーは戻り値として明示的に扱われます。関数は通常、最後の戻り値として error 型の値を返します。エラーが発生しなかった場合は nil が返され、エラーが発生した場合は nil ではない error オブジェクトが返されます。開発者はこの error の戻り値をチェックし、適切なエラー処理を行う責任があります。

result, err := someFunction()
if err != nil {
    // エラー処理
}
// 正常処理

nilポインタデリファレンス (Nil Pointer Dereference)

nilポインタデリファレンスは、プログラミングにおいて、nil(または null)値を持つポインタが指すメモリ領域にアクセスしようとしたときに発生するランタイムエラーです。Go言語では、これは「panic」(パニック)として扱われ、プログラムの実行が停止します。

例えば、以下のようなコードで発生します。

var s *MyStruct // s は nil
s.DoSomething() // ここでnilポインタデリファレンスが発生

このコミットのケースでは、send 関数がエラーを返した際に rnil になるにもかかわらず、r.Cookies() を呼び出そうとしたことが問題でした。

技術的詳細

修正前の Client.Post メソッドの関連部分は以下のようになっていました。

r, err = send(req, c.Transport)
if c.Jar != nil { // ここで c.Jar がnilでないかだけをチェック
    c.Jar.SetCookies(req.URL, r.Cookies()) // r がnilの場合、ここでパニック
}
return r, err

ここで問題となるのは、send(req, c.Transport) の呼び出しです。この関数は (*Response, error) のペアを返します。

  1. 成功ケース: send が成功した場合、r は有効な *Response オブジェクトを指し、errnil です。この場合、c.Jar != nil の条件が真であれば、c.Jar.SetCookies(req.URL, r.Cookies()) は問題なく実行されます。
  2. エラーケース: send が失敗した場合、rnil となり、err は非nilのエラーオブジェクトを指します。このとき、if c.Jar != nil の条件は c.Jar 自体が nil でない限り真となります。しかし、rnil であるため、r.Cookies() を呼び出そうとすると、nil ポインタデリファレンスが発生し、プログラムがパニックに陥ります。

この修正は、c.Jar.SetCookies を呼び出す前に、send 関数がエラーを返していないこと(つまり r が有効な *Response オブジェクトであること)を確実にチェックすることで、この脆弱性を解消しています。

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

--- a/src/pkg/net/http/client.go
+++ b/src/pkg/net/http/client.go
@@ -275,7 +275,7 @@ func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response,
 	}\n \treq.Header.Set(\"Content-Type\", bodyType)\n \tr, err = send(req, c.Transport)\n-\tif c.Jar != nil {\n+\tif err == nil && c.Jar != nil {\n \t\tc.Jar.SetCookies(req.URL, r.Cookies())\n \t}\n \treturn r, err

コアとなるコードの解説

変更は src/pkg/net/http/client.go ファイルの Client.Post メソッド内の一行です。

修正前:

if c.Jar != nil {
    c.Jar.SetCookies(req.URL, r.Cookies())
}

修正後:

if err == nil && c.Jar != nil {
    c.Jar.SetCookies(req.URL, r.Cookies())
}

この変更の核心は、if 文の条件に err == nil を追加した点です。

  • err == nil: これは、send 関数がエラーを返さずに正常に完了したことを意味します。send 関数が正常に完了した場合にのみ、r (Responseオブジェクト) は有効なポインタを保持します。
  • c.Jar != nil: これは、ClientCookieJar が設定されているかどうかをチェックします。CookieJar が設定されていない場合は、Cookieの処理は不要です。

この二つの条件を && (論理AND) で結合することで、以下の論理が保証されます。

send 関数がエラーなく成功し、かつ ClientCookieJar が設定されている場合にのみ、c.Jar.SetCookies(req.URL, r.Cookies()) を実行する。」

これにより、send 関数がエラーを返して rnil になった場合でも、r.Cookies() が呼び出されることがなくなり、nilポインタデリファレンスによるパニックが回避されます。これは、Go言語における堅牢なエラーハンドリングの原則に則った、シンプルかつ効果的な修正です。

関連リンク

参考にした情報源リンク