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

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

このコミットは、Go言語の標準ライブラリ net/rpc/jsonrpc パッケージにおける、無効なRPC応答(リプライ)が原因で発生するnilポインタデリファレンス(nil pointer dereference)のバグを修正するものです。具体的には、JSON-RPCクライアントがサーバーからの応答を処理する際に、resultフィールドがnullである場合に発生する問題を解決し、関連するテストケースを追加しています。

コミット

  • コミットハッシュ: 0d559f7b92c1425b9a78ff67933a9baaa0534928
  • Author: Adrian Nos nos.adrian@gmail.com
  • Date: Tue Mar 12 11:45:56 2013 -0400
  • コミットメッセージ:
    net/rpc/jsonrpc: nil pointer deference on invalid reply.
    Fixes #5006.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/7691045
    

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

https://github.com/golang/go/commit/0d559f7b92c1425b9a78ff67933a9baaa0534928

元コミット内容

net/rpc/jsonrpc: nil pointer deference on invalid reply.
Fixes #5006.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7691045

変更の背景

この変更は、Go言語のIssue #5006「net/rpc/jsonrpc: nil pointer dereference on invalid reply」を修正するために行われました。このバグは、net/rpc/jsonrpcクライアントがJSON-RPCサーバーからの応答を処理する際に、特定の無効な応答形式(特にresultフィールドがnullである場合)を受け取ると、nilポインタデリファレンスを引き起こし、プログラムがクラッシュする可能性がありました。

JSON-RPCプロトコルでは、成功した応答にはresultフィールドが含まれ、エラー応答にはerrorフィールドが含まれます。しかし、プロトコルに厳密に従わない、あるいは予期せぬ形式の応答が返された場合、クライアント側で適切に処理されないと問題が発生します。このコミットは、このようなエッジケースを適切にハンドリングし、クライアントの堅牢性を向上させることを目的としています。

前提知識の解説

RPC (Remote Procedure Call)

RPCは、ネットワーク上の別のコンピュータにあるプログラムのサブルーチンや関数を、あたかもローカルにあるかのように呼び出すための技術です。これにより、分散システムにおけるプロセス間通信が容易になります。クライアントはリモートの関数を呼び出し、サーバーはその呼び出しを受け取って処理し、結果をクライアントに返します。

JSON-RPC

JSON-RPCは、RPCプロトコルの一種で、メッセージのエンコーディングにJSONを使用します。HTTPなどのトランスポートプロトコル上で動作することが多く、シンプルで人間が読みやすい形式が特徴です。JSON-RPCのメッセージは、jsonrpc(プロトコルバージョン)、method(呼び出すメソッド名)、params(引数)、id(リクエストID)などのフィールドで構成されます。応答メッセージには、result(成功時の結果)、error(エラー情報)、idが含まれます。

net/rpc パッケージ

Go言語の標準ライブラリ net/rpc は、RPCクライアントとサーバーを実装するためのフレームワークを提供します。これは、Goのencoding/gob形式をデフォルトのエンコーディングとして使用しますが、net/rpc/jsonrpcパッケージのように、他のエンコーディング(JSON)を使用するクライアント/サーバーを構築するためのインターフェースも提供しています。

net/rpc/jsonrpc パッケージ

net/rpcパッケージのサブパッケージであり、JSON-RPCプロトコルを使用してRPC通信を行うためのクライアントとサーバーの実装を提供します。このパッケージを使用することで、GoアプリケーションはJSON-RPCを話す他のシステムと容易に連携できます。

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

nilポインタデリファレンスは、プログラミングにおける一般的な実行時エラーの一つです。ポインタが何も指していない(nilまたはnull)状態で、そのポインタが指すメモリ領域にアクセスしようとすると発生します。Go言語では、nilポインタデリファレンスが発生すると、パニック(panic)が発生し、プログラムがクラッシュします。これは、プログラムの安定性にとって非常に危険な状態です。

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

Go言語では、エラーは戻り値として明示的に扱われます。関数は通常、最後の戻り値としてerror型の値を返します。エラーが発生しなかった場合はnilを返します。呼び出し元は、このerror値をチェックして、処理を続行するか、エラーを上位に伝播させるかを決定します。

