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, Disconnecting,
Disconnected 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 DisconnectionReason* = enum
DisconnectRequested, DisconnectRequested = 0x00,
TcpError, TcpError = 0x01,
BreachOfProtocol, BreachOfProtocol = 0x02,
UselessPeer, UselessPeer = 0x03,
TooManyPeers, TooManyPeers = 0x04,
AlreadyConnected, AlreadyConnected = 0x05,
IncompatibleProtocolVersion, IncompatibleProtocolVersion = 0x06,
NullNodeIdentityReceived, NullNodeIdentityReceived = 0x07,
ClientQuitting, ClientQuitting = 0x08,
UnexpectedIdentity, UnexpectedIdentity = 0x09,
SelfConnection, SelfConnection = 0x0A,
MessageTimeout, MessageTimeout = 0x0B,
SubprotocolReason = 0x10 SubprotocolReason = 0x10
proc `$`*(peer: Peer): string = $peer.remote 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") "Invalid RLPx message body")
elif nextMsgId == 1: # p2p.disconnect elif nextMsgId == 1: # p2p.disconnect
if nextMsgData.isList(): let reasonList = rlp.read(nextMsgData, DisconnectionReasonList)
let reason = DisconnectionReason nextMsgData.listElem(0).toInt(uint32) let reason = reasonList.value
await peer.disconnect(reason) await peer.disconnect(reason)
trace "disconnect message received in waitSingleMsg", reason, peer trace "disconnect message received in waitSingleMsg", reason, peer
raisePeerDisconnected("Unexpected disconnect", reason) raisePeerDisconnected("Unexpected disconnect", reason)
else:
raise newException(RlpTypeMismatch,
"List expected, but the source RLP is not a list.")
else: else:
warn "Dropped RLPX message", warn "Dropped RLPX message",
msg = peer.dispatcher.messages[nextMsgId].name msg = peer.dispatcher.messages[nextMsgId].name

View File

@ -8,6 +8,8 @@ import
./rlp/[writer, object_serialization], ./rlp/[writer, object_serialization],
./rlp/priv/defs ./rlp/priv/defs
from stew/objects import checkedEnumAssign
export export
writer, object_serialization 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 = proc readImpl(rlp: var Rlp, T: type[enum]): T =
let value = rlp.toInt(int) 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, raise newException(RlpTypeMismatch,
"Enum expected, but the source RLP is not in valid range.") "Enum value expected, but the source RLP is not in valid range.")
result = type(result)(value)
rlp.skipElem rlp.skipElem
res
proc readImpl(rlp: var Rlp, T: type bool): T = proc readImpl(rlp: var Rlp, T: type bool): T =
result = rlp.toInt(int) != 0 result = rlp.toInt(int) != 0
rlp.skipElem rlp.skipElem

View File

@ -59,15 +59,25 @@
"error": "RlpTypeMismatch", "error": "RlpTypeMismatch",
"description": "listElem to assert on not a single entry list" "description": "listElem to assert on not a single entry list"
}, },
"Listing single element list with entry off enum range": { "DisconnectReason: single element list with entry out off enum range": {
"payload": "01c116", "payload": "01c111",
"error": "RlpTypeMismatch", "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": { "DisconnectReason: single element out off enum range": {
"payload": "0116", "payload": "0111",
"error": "RlpTypeMismatch", "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": { "devp2p hello packet version 22 + additional list elements for EIP-8": {
"payload": "00f87137916b6e6574682f76302e39312f706c616e39cdc5836574683dc6846d6f726b1682270fb840fda1cff674c90c9a197539fe3dfb53086ace64f83ed7c6eabec741f7f381cc803e52ab2cd55d5569bce4347107a310dfd5f88a010cd2ffd1005ca406f1842877c883666f6f836261720304" "payload": "00f87137916b6e6574682f76302e39312f706c616e39cdc5836574683dc6846d6f726b1682270fb840fda1cff674c90c9a197539fe3dfb53086ace64f83ed7c6eabec741f7f381cc803e52ab2cd55d5569bce4347107a310dfd5f88a010cd2ffd1005ca406f1842877c883666f6f836261720304"

View File

@ -203,16 +203,48 @@ suite "test api usage":
test "invalid enum": test "invalid enum":
type type
MyEnum = enum MyEnum = enum
foo, foo = 0x00,
bar bar = 0x01
var writer = initRlpWriter() var writer = initRlpWriter()
writer.append(2) writer.append(1) # valid
writer.append(-1) writer.append(2) # invalid
writer.append(-1) # invalid
let bytes = writer.finish() let bytes = writer.finish()
var rlp = rlpFromBytes(bytes) var rlp = rlpFromBytes(bytes)
check rlp.read(MyEnum) == bar
expect RlpTypeMismatch: expect RlpTypeMismatch:
discard rlp.read(MyEnum) discard rlp.read(MyEnum)
rlp.skipElem() rlp.skipElem()
expect RlpTypeMismatch: expect RlpTypeMismatch:
discard rlp.read(MyEnum) 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()