[インデックス 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/rpcdocumentation: https://pkg.go.dev/net/rpc - Go
encoding/jsondocumentation: https://pkg.go.dev/encoding/json - Go
netpackage documentation (forPipe()): https://pkg.go.dev/net