技術的詳細

このコミットが修正する問題は、net/rpc/jsonrpcクライアントがサーバーからの応答を解析する際に、JSON-RPCの応答オブジェクトのresultフィールドがnullである場合に発生していました。

JSON-RPC 2.0の仕様では、成功応答はresultフィールドを持ち、エラー応答はerrorフィールドを持ちます。しかし、resultnullであるケースは、エラーではないが結果がない、あるいは特定の状況下で有効な応答として扱われることがあります。

元のコードでは、clientCodecReadResponseHeaderメソッド内で、c.resp.Error != nilの場合にのみエラー処理を行っていました。しかし、c.resp.Errornilであっても、c.resp.Resultnilである場合に、後続の処理でc.resp.Resultが指す値にアクセスしようとすると、nilポインタデリファレンスが発生する可能性がありました。

この修正では、client.goReadResponseHeaderメソッドにおいて、エラーチェックの条件にc.resp.Result == nilを追加することで、この問題を解決しています。これにより、errorフィールドがnilであってもresultフィールドがnilであるような無効な応答が来た場合に、明示的にエラーとして扱うようになります。

また、all_test.goTestMalformedOutputという新しいテストケースが追加されました。このテストは、意図的に{"id":0,"result":null,"error":null}という形式の不正なJSON-RPC応答をクライアントに送りつけ、クライアントがエラーを返すことを検証します。これにより、修正が正しく機能していることが確認されます。

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

--- a/src/pkg/net/rpc/jsonrpc/all_test.go
+++ b/src/pkg/net/rpc/jsonrpc/all_test.go
@@ -9,6 +9,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"io/ioutil"
 	"net"
 	"net/rpc"
 	"testing"
@@ -185,6 +186,22 @@ func TestMalformedInput(t *testing.T) {
 	ServeConn(srv)                 // must return, not loop
 }
 
