[インデックス 13721] ファイルの概要
このコミットは、Go言語の標準ライブラリである net/rpc/jsonrpc
パッケージにおいて、JSON-RPCリクエストに params
フィールドが存在しない場合に発生するクラッシュを修正するものです。具体的には、接続後の最初のリクエストで params
フィールドが欠落していると、c.req.Params
が nil
であるためにパニックが発生する問題(Issue #3848)に対処しています。
コミット
commit 3efc482190c9c2fa80cb0fc80d160624514652db
Author: Alexandru Moșoi <brtzsnr@gmail.com>
Date: Fri Aug 31 15:52:27 2012 -0400
net/rpc/jsonrpc: handles missing "params" in jsonrpc.
A crash happens in the first request in a connection
if "params" field is missing because c.req.Params is Nil.
Fixes #3848.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6446051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3efc482190c9c2fa80cb0fc80d160624514652db
元コミット内容
net/rpc/jsonrpc: handles missing "params" in jsonrpc.
A crash happens in the first request in a connection if "params" field is missing because c.req.Params is Nil.
Fixes #3848.
変更の背景
Go言語の net/rpc/jsonrpc
パッケージは、JSON形式でRPC(Remote Procedure Call)を実装するための機能を提供します。JSON-RPCプロトコルでは、クライアントからサーバーへのリクエストに method
、params
、id
などのフィールドが含まれます。params
フィールドは、呼び出すメソッドに渡す引数を指定するもので、JSON配列またはJSONオブジェクトの形式を取ります。
このコミットが修正する問題は、クライアントが params
フィールドを省略したJSON-RPCリクエストを送信した場合に発生していました。Goの net/rpc/jsonrpc
サーバーの実装において、受信したリクエストの params
フィールドが nil
であることを適切に処理していなかったため、c.req.Params
を参照しようとした際に nil
ポインタデリファレンスが発生し、サーバーがクラッシュするというバグが存在していました(Issue #3848)。これは、特に接続後の最初のリクエストで顕著に発生する可能性がありました。
この修正は、サーバーの堅牢性を高め、不正な形式のリクエスト(params
フィールドの欠落)に対しても安定して動作するようにすることを目的としています。
前提知識の解説
JSON-RPC
JSON-RPCは、JSON形式でデータをやり取りするリモートプロシージャコールプロトコルです。基本的なリクエストの構造は以下のようになります。
{
"jsonrpc": "2.0",
"method": "method_name",
"params": [param1, param2], // または {"key": "value"}
"id": 1 // リクエストID
}
jsonrpc
: プロトコルのバージョン(通常 "2.0")。method
: 呼び出すリモートメソッドの名前。params
: メソッドに渡す引数。これはJSON配列またはJSONオブジェクトのいずれかです。JSON-RPC 2.0の仕様では、params
フィールドはオプションとされています。id
: リクエストとレスポンスを関連付けるための識別子。
Goの net/rpc
パッケージ
Goの標準ライブラリ net/rpc
は、RPCクライアントとサーバーを構築するためのフレームワークを提供します。これは、ネットワーク越しにGoの構造体メソッドを呼び出すことを可能にします。net/rpc/jsonrpc
は、この net/rpc
の上に構築され、JSON形式でのデータエンコーディング/デコーディングを扱います。
nil
ポインタとパニック
Go言語では、ポインタが何も指していない状態を nil
と表現します。nil
ポインタをデリファレンス(つまり、nil
ポインタが指す値にアクセスしようとすること)すると、ランタイムパニックが発生し、プログラムが異常終了します。このコミットのバグは、まさにこの nil
ポインタデリファレンスによって引き起こされていました。
net.Pipe()
net.Pipe()
は、Goの net
パッケージで提供される関数で、インメモリの双方向コネクション(パイプ)を作成します。これは、ネットワークI/Oを伴うコード(この場合はRPCサーバーとクライアント間の通信)を、実際のネットワーク接続なしでテストする際に非常に便利です。net.Pipe()
は2つの net.Conn
インターフェースを実装するオブジェクトを返します。一方はクライアント側、もう一方はサーバー側として使用できます。
技術的詳細
このコミットの技術的詳細は、主に net/rpc/jsonrpc
パッケージ内の server.go
と all_test.go
の変更に集約されます。
server.go
の変更
-
エラー変数の追加:
var errMissingParams = errors.New("jsonrpc: request body missing params")
params
フィールドが欠落している場合に返す特定のエラーが定義されました。これにより、エラーの種類を明確に識別できるようになります。 -
serverRequest.reset()
メソッドの変更:serverRequest
構造体は、受信したJSON-RPCリクエストの情報を保持します。reset()
メソッドは、リクエスト処理後にこの構造体を再利用するために初期化する役割を担います。 変更前は、r.Params
とr.Id
がnil
でない場合にスライス操作 ([0:0]
) を行っていました。しかし、もしこれらが既にnil
であった場合、nil
スライスに対する操作はパニックを引き起こす可能性があります。 変更後は、単純にr.Params = nil
とr.Id = nil
を代入することで、安全かつ確実にこれらのフィールドを初期化するように修正されました。これにより、nil
ポインタデリファレンスのリスクが排除されます。 -
ReadRequestBody
メソッドでのparams
フィールドのチェック:ReadRequestBody
メソッドは、受信したリクエストボディを解析し、RPCメソッドの引数として使用するデータを準備します。 このメソッドの冒頭に以下のチェックが追加されました。if c.req.Params == nil { return errMissingParams }
これにより、
c.req.Params
がnil
である(つまり、JSON-RPCリクエストにparams
フィールドがなかった)場合、後続の処理でnil
ポインタデリファレンスが発生する前に、errMissingParams
エラーを返して早期に処理を終了するようになりました。これは、問題の根本原因に対する直接的な修正です。
all_test.go
の変更
テストファイルには、新しいテストケースと既存テストの改善が加えられました。
-
ArithAddResp
構造体の追加:type ArithAddResp struct { Id interface{} `json:"id"` Result Reply `json:"result"` Error interface{} `json:"error"` }
これは、JSON-RPCレスポンスをアンマーシャルするための新しい構造体です。以前は
addResp
という匿名構造体が使われていましたが、新しいテストケースで再利用できるように名前付き構造体として定義されました。 -
TestServerNoParams
テストケースの追加: この新しいテストは、params
フィールドが欠落したJSON-RPCリクエストをサーバーに送信するシナリオをシミュレートします。fmt.Fprintf(cli, `{"method": "Arith.Add", "id": "123"}`)
このリクエストには
params
が含まれていません。テストは、サーバーがエラーを返すことを期待し、resp.Error == nil
でないことを確認します。これにより、params
欠落時のエラーハンドリングが正しく機能するかを検証します。 -
TestServerEmptyMessage
テストケースの追加: このテストは、空のJSONメッセージ{}
をサーバーに送信するシナリオをシミュレートします。fmt.Fprintf(cli, "{}")
空のメッセージも不正なリクエストであるため、サーバーがエラーを返すことを期待します。これにより、サーバーが予期せぬ入力に対しても堅牢であることを確認します。
-
TestServer
の変更: 既存のTestServer
関数内で、以前は匿名構造体addResp
を使用していましたが、新しく定義されたArithAddResp
に置き換えられました。また、以前TestServer
の最後にあった空メッセージのテストロジックは、新しく追加されたTestServerEmptyMessage
に移動され、より明確なテスト構造になりました。
これらの変更により、net/rpc/jsonrpc
サーバーは params
フィールドが欠落したリクエストや空のメッセージに対して適切にエラーを返し、クラッシュを防ぐことができるようになりました。
コアとなるコードの変更箇所
src/pkg/net/rpc/jsonrpc/server.go
--- a/src/pkg/net/rpc/jsonrpc/server.go
+++ b/src/pkg/net/rpc/jsonrpc/server.go
@@ -12,6 +12,8 @@ import (
"sync"
)
+var errMissingParams = errors.New("jsonrpc: request body missing params")
+
type serverCodec struct {
dec *json.Decoder // for reading JSON values
enc *json.Encoder // for writing JSON values
@@ -50,12 +52,8 @@ type serverRequest struct {
func (r *serverRequest) reset() {
r.Method = ""
- if r.Params != nil {
- *r.Params = (*r.Params)[0:0]
- }
- if r.Id != nil {
- *r.Id = (*r.Id)[0:0]
- }
+ r.Params = nil
+ r.Id = nil
}
type serverResponse struct {
@@ -88,6 +86,9 @@ func (c *serverCodec) ReadRequestBody(x interface{}) error {
if x == nil {
return nil
}
+ if c.req.Params == nil {
+ return errMissingParams
+ }
// JSON params is array value.
// RPC params is struct.
// Unmarshal into array containing struct for now.
src/pkg/net/rpc/jsonrpc/all_test.go
--- a/src/pkg/net/rpc/jsonrpc/all_test.go
+++ b/src/pkg/net/rpc/jsonrpc/all_test.go
@@ -24,6 +24,12 @@ type Reply struct {
type Arith int
+type ArithAddResp struct {
+ Id interface{} `json:"id"`
+ Result Reply `json:"result"`
+ Error interface{} `json:"error"`
+}
+
func (t *Arith) Add(args *Args, reply *Reply) error {
reply.C = args.A + args.B
return nil
@@ -50,13 +56,39 @@ func init() {
rpc.Register(new(Arith))
}
-func TestServer(t *testing.T) {
- type addResp struct {
- Id interface{} `json:"id"`
- Result Reply `json:"result"`
- Error interface{} `json:"error"`
+func TestServerNoParams(t *testing.T) {
+ cli, srv := net.Pipe()
+ defer cli.Close()
+ go ServeConn(srv)
+ dec := json.NewDecoder(cli)
+
+ fmt.Fprintf(cli, `{"method": "Arith.Add", "id": "123"}`)\n+\tvar resp ArithAddResp
+ if err := dec.Decode(&resp); err != nil {
+ t.Fatalf("Decode after no params: %s", err)
}
+ if resp.Error == nil {
+ t.Fatalf("Expected error, got nil")
+ }
+}
+
+func TestServerEmptyMessage(t *testing.T) {
+ cli, srv := net.Pipe()
+ defer cli.Close()
+ go ServeConn(srv)
+ dec := json.NewDecoder(cli)
+
+ fmt.Fprintf(cli, "{}")
+ var resp ArithAddResp
+ if err := dec.Decode(&resp); err != nil {
+ t.Fatalf("Decode after empty: %s", err)
+ }
+ if resp.Error == nil {
+ t.Fatalf("Expected error, got nil")
+ }
+}
+
+func TestServer(t *testing.T) {
cli, srv := net.Pipe()
defer cli.Close()
@@ -65,7 +97,7 @@ func TestServer(t *testing.T) {
// Send hand-coded requests to server, parse responses.
for i := 0; i < 10; i++ {
fmt.Fprintf(cli, `{"method": "Arith.Add", "id": "\u%04d", "params": [{"A": %d, "B": %d}]}`, i, i, i+1)
- var resp addResp
+ var resp ArithAddResp
err := dec.Decode(&resp)
if err != nil {
t.Fatalf("Decode: %s", err)
@@ -80,15 +112,6 @@ func TestServer(t *testing.T) {
if resp.Result.C != i+(i+1) {
t.Fatalf("resp: bad result: %d+%d=%d", i, i+1, resp.Result.C)
}
- }
-
- fmt.Fprintf(cli, "{}\\n")
- var resp addResp
- if err := dec.Decode(&resp); err != nil {
- t.Fatalf("Decode after empty: %s", err)
- }
- if resp.Error == nil {
- t.Fatalf("Expected error, got nil")
}
}
コアとなるコードの解説
このコミットの核心は、server.go
の ReadRequestBody
メソッドにおける nil
チェックと、serverRequest.reset()
メソッドの簡素化です。
ReadRequestBody
メソッドは、JSON-RPCリクエストの params
部分をGoの構造体にデコードする役割を担います。以前の実装では、params
フィールドがJSONリクエストに存在しない場合、c.req.Params
が nil
のままになり、その後の処理でこの nil
ポインタをデリファレンスしようとするとパニックが発生していました。
修正後のコードでは、ReadRequestBody
の冒頭で if c.req.Params == nil
というチェックが追加されました。これにより、params
が欠落しているリクエストを検出した場合、すぐに errMissingParams
を返して処理を中断します。これにより、nil
ポインタデリファレンスによるクラッシュを未然に防ぎ、クライアントに対して適切なエラーレスポンスを返すことができるようになります。
また、serverRequest.reset()
メソッドの変更は、serverRequest
構造体の再利用時の安全性を高めます。以前は r.Params
や r.Id
が nil
でない場合にスライスを再初期化しようとしていましたが、これが nil
ポインタに対して実行されるとパニックの原因となる可能性がありました。新しい実装では、これらのフィールドを直接 nil
に設定することで、常に安全に初期化が行われるようになります。
テストファイル all_test.go
の変更は、これらの修正が正しく機能することを保証するためのものです。特に TestServerNoParams
と TestServerEmptyMessage
は、params
フィールドが欠落しているケースや、リクエストが空であるケースといった、以前クラッシュを引き起こしていた具体的なシナリオを網羅しています。これらのテストが追加されたことで、将来的な回帰を防ぎ、サーバーの堅牢性を維持するのに役立ちます。
関連リンク
- Go Issue #3848: https://code.google.com/p/go/issues/detail?id=3848 (古いGoogle Codeのリンクですが、コミットメッセージに記載されています)
- Go CL 6446051: https://golang.org/cl/6446051 (Goのコードレビューシステムへのリンク)
参考にした情報源リンク
- JSON-RPC 2.0 Specification: https://www.jsonrpc.org/specification
- Go
net/rpc
documentation: https://pkg.go.dev/net/rpc - Go
encoding/json
documentation: https://pkg.go.dev/encoding/json - Go
net
package documentation (forPipe()
): https://pkg.go.dev/net