Fix RLP deserialzation for Enum with holes (#554)

RLP Enum deserialization would currently not check if "hole values"
were attempted to be converted to the enum type. Now use
checkedEnumAssign and fail with RlpTypeMismatch on invalid values.

There is at least one occurance of an enum with holes in rlpx p2p:
DisconnectionReason. For this enum the issue could occur.

Also:
- Added enum RLP tests and rlpx p2p disconnect message tests to
test the DisconnectionReason with enum hole value.
- Fixed worse custom DisconnectionReason decoding occurance
in rlpx in waitSingleMsg proc where this issue could occur.
This commit is contained in:
Kim De Mey 2022-11-14 15:49:37 +01:00 committed by GitHub
parent 9b0f054b04
commit 522db295f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 81 additions and 33 deletions

View File

@ -167,19 +167,23 @@ type
Disconnecting,
Disconnected
# Disconnect message reasons as specified:
# https://github.com/ethereum/devp2p/blob/master/rlpx.md#disconnect-0x01
# Receiving values that are too large or that are in the enum hole will
# trigger `RlpTypeMismatch` error on deserialization.
DisconnectionReason* = enum
DisconnectRequested,
TcpError,
BreachOfProtocol,
UselessPeer,
TooManyPeers,
AlreadyConnected,
IncompatibleProtocolVersion,
NullNodeIdentityReceived,
ClientQuitting,
UnexpectedIdentity,
SelfConnection,
MessageTimeout,
DisconnectRequested = 0x00,
TcpError = 0x01,
BreachOfProtocol = 0x02,
UselessPeer = 0x03,
TooManyPeers = 0x04,
AlreadyConnected = 0x05,
IncompatibleProtocolVersion = 0x06,
NullNodeIdentityReceived = 0x07,
ClientQuitting = 0x08,
UnexpectedIdentity = 0x09,
SelfConnection = 0x0A,
MessageTimeout = 0x0B,
SubprotocolReason = 0x10
proc `$`*(peer: Peer): string = $peer.remote

View File

@ -708,14 +708,11 @@ proc waitSingleMsg(peer: Peer, MsgType: type): Future[MsgType] {.async.} =
"Invalid RLPx message body")
elif nextMsgId == 1: # p2p.disconnect
if nextMsgData.isList():
let reason = DisconnectionReason nextMsgData.listElem(0).toInt(uint32)
await peer.disconnect(reason)
trace "disconnect message received in waitSingleMsg", reason, peer
raisePeerDisconnected("Unexpected disconnect", reason)
else:
raise newException(RlpTypeMismatch,
"List expected, but the source RLP is not a list.")
let reasonList = rlp.read(nextMsgData, DisconnectionReasonList)
let reason = reasonList.value
await peer.disconnect(reason)
trace "disconnect message received in waitSingleMsg", reason, peer
raisePeerDisconnected("Unexpected disconnect", reason)
else:
warn "Dropped RLPX message",
msg = peer.dispatcher.messages[nextMsgId].name

View File

@ -8,6 +8,8 @@ import
./rlp/[writer, object_serialization],
./rlp/priv/defs
from stew/objects import checkedEnumAssign
export
writer, object_serialization
@ -293,12 +295,15 @@ proc readImpl(rlp: var Rlp, T: type Integer): Integer =
proc readImpl(rlp: var Rlp, T: type[enum]): T =
let value = rlp.toInt(int)
if value < ord(T.low) or value > ord(T.high):
var res: T
if not checkedEnumAssign(res, value):
raise newException(RlpTypeMismatch,
"Enum expected, but the source RLP is not in valid range.")
result = type(result)(value)
"Enum value expected, but the source RLP is not in valid range.")
rlp.skipElem
res
proc readImpl(rlp: var Rlp, T: type bool): T =
result = rlp.toInt(int) != 0
rlp.skipElem

View File

@ -59,15 +59,25 @@
"error": "RlpTypeMismatch",
"description": "listElem to assert on not a single entry list"
},
"Listing single element list with entry off enum range": {
"payload": "01c116",
"DisconnectReason: single element list with entry out off enum range": {
"payload": "01c111",
"error": "RlpTypeMismatch",
"description": "Disconnect reason code out of bounds 0..16 (got: 22)"
"description": "Disconnect reason code out of bounds 0..16 (got: 17)"
},
"Listing single element off enum range": {
"payload": "0116",
"DisconnectReason: single element out off enum range": {
"payload": "0111",
"error": "RlpTypeMismatch",
"description": "Disconnect reason code out of bounds 0..16 (got: 22)"
"description": "Disconnect reason code out of bounds 0..16 (got: 17)"
},
"DisconnectReason: single element list with enum hole value": {
"payload": "01c10C",
"error": "RlpTypeMismatch",
"description": "Error on Disconnect reason with enum hole value"
},
"DisconnectReason: single element with enum hole value": {
"payload": "010C",
"error": "RlpTypeMismatch",
"description": "Error on Disconnect reason with enum hole value"
},
"devp2p hello packet version 22 + additional list elements for EIP-8": {
"payload": "00f87137916b6e6574682f76302e39312f706c616e39cdc5836574683dc6846d6f726b1682270fb840fda1cff674c90c9a197539fe3dfb53086ace64f83ed7c6eabec741f7f381cc803e52ab2cd55d5569bce4347107a310dfd5f88a010cd2ffd1005ca406f1842877c883666f6f836261720304"

View File

@ -203,16 +203,48 @@ suite "test api usage":
test "invalid enum":
type
MyEnum = enum
foo,
bar
foo = 0x00,
bar = 0x01
var writer = initRlpWriter()
writer.append(2)
writer.append(-1)
writer.append(1) # valid
writer.append(2) # invalid
writer.append(-1) # invalid
let bytes = writer.finish()
var rlp = rlpFromBytes(bytes)
check rlp.read(MyEnum) == bar
expect RlpTypeMismatch:
discard rlp.read(MyEnum)
rlp.skipElem()
expect RlpTypeMismatch:
discard rlp.read(MyEnum)
test "invalid enum - enum with hole":
type
MyEnum = enum
foo = 0x00,
bar = 0x01,
baz = 0x0100
var writer = initRlpWriter()
writer.append(1) # valid
writer.append(2) # invalid - enum hole value
writer.append(256) # valid
writer.append(257) # invalid - too large
let bytes = writer.finish()
var rlp = rlpFromBytes(bytes)
check rlp.read(MyEnum) == bar
expect RlpTypeMismatch:
discard rlp.read(MyEnum)
rlp.skipElem()
check rlp.read(MyEnum) == baz
expect RlpTypeMismatch:
discard rlp.read(MyEnum)
rlp.skipElem()