From 2f6ba1fded4283b6d54054ee4dca13e765785535 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 23 Nov 2023 11:43:47 +0300 Subject: [PATCH 1/4] rpcsrv: define GetSignersWithWitnesses on a pointer Nil receiver can be properly handled and all other `Param`'s mathods are defined on a pointer. Signed-off-by: Anna Shaleva --- pkg/services/rpcsrv/params/param.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/rpcsrv/params/param.go b/pkg/services/rpcsrv/params/param.go index 29d390648..3e07f22f4 100644 --- a/pkg/services/rpcsrv/params/param.go +++ b/pkg/services/rpcsrv/params/param.go @@ -394,7 +394,7 @@ func (p *Param) GetSignerWithWitness() (neorpc.SignerWithWitness, error) { // GetSignersWithWitnesses returns a slice of SignerWithWitness with CalledByEntry // scope from an array of Uint160 or an array of serialized transaction.Signer stored // in the parameter. -func (p Param) GetSignersWithWitnesses() ([]transaction.Signer, []transaction.Witness, error) { +func (p *Param) GetSignersWithWitnesses() ([]transaction.Signer, []transaction.Witness, error) { hashes, err := p.GetArray() if err != nil { return nil, nil, err From 22c654b2000ee1b4552e74a5ad6f39a75d6b9dab Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 23 Nov 2023 12:12:34 +0300 Subject: [PATCH 2/4] neorpc: restrict maximum subitems number in SignerWithWitness Restrict the number of Rules, Contracts and Groups. A part of #3131. Signed-off-by: Anna Shaleva --- pkg/neorpc/types.go | 9 +++++++ pkg/neorpc/types_test.go | 52 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/pkg/neorpc/types.go b/pkg/neorpc/types.go index 93e60e6a1..790d27341 100644 --- a/pkg/neorpc/types.go +++ b/pkg/neorpc/types.go @@ -115,6 +115,15 @@ func (s *SignerWithWitness) UnmarshalJSON(data []byte) error { if err != nil { return fmt.Errorf("not a signer: %w", err) } + if len(aux.AllowedContracts) > transaction.MaxAttributes { + return fmt.Errorf("invalid number of AllowedContracts: got %d, allowed %d at max", len(aux.AllowedContracts), transaction.MaxAttributes) + } + if len(aux.AllowedGroups) > transaction.MaxAttributes { + return fmt.Errorf("invalid number of AllowedGroups: got %d, allowed %d at max", len(aux.AllowedGroups), transaction.MaxAttributes) + } + if len(aux.Rules) > transaction.MaxAttributes { + return fmt.Errorf("invalid number of Rules: got %d, allowed %d at max", len(aux.Rules), transaction.MaxAttributes) + } acc, err := util.Uint160DecodeStringLE(strings.TrimPrefix(aux.Account, "0x")) if err != nil { acc, err = address.StringToUint160(aux.Account) diff --git a/pkg/neorpc/types_test.go b/pkg/neorpc/types_test.go index f0c99ea23..c817bc1e8 100644 --- a/pkg/neorpc/types_test.go +++ b/pkg/neorpc/types_test.go @@ -2,10 +2,12 @@ package neorpc import ( "encoding/json" + "fmt" "testing" "github.com/nspcc-dev/neo-go/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/stretchr/testify/require" ) @@ -37,4 +39,54 @@ func TestSignerWithWitnessMarshalUnmarshalJSON(t *testing.T) { actual, err := json.Marshal(s) require.NoError(t, err) require.Equal(t, expected, string(actual)) + + t.Run("subitems overflow", func(t *testing.T) { + checkSubitems := func(t *testing.T, bad any) { + data, err := json.Marshal(bad) + require.NoError(t, err) + err = json.Unmarshal(data, &SignerWithWitness{}) + + require.Error(t, err) + require.Contains(t, err.Error(), fmt.Sprintf("got %d, allowed %d at max", transaction.MaxAttributes+1, transaction.MaxAttributes)) + } + + t.Run("groups", func(t *testing.T) { + pk, err := keys.NewPrivateKey() + require.NoError(t, err) + bad := &SignerWithWitness{ + Signer: transaction.Signer{ + AllowedGroups: make([]*keys.PublicKey, transaction.MaxAttributes+1), + }, + } + for i := range bad.AllowedGroups { + bad.AllowedGroups[i] = pk.PublicKey() + } + + checkSubitems(t, bad) + }) + t.Run("contracts", func(t *testing.T) { + bad := &SignerWithWitness{ + Signer: transaction.Signer{ + AllowedContracts: make([]util.Uint160, transaction.MaxAttributes+1), + }, + } + + checkSubitems(t, bad) + }) + t.Run("rules", func(t *testing.T) { + bad := &SignerWithWitness{ + Signer: transaction.Signer{ + Rules: make([]transaction.WitnessRule, transaction.MaxAttributes+1), + }, + } + for i := range bad.Rules { + bad.Rules[i] = transaction.WitnessRule{ + Action: transaction.WitnessAllow, + Condition: &transaction.ConditionScriptHash{}, + } + } + + checkSubitems(t, bad) + }) + }) } From 802d8d24b9f523818d12037dbc485c47d578702d Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 23 Nov 2023 13:23:22 +0300 Subject: [PATCH 3/4] config: add MaxRequestBodySize RPC configuration option A part of #3131, follow the https://github.com/neo-project/neo-modules/pull/827. Signed-off-by: Anna Shaleva --- docs/node-configuration.md | 3 +++ pkg/config/config.go | 3 +++ pkg/config/rpc_config.go | 1 + pkg/services/rpcsrv/server.go | 6 ++++++ 4 files changed, 13 insertions(+) diff --git a/docs/node-configuration.md b/docs/node-configuration.md index d80ed5267..3648108fd 100644 --- a/docs/node-configuration.md +++ b/docs/node-configuration.md @@ -187,6 +187,7 @@ RPC: MaxFindResultItems: 100 MaxFindStoragePageSize: 50 MaxNEP11Tokens: 100 + MaxRequestBodyBytes: 5242880 MaxWebSocketClients: 64 SessionEnabled: false SessionExpirationTime: 15 @@ -225,6 +226,8 @@ where: - `MaxFindStoragePageSize` - the maximum number of elements for `findstorage` response per single page. - `MaxNEP11Tokens` - limit for the number of tokens returned from `getnep11balances` call. +- `MaxRequestBodyBytes` - the maximum allowed HTTP request body size in bytes + (5MB by default). - `MaxWebSocketClients` - the maximum simultaneous websocket client connection number (64 by default). Attempts to establish additional connections will lead to websocket handshake failures. Use "-1" to disable websocket diff --git a/pkg/config/config.go b/pkg/config/config.go index d30a30e61..4faf31a98 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -30,6 +30,9 @@ const ( // DefaultMaxNEP11Tokens is the default maximum number of resulting NEP11 tokens // that can be traversed by `getnep11balances` JSON-RPC handler. DefaultMaxNEP11Tokens = 100 + // DefaultMaxRequestBodyBytes is the default maximum allowed size of HTTP + // request body in bytes. + DefaultMaxRequestBodyBytes = 5 * 1024 * 1024 ) // Version is the version of the node, set at the build time. diff --git a/pkg/config/rpc_config.go b/pkg/config/rpc_config.go index bd9d0acdd..b0c62bf3a 100644 --- a/pkg/config/rpc_config.go +++ b/pkg/config/rpc_config.go @@ -16,6 +16,7 @@ type ( MaxFindResultItems int `yaml:"MaxFindResultItems"` MaxFindStorageResultItems int `yaml:"MaxFindStoragePageSize"` MaxNEP11Tokens int `yaml:"MaxNEP11Tokens"` + MaxRequestBodyBytes int `yaml:"MaxRequestBodyBytes"` MaxWebSocketClients int `yaml:"MaxWebSocketClients"` SessionEnabled bool `yaml:"SessionEnabled"` SessionExpirationTime int `yaml:"SessionExpirationTime"` diff --git a/pkg/services/rpcsrv/server.go b/pkg/services/rpcsrv/server.go index bb54fc4a3..2ec692cb6 100644 --- a/pkg/services/rpcsrv/server.go +++ b/pkg/services/rpcsrv/server.go @@ -313,6 +313,10 @@ func New(chain Ledger, conf config.RPC, coreServer *network.Server, conf.MaxNEP11Tokens = config.DefaultMaxNEP11Tokens log.Info("MaxNEP11Tokens is not set or wrong, setting default value", zap.Int("MaxNEP11Tokens", config.DefaultMaxNEP11Tokens)) } + if conf.MaxRequestBodyBytes <= 0 { + conf.MaxRequestBodyBytes = config.DefaultMaxRequestBodyBytes + log.Info("MaxRequestBodyBytes is not set or wong, setting default value", zap.Int("MaxRequestBodyBytes", config.DefaultMaxRequestBodyBytes)) + } if conf.MaxWebSocketClients == 0 { conf.MaxWebSocketClients = defaultMaxWebSocketClients log.Info("MaxWebSocketClients is not set or wrong, setting default value", zap.Int("MaxWebSocketClients", defaultMaxWebSocketClients)) @@ -474,6 +478,8 @@ func (s *Server) SetOracleHandler(orc OracleHandler) { } func (s *Server) handleHTTPRequest(w http.ResponseWriter, httpRequest *http.Request) { + // Restrict request body before further processing. + httpRequest.Body = http.MaxBytesReader(w, httpRequest.Body, int64(s.config.MaxRequestBodyBytes)) req := params.NewRequest() if httpRequest.URL.Path == "/ws" && httpRequest.Method == "GET" { From d511f6e5a9502f49c8941e1da959e64a263497ab Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 23 Nov 2023 13:39:24 +0300 Subject: [PATCH 4/4] confi: add MaxRequestHeaderBytes RPC configuration option A part of #3131, follow the notion of https://github.com/neo-project/neo-modules/pull/827, but don't restrict request line size due to https://github.com/golang/go/issues/15494. Signed-off-by: Anna Shaleva --- docs/node-configuration.md | 3 +++ pkg/config/config.go | 4 ++++ pkg/config/rpc_config.go | 1 + pkg/services/rpcsrv/server.go | 45 ++++++++++++++++++++--------------- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/docs/node-configuration.md b/docs/node-configuration.md index 3648108fd..1c14bc76a 100644 --- a/docs/node-configuration.md +++ b/docs/node-configuration.md @@ -188,6 +188,7 @@ RPC: MaxFindStoragePageSize: 50 MaxNEP11Tokens: 100 MaxRequestBodyBytes: 5242880 + MaxRequestHeaderBytes: 1048576 MaxWebSocketClients: 64 SessionEnabled: false SessionExpirationTime: 15 @@ -228,6 +229,8 @@ where: `getnep11balances` call. - `MaxRequestBodyBytes` - the maximum allowed HTTP request body size in bytes (5MB by default). +- `MaxRequestHeaderBytes` - the maximum allowed HTTP request header size in bytes + (1MB by default). - `MaxWebSocketClients` - the maximum simultaneous websocket client connection number (64 by default). Attempts to establish additional connections will lead to websocket handshake failures. Use "-1" to disable websocket diff --git a/pkg/config/config.go b/pkg/config/config.go index 4faf31a98..cd2c157c3 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -3,6 +3,7 @@ package config import ( "bytes" "fmt" + "net/http" "os" "time" @@ -33,6 +34,9 @@ const ( // DefaultMaxRequestBodyBytes is the default maximum allowed size of HTTP // request body in bytes. DefaultMaxRequestBodyBytes = 5 * 1024 * 1024 + // DefaultMaxRequestHeaderBytes is the maximum permitted size of the headers + // in an HTTP request. + DefaultMaxRequestHeaderBytes = http.DefaultMaxHeaderBytes ) // Version is the version of the node, set at the build time. diff --git a/pkg/config/rpc_config.go b/pkg/config/rpc_config.go index b0c62bf3a..c9b0a1630 100644 --- a/pkg/config/rpc_config.go +++ b/pkg/config/rpc_config.go @@ -17,6 +17,7 @@ type ( MaxFindStorageResultItems int `yaml:"MaxFindStoragePageSize"` MaxNEP11Tokens int `yaml:"MaxNEP11Tokens"` MaxRequestBodyBytes int `yaml:"MaxRequestBodyBytes"` + MaxRequestHeaderBytes int `yaml:"MaxRequestHeaderBytes"` MaxWebSocketClients int `yaml:"MaxWebSocketClients"` SessionEnabled bool `yaml:"SessionEnabled"` SessionExpirationTime int `yaml:"SessionExpirationTime"` diff --git a/pkg/services/rpcsrv/server.go b/pkg/services/rpcsrv/server.go index 2ec692cb6..6e7a6ff55 100644 --- a/pkg/services/rpcsrv/server.go +++ b/pkg/services/rpcsrv/server.go @@ -267,25 +267,6 @@ var rpcWsHandlers = map[string]func(*Server, params.Params, *subscriber) (any, * // untyped nil or non-nil structure implementing OracleHandler interface. func New(chain Ledger, conf config.RPC, coreServer *network.Server, orc OracleHandler, log *zap.Logger, errChan chan<- error) Server { - addrs := conf.Addresses - httpServers := make([]*http.Server, len(addrs)) - for i, addr := range addrs { - httpServers[i] = &http.Server{ - Addr: addr, - } - } - - var tlsServers []*http.Server - if cfg := conf.TLSConfig; cfg.Enabled { - addrs := cfg.Addresses - tlsServers = make([]*http.Server, len(addrs)) - for i, addr := range addrs { - tlsServers[i] = &http.Server{ - Addr: addr, - } - } - } - protoCfg := chain.GetConfig().ProtocolConfiguration if conf.SessionEnabled { if conf.SessionExpirationTime <= 0 { @@ -317,6 +298,10 @@ func New(chain Ledger, conf config.RPC, coreServer *network.Server, conf.MaxRequestBodyBytes = config.DefaultMaxRequestBodyBytes log.Info("MaxRequestBodyBytes is not set or wong, setting default value", zap.Int("MaxRequestBodyBytes", config.DefaultMaxRequestBodyBytes)) } + if conf.MaxRequestHeaderBytes <= 0 { + conf.MaxRequestHeaderBytes = config.DefaultMaxRequestHeaderBytes + log.Info("MaxRequestHeaderBytes is not set or wong, setting default value", zap.Int("MaxRequestHeaderBytes", config.DefaultMaxRequestHeaderBytes)) + } if conf.MaxWebSocketClients == 0 { conf.MaxWebSocketClients = defaultMaxWebSocketClients log.Info("MaxWebSocketClients is not set or wrong, setting default value", zap.Int("MaxWebSocketClients", defaultMaxWebSocketClients)) @@ -329,6 +314,28 @@ func New(chain Ledger, conf config.RPC, coreServer *network.Server, if conf.EnableCORSWorkaround { wsOriginChecker = func(_ *http.Request) bool { return true } } + + addrs := conf.Addresses + httpServers := make([]*http.Server, len(addrs)) + for i, addr := range addrs { + httpServers[i] = &http.Server{ + Addr: addr, + MaxHeaderBytes: conf.MaxRequestHeaderBytes, + } + } + + var tlsServers []*http.Server + if cfg := conf.TLSConfig; cfg.Enabled { + addrs := cfg.Addresses + tlsServers = make([]*http.Server, len(addrs)) + for i, addr := range addrs { + tlsServers[i] = &http.Server{ + Addr: addr, + MaxHeaderBytes: conf.MaxRequestHeaderBytes, + } + } + } + return Server{ http: httpServers, https: tlsServers,