Skip to content

Commit f4b2d34

Browse files
authored
Merge pull request #34 from filecoin-project/schomatis/fix/limit-request-size
Limit request size
2 parents 0b1396b + 27f5137 commit f4b2d34

File tree

3 files changed

+48
-5
lines changed

3 files changed

+48
-5
lines changed

handler.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ type request struct {
4343
Meta map[string]string `json:"meta,omitempty"`
4444
}
4545

46+
// Limit request size. Ideally this limit should be specific for each field
47+
// in the JSON request but as a simple defensive measure we just limit the
48+
// entire HTTP body.
49+
// Configured by WithMaxRequestSize.
50+
const DEFAULT_MAX_REQUEST_SIZE = 100 << 20 // 100 MiB
51+
4652
type respError struct {
4753
Code int `json:"code"`
4854
Message string `json:"message"`
@@ -111,7 +117,33 @@ func (s *RPCServer) handleReader(ctx context.Context, r io.Reader, w io.Writer,
111117
}
112118

113119
var req request
114-
if err := json.NewDecoder(r).Decode(&req); err != nil {
120+
// We read the entire request upfront in a buffer to be able to tell if the
121+
// client sent more than maxRequestSize and report it back as an explicit error,
122+
// instead of just silently truncating it and reporting a more vague parsing
123+
// error.
124+
bufferedRequest := new(bytes.Buffer)
125+
// We use LimitReader to enforce maxRequestSize. Since it won't return an
126+
// EOF we can't actually know if the client sent more than the maximum or
127+
// not, so we read one byte more over the limit to explicitly query that.
128+
// FIXME: Maybe there's a cleaner way to do this.
129+
reqSize, err := bufferedRequest.ReadFrom(io.LimitReader(r, s.maxRequestSize+1))
130+
if err != nil {
131+
// ReadFrom will discard EOF so any error here is unexpected and should
132+
// be reported.
133+
rpcError(wf, &req, rpcParseError, xerrors.Errorf("reading request: %w", err))
134+
return
135+
}
136+
if reqSize > s.maxRequestSize {
137+
rpcError(wf, &req, rpcParseError,
138+
// rpcParseError is the closest we have from the standard errors defined
139+
// in [jsonrpc spec](https://www.jsonrpc.org/specification#error_object)
140+
// to report the maximum limit.
141+
xerrors.Errorf("request bigger than maximum %d allowed",
142+
s.maxRequestSize))
143+
return
144+
}
145+
146+
if err := json.NewDecoder(bufferedRequest).Decode(&req); err != nil {
115147
rpcError(wf, &req, rpcParseError, xerrors.Errorf("unmarshaling request: %w", err))
116148
return
117149
}

options_server.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ import (
88
type ParamDecoder func(ctx context.Context, json []byte) (reflect.Value, error)
99

1010
type ServerConfig struct {
11-
paramDecoders map[reflect.Type]ParamDecoder
11+
paramDecoders map[reflect.Type]ParamDecoder
12+
maxRequestSize int64
1213
}
1314

1415
type ServerOption func(c *ServerConfig)
1516

1617
func defaultServerConfig() ServerConfig {
1718
return ServerConfig{
18-
paramDecoders: map[reflect.Type]ParamDecoder{},
19+
paramDecoders: map[reflect.Type]ParamDecoder{},
20+
maxRequestSize: DEFAULT_MAX_REQUEST_SIZE,
1921
}
2022
}
2123

@@ -24,3 +26,9 @@ func WithParamDecoder(t interface{}, decoder ParamDecoder) ServerOption {
2426
c.paramDecoders[reflect.TypeOf(t).Elem()] = decoder
2527
}
2628
}
29+
30+
func WithMaxRequestSize(max int64) ServerOption {
31+
return func(c *ServerConfig) {
32+
c.maxRequestSize = max
33+
}
34+
}

server.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ type RPCServer struct {
2222
methods map[string]rpcHandler
2323

2424
paramDecoders map[reflect.Type]ParamDecoder
25+
26+
maxRequestSize int64
2527
}
2628

2729
// NewServer creates new RPCServer instance
@@ -32,8 +34,9 @@ func NewServer(opts ...ServerOption) *RPCServer {
3234
}
3335

3436
return &RPCServer{
35-
methods: map[string]rpcHandler{},
36-
paramDecoders: config.paramDecoders,
37+
methods: map[string]rpcHandler{},
38+
paramDecoders: config.paramDecoders,
39+
maxRequestSize: config.maxRequestSize,
3740
}
3841
}
3942

0 commit comments

Comments
 (0)