Skip to content

Commit 1015733

Browse files
fix(dot/subscription): unsafe type casting from untrusted input (#2529)
* chore: using checks to use `interface{}` types
1 parent d2ee47e commit 1015733

File tree

2 files changed

+64
-31
lines changed

2 files changed

+64
-31
lines changed

dot/rpc/subscription/websocket.go

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ type httpclient interface {
3333
}
3434

3535
var (
36+
errUnexpectedType = errors.New("unexpected type")
37+
errUnexpectedParamLen = errors.New("unexpected params length")
3638
errCannotReadFromWebsocket = errors.New("cannot read message from websocket")
3739
errEmptyMethod = errors.New("empty method")
3840
)
@@ -163,25 +165,35 @@ func (c *WSConn) initStorageChangeListener(reqID float64, params interface{}) (L
163165
wsconn: c,
164166
}
165167

166-
pA, ok := params.([]interface{})
167-
if !ok {
168-
return nil, fmt.Errorf("unknown parameter type")
169-
}
170-
for _, param := range pA {
171-
switch p := param.(type) {
172-
case []interface{}:
173-
for _, pp := range param.([]interface{}) {
174-
data, ok := pp.(string)
175-
if !ok {
176-
return nil, fmt.Errorf("unknown parameter type")
168+
// the following type checking/casting is needed in order to satisfy some
169+
// websocket request field params eg.:
170+
// "params": ["0x..."] or
171+
// "params": [["0x...", "0x..."]]
172+
switch filters := params.(type) {
173+
case []interface{}:
174+
for _, interfaceKey := range filters {
175+
switch key := interfaceKey.(type) {
176+
case string:
177+
stgobs.filter[key] = []byte{}
178+
case []string:
179+
for _, k := range key {
180+
stgobs.filter[k] = []byte{}
181+
}
182+
case []interface{}:
183+
for _, k := range key {
184+
k, ok := k.(string)
185+
if !ok {
186+
return nil, fmt.Errorf("%w: %T, expected type string", errUnexpectedType, k)
187+
}
188+
189+
stgobs.filter[k] = []byte{}
177190
}
178-
stgobs.filter[data] = []byte{}
191+
default:
192+
return nil, fmt.Errorf("%w: %T, expected type string, []string, []interface{}", errUnexpectedType, interfaceKey)
179193
}
180-
case string:
181-
stgobs.filter[p] = []byte{}
182-
default:
183-
return nil, fmt.Errorf("unknown parameter type")
184194
}
195+
default:
196+
return nil, fmt.Errorf("%w: %T, expected type []interface{}", errUnexpectedType, params)
185197
}
186198

187199
c.mu.Lock()
@@ -269,14 +281,32 @@ func (c *WSConn) initAllBlocksListerner(reqID float64, _ interface{}) (Listener,
269281
}
270282

271283
func (c *WSConn) initExtrinsicWatch(reqID float64, params interface{}) (Listener, error) {
272-
pA := params.([]interface{})
284+
var encodedExtrinsic string
285+
286+
switch encodedHex := params.(type) {
287+
case []string:
288+
if len(encodedHex) != 1 {
289+
return nil, fmt.Errorf("%w: expected 1 param, got: %d", errUnexpectedParamLen, len(encodedHex))
290+
}
291+
encodedExtrinsic = encodedHex[0]
292+
// the bellow case is needed to cover a interface{} slice containing one string
293+
// as `[]interface{"a"}` is not the same as `[]string{"a"}`
294+
case []interface{}:
295+
if len(encodedHex) != 1 {
296+
return nil, fmt.Errorf("%w: expected 1 param, got: %d", errUnexpectedParamLen, len(encodedHex))
297+
}
273298

274-
if len(pA) != 1 {
275-
return nil, errors.New("expecting only one parameter")
299+
var ok bool
300+
encodedExtrinsic, ok = encodedHex[0].(string)
301+
if !ok {
302+
return nil, fmt.Errorf("%w: %T, expected type string", errUnexpectedType, encodedHex[0])
303+
}
304+
default:
305+
return nil, fmt.Errorf("%w: %T, expected type []string or []interface{}", errUnexpectedType, params)
276306
}
277307

278308
// The passed parameter should be a HEX of a SCALE encoded extrinsic
279-
extBytes, err := common.HexToBytes(pA[0].(string))
309+
extBytes, err := common.HexToBytes(encodedExtrinsic)
280310
if err != nil {
281311
return nil, err
282312
}

dot/rpc/subscription/websocket_test.go

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ func TestWSConn_HandleConn(t *testing.T) {
4545
res, err = wsconn.initStorageChangeListener(1, nil)
4646
require.Nil(t, res)
4747
require.Len(t, wsconn.Subscriptions, 0)
48-
require.EqualError(t, err, "unknown parameter type")
48+
require.ErrorIs(t, err, errUnexpectedType)
49+
require.EqualError(t, err, "unexpected type: <nil>, expected type []interface{}")
4950

5051
res, err = wsconn.initStorageChangeListener(2, []interface{}{})
5152
require.NotNil(t, res)
@@ -55,33 +56,35 @@ func TestWSConn_HandleConn(t *testing.T) {
5556
require.NoError(t, err)
5657
require.Equal(t, []byte(`{"jsonrpc":"2.0","result":1,"id":2}`+"\n"), msg)
5758

58-
res, err = wsconn.initStorageChangeListener(3, []interface{}{"0x26aa"})
59+
var testFilter0 = []interface{}{"0x26aa"}
60+
res, err = wsconn.initStorageChangeListener(3, testFilter0)
5961
require.NotNil(t, res)
6062
require.NoError(t, err)
6163
require.Len(t, wsconn.Subscriptions, 2)
6264
_, msg, err = c.ReadMessage()
6365
require.NoError(t, err)
6466
require.Equal(t, []byte(`{"jsonrpc":"2.0","result":2,"id":3}`+"\n"), msg)
6567

66-
var testFilters = []interface{}{}
67-
var testFilter1 = []interface{}{"0x26aa", "0x26a1"}
68-
res, err = wsconn.initStorageChangeListener(4, append(testFilters, testFilter1))
69-
require.NotNil(t, res)
68+
var testFilter1 = []interface{}{[]interface{}{"0x26aa", "0x26a1"}}
69+
res, err = wsconn.initStorageChangeListener(4, testFilter1)
7070
require.NoError(t, err)
71+
require.NotNil(t, res)
7172
require.Len(t, wsconn.Subscriptions, 3)
7273
_, msg, err = c.ReadMessage()
7374
require.NoError(t, err)
7475
require.Equal(t, []byte(`{"jsonrpc":"2.0","result":3,"id":4}`+"\n"), msg)
7576

76-
var testFilterWrongType = []interface{}{"0x26aa", 1}
77-
res, err = wsconn.initStorageChangeListener(5, append(testFilters, testFilterWrongType))
78-
require.EqualError(t, err, "unknown parameter type")
77+
var testFilterWrongType = []interface{}{[]int{123}}
78+
res, err = wsconn.initStorageChangeListener(5, testFilterWrongType)
79+
require.ErrorIs(t, err, errUnexpectedType)
80+
require.EqualError(t, err, "unexpected type: []int, expected type string, []string, []interface{}")
7981
require.Nil(t, res)
8082
// keep subscriptions len == 3, no additions was made
8183
require.Len(t, wsconn.Subscriptions, 3)
8284

8385
res, err = wsconn.initStorageChangeListener(6, []interface{}{1})
84-
require.EqualError(t, err, "unknown parameter type")
86+
require.ErrorIs(t, err, errUnexpectedType)
87+
require.EqualError(t, err, "unexpected type: int, expected type string, []string, []interface{}")
8588
require.Nil(t, res)
8689
require.Len(t, wsconn.Subscriptions, 3)
8790

@@ -207,7 +210,7 @@ func TestWSConn_HandleConn(t *testing.T) {
207210
wsconn.CoreAPI = modules.NewMockCoreAPI()
208211
wsconn.BlockAPI = nil
209212
wsconn.TxStateAPI = modules.NewMockTransactionStateAPI()
210-
listner, err := wsconn.initExtrinsicWatch(0, []interface{}{"NotHex"})
213+
listner, err := wsconn.initExtrinsicWatch(0, []string{"NotHex"})
211214
require.EqualError(t, err, "could not byteify non 0x prefixed string: NotHex")
212215
require.Nil(t, listner)
213216

0 commit comments

Comments
 (0)