+func TestMalformedOutput(t *testing.T) {
+	cli, srv := net.Pipe()
+	go srv.Write([]byte(`{"id":0,"result":null,"error":null}`))
+	go ioutil.ReadAll(srv)
+
+	client := NewClient(cli)
+	defer client.Close()
+
+	args := &Args{7, 8}
+	reply := new(Reply)
+	err := client.Call("Arith.Add", args, reply)
+	if err == nil {
+		t.Error("expected error")
+	}
+}
+
 func TestUnexpectedError(t *testing.T) {
 	cli, srv := myPipe()
 	go cli.PipeWriter.CloseWithError(errors.New("unexpected error!")) // reader will get this error
diff --git a/src/pkg/net/rpc/jsonrpc/client.go b/src/pkg/net/rpc/jsonrpc/client.go
index 3fa8cbf08a..2194f21257 100644
--- a/src/pkg/net/rpc/jsonrpc/client.go
+++ b/src/pkg/net/rpc/jsonrpc/client.go
@@ -83,7 +83,7 @@ func (c *clientCodec) ReadResponseHeader(r *rpc.Response) error {
 
 	r.Error = ""
 	r.Seq = c.resp.Id
-	if c.resp.Error != nil {
+	if c.resp.Error != nil || c.resp.Result == nil {
 		x, ok := c.resp.Error.(string)
 		if !ok {
 			return fmt.Errorf("invalid error %v", c.resp.Error)

コアとなるコードの解説

src/pkg/net/rpc/jsonrpc/client.go の変更

	if c.resp.Error != nil || c.resp.Result == nil {
		x, ok := c.resp.Error.(string)
		if !ok {
			return fmt.Errorf("invalid error %v", c.resp.Error)
		}
		r.Error = x
	}

この変更は、clientCodec構造体のReadResponseHeaderメソッド内で行われています。このメソッドは、JSON-RPCサーバーからの応答ヘッダーを読み取り、rpc.Response構造体に解析する役割を担っています。

元のコードでは、if c.resp.Error != nilという条件のみでエラーをチェックしていました。これは、JSON-RPC応答に明示的なエラーフィールドが含まれている場合にのみ、それをエラーとして認識するという意味です。

しかし、この修正では条件がif c.resp.Error != nil || c.resp.Result == nilに変更されました。

  • c.resp.Error != nil: これは従来通り、応答にエラー情報が含まれている場合を捕捉します。
  • c.resp.Result == nil: これが追加された新しい条件です。これは、応答にerrorフィールドがない(nilである)にもかかわらず、resultフィールドもnilである場合を捕捉します。JSON-RPCの仕様では、成功応答にはresultフィールドが、エラー応答にはerrorフィールドが含まれるべきです。resulterrornilであるような応答は、通常、不正な形式と見なされます。

この変更により、クライアントは、resultnullであるような不正な応答を受け取った場合でも、それをエラーとして適切に処理し、後続のnilポインタデリファレンスを防ぐことができます。r.Error = xによって、rpc.Responseオブジェクトのエラーフィールドに適切なエラーメッセージが設定され、クライアントのCallメソッドにエラーが返されるようになります。

src/pkg/net/rpc/jsonrpc/all_test.go の変更

func TestMalformedOutput(t *testing.T) {
	cli, srv := net.Pipe()
	go srv.Write([]byte(`{"id":0,"result":null,"error":null}`))
	go ioutil.ReadAll(srv)

	client := NewClient(cli)
	defer client.Close()

	args := &Args{7, 8}
	reply := new(Reply)
	err := client.Call("Arith.Add", args, reply)
	if err == nil {
		t.Error("expected error")
	}
}

この新しいテストケースは、修正が正しく機能することを検証するために追加されました。

  1. net.Pipe()を使用して、クライアントとサーバー間のインメモリパイプを作成します。これにより、実際のネットワーク接続なしに通信をシミュレートできます。
  2. go srv.Write([]byte({"id":0,"result":null,"error":null})): サーバー側(srv)から、意図的にresulterrorの両方がnullである不正なJSON-RPC応答を書き込みます。これは、修正が対処しようとしているエッジケースを再現します。
  3. go ioutil.ReadAll(srv): サーバーからの残りの出力を読み捨てます。これは、パイプがブロックされないようにするための一般的なパターンです。
  4. client := NewClient(cli): クライアント側(cli)で新しいJSON-RPCクライアントを作成します。
  5. client.Call("Arith.Add", args, reply): クライアントがリモートメソッドArith.Addを呼び出します。この呼び出しは、サーバーから不正な応答を受け取ることを期待しています。
  6. if err == nil { t.Error("expected error") }: ここがテストの核心です。もしclient.Callがエラーを返さなかった場合(つまりerrnilだった場合)、それはバグが修正されていないことを意味します。テストは「エラーが期待された」と報告し、失敗します。

このテストは、resultnullである不正な応答が来た場合に、クライアントが適切にエラーを検出し、nilポインタデリファレンスを起こさずにエラーを返すことを保証します。

関連リンク

  • GitHubコミットページ: https://github.com/golang/go/commit/0d559f7b92c1425b9a78ff67933a9baaa0534928
  • Go Issue #5006: https://golang.org/issue/5006
  • Go Code Review (CL) 7691045: https://golang.org/cl/7691045

参考にした情報源リンク

  • Go言語の公式ドキュメント (net/rpc, net/rpc/jsonrpc): https://pkg.go.dev/net/rpc, https://pkg.go.dev/net/rpc/jsonrpc
  • JSON-RPC 2.0 Specification: https://www.jsonrpc.org/specification
  • Go言語におけるnilポインタデリファレンスに関する一般的な情報源 (例: Go言語の公式ブログ、技術記事など)
    • (具体的なURLは検索結果によるため、ここでは一般的な説明に留めます)
    • 例: "Go nil pointer dereference", "Go error handling" などのキーワードで検索。
    • Go言語の公式ブログやEffective Goなどが参考になります。
  • net.Pipe() の使用例に関する情報源 (例: Go言語のテストに関する記事)
    • (具体的なURLは検索結果によるため、ここでは一般的な説明に留めます)
    • 例: "Go net.Pipe example", "Go testing network" などのキーワードで検索。

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

このコミットは、Go言語の標準ライブラリ net/rpc/jsonrpc パッケージにおける、無効なRPC応答(リプライ)が原因で発生するnilポインタデリファレンス(nil pointer dereference)のバグを修正するものです。具体的には、JSON-RPCクライアントがサーバーからの応答を処理する際に、resultフィールドがnullである場合に発生する問題を解決し、関連するテストケースを追加しています。

コミット

  • コミットハッシュ: 0d559f7b92c1425b9a78ff67933a9baaa0534928
  • Author: Adrian Nos nos.adrian@gmail.com
  • Date: Tue Mar 12 11:45:56 2013 -0400
  • コミットメッセージ:
    net/rpc/jsonrpc: nil pointer deference on invalid reply.
    Fixes #5006.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/7691045
    

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

https://github.com/golang/go/commit/0d559f7b92c1425b9a78ff67933a9baaa0534928

元コミット内容

net/rpc/jsonrpc: nil pointer deference on invalid reply.
Fixes #5006.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7691045

変更の背景

この変更は、Go言語のIssue #5006「net/rpc/jsonrpc: nil pointer dereference on invalid reply」を修正するために行われました。このバグは、net/rpc/jsonrpcクライアントがJSON-RPCサーバーからの応答を処理する際に、特定の無効な応答形式(特にresultフィールドがnullである場合)を受け取ると、nilポインタデリファレンスを引き起こし、プログラムがクラッシュする可能性がありました。

JSON-RPCプロトコルでは、成功した応答にはresultフィールドが含まれ、エラー応答にはerrorフィールドが含まれます。しかし、プロトコルに厳密に従わない、あるいは予期せぬ形式の応答が返された場合、クライアント側で適切に処理されないと問題が発生します。このコミットは、このようなエッジケースを適切にハンドリングし、クライアントの堅牢性を向上させることを目的としています。

前提知識の解説

RPC (Remote Procedure Call)

RPCは、ネットワーク上の別のコンピュータにあるプログラムのサブルーチンや関数を、あたかもローカルにあるかのように呼び出すための技術です。これにより、分散システムにおけるプロセス間通信が容易になります。クライアントはリモートの関数を呼び出し、サーバーはその呼び出しを受け取って処理し、結果をクライアントに返します。

JSON-RPC

JSON-RPCは、RPCプロトコルの一種で、メッセージのエンコーディングにJSONを使用します。HTTPなどのトランスポートプロトコル上で動作することが多く、シンプルで人間が読みやすい形式が特徴です。JSON-RPCのメッセージは、jsonrpc(プロトコルバージョン)、method(呼び出すメソッド名)、params(引数)、id(リクエストID)などのフィールドで構成されます。応答メッセージには、result(成功時の結果)、error(エラー情報)、idが含まれます。

net/rpc パッケージ

Go言語の標準ライブラリ net/rpc は、RPCクライアントとサーバーを実装するためのフレームワークを提供します。これは、Goのencoding/gob形式をデフォルトのエンコーディングとして使用しますが、net/rpc/jsonrpcパッケージのように、他のエンコーディング(JSON)を使用するクライアント/サーバーを構築するためのインターフェースも提供しています。

net/rpc/jsonrpc パッケージ

net/rpcパッケージのサブパッケージであり、JSON-RPCプロトコルを使用してRPC通信を行うためのクライアントとサーバーの実装を提供します。このパッケージを使用することで、GoアプリケーションはJSON-RPCを話す他のシステムと容易に連携できます。

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

nilポインタデリファレンスは、プログラミングにおける一般的な実行時エラーの一つです。ポインタが何も指していない(nilまたはnull)状態で、そのポインタが指すメモリ領域にアクセスしようとすると発生します。Go言語では、nilポインタデリファレンスが発生すると、パニック(panic)が発生し、プログラムがクラッシュします。これは、プログラムの安定性にとって非常に危険な状態です。

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

Go言語では、エラーは戻り値として明示的に扱われます。関数は通常、最後の戻り値としてerror型の値を返します。エラーが発生しなかった場合はnilを返します。呼び出し元は、このerror値をチェックして、処理を続行するか、エラーを上位に伝播させるかを決定します。

技術的詳細

このコミットが修正する問題は、net/rpc/jsonrpcクライアントがサーバーからの応答を解析する際に、JSON-RPCの応答オブジェクトのresultフィールドがnullである場合に発生していました。

JSON-RPC 2.0の仕様では、成功応答はresultフィールドを持ち、エラー応答はerrorフィールドを持ちます。しかし、resultnullであるケースは、エラーではないが結果がない、あるいは特定の状況下で有効な応答として扱われることがあります。

元のコードでは、clientCodecReadResponseHeaderメソッド内で、c.resp.Error != nilの場合にのみエラー処理を行っていました。しかし、c.resp.Errornilであっても、c.resp.Resultnilである場合に、後続の処理でc.resp.Resultが指す値にアクセスしようとすると、nilポインタデリファレンスが発生する可能性がありました。

この修正では、client.goReadResponseHeaderメソッドにおいて、エラーチェックの条件にc.resp.Result == nilを追加することで、この問題を解決しています。これにより、errorフィールドがnilであってもresultフィールドがnilであるような無効な応答が来た場合に、明示的にエラーとして扱うようになります。

また、all_test.goTestMalformedOutputという新しいテストケースが追加されました。このテストは、意図的に{"id":0,"result":null,"error":null}という形式の不正なJSON-RPC応答をクライアントに送りつけ、クライアントがエラーを返すことを検証します。これにより、修正が正しく機能していることが確認されます。

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

--- a/src/pkg/net/rpc/jsonrpc/all_test.go
+++ b/src/pkg/net/rpc/jsonrpc/all_test.go
@@ -9,6 +9,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"io/ioutil"
 	"net"
 	"net/rpc"
 	"testing"
@@ -185,6 +186,22 @@ func TestMalformedInput(t *testing.T) {
 	ServeConn(srv)                 // must return, not loop
 }
 
+func TestMalformedOutput(t *testing.T) {
+	cli, srv := net.Pipe()
+	go srv.Write([]byte(`{"id":0,"result":null,"error":null}`))
+	go ioutil.ReadAll(srv)
+
+	client := NewClient(cli)
+	defer client.Close()
+
+	args := &Args{7, 8}
+	reply := new(Reply)
+	err := client.Call("Arith.Add", args, reply)
+	if err == nil {
+		t.Error("expected error")
+	}
+}
+
 func TestUnexpectedError(t *testing.T) {
 	cli, srv := myPipe()
 	go cli.PipeWriter.CloseWithError(errors.New("unexpected error!")) // reader will get this error
diff --git a/src/pkg/net/rpc/jsonrpc/client.go b/src/pkg/net/rpc/jsonrpc/client.go
index 3fa8cbf08a..2194f21257 100644
--- a/src/pkg/net/rpc/jsonrpc/client.go
+++ b/src/pkg/net/rpc/jsonrpc/client.go
@@ -83,7 +83,7 @@ func (c *clientCodec) ReadResponseHeader(r *rpc.Response) error {
 
 	r.Error = ""
 	r.Seq = c.resp.Id
-	if c.resp.Error != nil {
+	if c.resp.Error != nil || c.resp.Result == nil {
 		x, ok := c.resp.Error.(string)
 		if !ok {
 			return fmt.Errorf("invalid error %v", c.resp.Error)

コアとなるコードの解説

src/pkg/net/rpc/jsonrpc/client.go の変更

	if c.resp.Error != nil || c.resp.Result == nil {
		x, ok := c.resp.Error.(string)
		if !ok {
			return fmt.Errorf("invalid error %v", c.resp.Error)
		}
		r.Error = x
	}

この変更は、clientCodec構造体のReadResponseHeaderメソッド内で行われています。このメソッドは、JSON-RPCサーバーからの応答ヘッダーを読み取り、rpc.Response構造体に解析する役割を担っています。

元のコードでは、if c.resp.Error != nilという条件のみでエラーをチェックしていました。これは、JSON-RPC応答に明示的なエラーフィールドが含まれている場合にのみ、それをエラーとして認識するという意味です。

しかし、この修正では条件がif c.resp.Error != nil || c.resp.Result == nilに変更されました。

  • c.resp.Error != nil: これは従来通り、応答にエラー情報が含まれている場合を捕捉します。
  • c.resp.Result == nil: これが追加された新しい条件です。これは、応答にerrorフィールドがない(nilである)にもかかわらず、resultフィールドもnilである場合を捕捉します。JSON-RPCの仕様では、成功応答にはresultフィールドが、エラー応答にはerrorフィールドが含まれるべきです。resulterrornilであるような応答は、通常、不正な形式と見なされます。

この変更により、クライアントは、resultnullであるような不正な応答を受け取った場合でも、それをエラーとして適切に処理し、後続のnilポインタデリファレンスを防ぐことができます。r.Error = xによって、rpc.Responseオブジェクトのエラーフィールドに適切なエラーメッセージが設定され、クライアントのCallメソッドにエラーが返されるようになります。

src/pkg/net/rpc/jsonrpc/all_test.go の変更

func TestMalformedOutput(t *testing.T) {
	cli, srv := net.Pipe()
	go srv.Write([]byte(`{"id":0,"result":null,"error":null}`))
	go ioutil.ReadAll(srv)

	client := NewClient(cli)
	defer client.Close()

	args := &Args{7, 8}
	reply := new(Reply)
	err := client.Call("Arith.Add", args, reply)
	if err == nil {
		t.Error("expected error")
	}
}

この新しいテストケースは、修正が正しく機能することを検証するために追加されました。

  1. net.Pipe()を使用して、クライアントとサーバー間のインメモリパイプを作成します。これにより、実際のネットワーク接続なしに通信をシミュレートできます。
  2. go srv.Write([]byte({"id":0,"result":null,"error":null})): サーバー側(srv)から、意図的にresulterrorの両方がnullである不正なJSON-RPC応答を書き込みます。これは、修正が対処しようとしているエッジケースを再現します。
  3. go ioutil.ReadAll(srv): サーバーからの残りの出力を読み捨てます。これは、パイプがブロックされないようにするための一般的なパターンです。
  4. client := NewClient(cli): クライアント側(cli)で新しいJSON-RPCクライアントを作成します。
  5. client.Call("Arith.Add", args, reply): クライアントがリモートメソッドArith.Addを呼び出します。この呼び出しは、サーバーから不正な応答を受け取ることを期待しています。
  6. if err == nil { t.Error("expected error") }: ここがテストの核心です。もしclient.Callがエラーを返さなかった場合(つまりerrnilだった場合)、それはバグが修正されていないことを意味します。テストは「エラーが期待された」と報告し、失敗します。

このテストは、resultnullである不正な応答が来た場合に、クライアントが適切にエラーを検出し、nilポインタデリファレンスを起こさずにエラーを返すことを保証します。

関連リンク

  • GitHubコミットページ: https://github.com/golang/go/commit/0d559f7b92c1425b9a78ff67933a9baaa0534928
  • Go Issue #5006: https://golang.org/issue/5006
  • Go Code Review (CL) 7691045: https://golang.org/cl/7691045

参考にした情報源リンク

  • Go言語の公式ドキュメント (net/rpc, net/rpc/jsonrpc): https://pkg.go.dev/net/rpc, https://pkg.go.dev/net/rpc/jsonrpc
  • JSON-RPC 2.0 Specification: https://www.jsonrpc.org/specification
  • Go言語におけるnilポインタデリファレンスに関する一般的な情報源 (例: Go言語の公式ブログ、技術記事など)
    • (具体的なURLは検索結果によるため、ここでは一般的な説明に留めます)
    • 例: "Go nil pointer dereference", "Go error handling" などのキーワードで検索。
    • Go言語の公式ブログやEffective Goなどが参考になります。
  • net.Pipe() の使用例に関する情報源 (例: Go言語のテストに関する記事)
    • (具体的なURLは検索結果によるため、ここでは一般的な説明に留めます)
    • 例: "Go net.Pipe example", "Go testing network" などのキーワードで検索。