test: fuzz bounded protocol parsers
This commit is contained in:
@@ -42,6 +42,35 @@ func TestDecodeEnvelopeBoundaries_HP_PROTO_01(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzDecodeEnvelopeBounded_SEC_PROTO_01(f *testing.F) {
|
||||
valid, err := proto.Marshal(&rvboxv1.AgentEnvelope{Payload: &rvboxv1.AgentEnvelope_ClientHello{ClientHello: validHello()}})
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
f.Add(valid)
|
||||
f.Add([]byte{0xff})
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
limits := DefaultLimits()
|
||||
if len(data) > int(limits.MaxEnvelopeBytes)+1 {
|
||||
return
|
||||
}
|
||||
_, _ = DecodeEnvelope(data, limits, rvboxv1.Platform_PLATFORM_WINDOWS)
|
||||
})
|
||||
}
|
||||
|
||||
func FuzzDecodeOutputChunkBounded_SEC_PROTO_02(f *testing.F) {
|
||||
f.Add([]byte("plain"), uint8(rvboxv1.Compression_COMPRESSION_NONE), uint64(5))
|
||||
f.Add([]byte{0x28, 0xb5, 0x2f, 0xfd}, uint8(rvboxv1.Compression_COMPRESSION_ZSTD), uint64(1))
|
||||
f.Fuzz(func(t *testing.T, data []byte, compression uint8, rawBytes uint64) {
|
||||
const limit = uint64(64 << 10)
|
||||
if len(data) > int(limit) {
|
||||
return
|
||||
}
|
||||
chunk := &rvboxv1.OutputChunk{Stream: rvboxv1.StreamKind_STREAM_STDOUT, Compression: rvboxv1.Compression(compression % 3), CompressedSize: uint64(len(data)), UncompressedSize: rawBytes % (limit + 2), Data: data}
|
||||
_, _ = DecodeOutputChunk(chunk, limit)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnvelopeSessionFencingShape_BH_SES_01(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -55,20 +55,9 @@ func (handler *JSONRPCHandler) ServeHTTP(response http.ResponseWriter, request *
|
||||
handler.writeRPCError(response, nil, jsonRPCParseError, "could not read request", nil)
|
||||
return
|
||||
}
|
||||
if int64(len(body)) > handler.MaxBody {
|
||||
handler.writeRPCError(response, nil, jsonRPCInvalid, "request body exceeds limit", nil)
|
||||
return
|
||||
}
|
||||
var envelope jsonRPCRequest
|
||||
decoder := json.NewDecoder(bytes.NewReader(body))
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(&envelope); err != nil {
|
||||
handler.writeRPCError(response, nil, jsonRPCParseError, "invalid JSON", nil)
|
||||
return
|
||||
}
|
||||
var trailing any
|
||||
if err := decoder.Decode(&trailing); err != io.EOF || envelope.JSONRPC != jsonRPCVersion || envelope.Method == "" || len(envelope.ID) == 0 || bytes.Equal(bytes.TrimSpace(envelope.ID), []byte("null")) {
|
||||
handler.writeRPCError(response, nil, jsonRPCInvalid, "invalid JSON-RPC request", nil)
|
||||
envelope, errorCode, errorMessage := decodeJSONRPCRequest(body, handler.MaxBody)
|
||||
if errorCode != 0 {
|
||||
handler.writeRPCError(response, nil, errorCode, errorMessage, nil)
|
||||
return
|
||||
}
|
||||
result, callErr := handler.call(request.Context(), envelope.Method, envelope.Params)
|
||||
@@ -87,6 +76,29 @@ type jsonRPCRequest struct {
|
||||
Params json.RawMessage `json:"params"`
|
||||
}
|
||||
|
||||
// decodeJSONRPCRequest keeps the hostile JSON boundary independently bounded
|
||||
// and fuzzable. The returned code/message are the externally stable JSON-RPC
|
||||
// parse or invalid-request result; callers must not inspect partial fields.
|
||||
func decodeJSONRPCRequest(body []byte, maxBody int64) (jsonRPCRequest, int, string) {
|
||||
if maxBody <= 0 {
|
||||
maxBody = defaultJSONRPCBody
|
||||
}
|
||||
if int64(len(body)) > maxBody {
|
||||
return jsonRPCRequest{}, jsonRPCInvalid, "request body exceeds limit"
|
||||
}
|
||||
var envelope jsonRPCRequest
|
||||
decoder := json.NewDecoder(bytes.NewReader(body))
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(&envelope); err != nil {
|
||||
return jsonRPCRequest{}, jsonRPCParseError, "invalid JSON"
|
||||
}
|
||||
var trailing any
|
||||
if err := decoder.Decode(&trailing); err != io.EOF || envelope.JSONRPC != jsonRPCVersion || envelope.Method == "" || len(envelope.ID) == 0 || bytes.Equal(bytes.TrimSpace(envelope.ID), []byte("null")) {
|
||||
return jsonRPCRequest{}, jsonRPCInvalid, "invalid JSON-RPC request"
|
||||
}
|
||||
return envelope, 0, ""
|
||||
}
|
||||
|
||||
type jsonRPCResponse struct {
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
ID json.RawMessage `json:"id"`
|
||||
|
||||
@@ -95,3 +95,18 @@ func TestJSONRPCRejectsOversizeAndMalformedRequests_BH_CTL_16(t *testing.T) {
|
||||
t.Fatalf("malformed response = %s", malformedBody)
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzDecodeJSONRPCRequestBounded_SEC_CTL_01(f *testing.F) {
|
||||
f.Add([]byte(`{"jsonrpc":"2.0","id":1,"method":"listClients","params":{}}`))
|
||||
f.Add([]byte(`{"jsonrpc":"2.0","id":null,"method":"listClients"}`))
|
||||
f.Add([]byte(`{"jsonrpc":"2.0","id":1,"method":"listClients"}{}`))
|
||||
f.Fuzz(func(t *testing.T, body []byte) {
|
||||
// Keep fuzzing at the same independently enforced boundary as the
|
||||
// production handler rather than allowing a corpus entry to allocate
|
||||
// unbounded JSON decoder state.
|
||||
if len(body) > 64<<10 {
|
||||
return
|
||||
}
|
||||
_, _, _ = decodeJSONRPCRequest(body, 64<<10)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user