mirror of https://github.com/status-im/nim-eth.git
Fix possible crashes due to RangeErrors in rlpx auth decoding (#558)
- Fix two possible RangeErrors, due to negative seq allocation in decodeAuthMessageEIP8 and in decodeAckMessageEIP8 - Set the minimum auth message size to AuthMessageEIP8Length, in case there are clients that no longer add padding - Add tests for invalid length cases
This commit is contained in:
parent
e7d3de6ebf
commit
9ba1eb99e3
|
@ -1,6 +1,6 @@
|
||||||
#
|
#
|
||||||
# Ethereum P2P
|
# Ethereum P2P
|
||||||
# (c) Copyright 2018
|
# (c) Copyright 2018-2022
|
||||||
# Status Research & Development GmbH
|
# Status Research & Development GmbH
|
||||||
#
|
#
|
||||||
# Licensed under either of
|
# Licensed under either of
|
||||||
|
@ -8,7 +8,7 @@
|
||||||
# MIT license (LICENSE-MIT)
|
# MIT license (LICENSE-MIT)
|
||||||
#
|
#
|
||||||
|
|
||||||
## This module implements Ethereum authentication
|
## This module implements Ethereum RLPx authentication
|
||||||
|
|
||||||
{.push raises: [Defect].}
|
{.push raises: [Defect].}
|
||||||
|
|
||||||
|
@ -22,18 +22,36 @@ export results
|
||||||
|
|
||||||
const
|
const
|
||||||
SupportedRlpxVersion* = 4'u8
|
SupportedRlpxVersion* = 4'u8
|
||||||
|
|
||||||
|
# Auth message sizes
|
||||||
|
|
||||||
|
# Pre EIP8
|
||||||
PlainAuthMessageV4Length* = 194
|
PlainAuthMessageV4Length* = 194
|
||||||
AuthMessageV4Length* = 307
|
AuthMessageV4Length* = 307
|
||||||
|
|
||||||
|
# EIP8
|
||||||
|
# signature + pubkey + nounce + version + rlp encoding overhead
|
||||||
|
# 65 + 64 + 32 + 1 + 7 = 169
|
||||||
PlainAuthMessageEIP8Length = 169
|
PlainAuthMessageEIP8Length = 169
|
||||||
PlainAuthMessageMaxEIP8* = PlainAuthMessageEIP8Length + 255
|
PlainAuthMessageMaxEIP8* = PlainAuthMessageEIP8Length + 255 # with padding
|
||||||
AuthMessageEIP8Length* = 282 + 2
|
# Min. encrypted message + size prefix = 284
|
||||||
AuthMessageMaxEIP8* = AuthMessageEIP8Length + 255
|
AuthMessageEIP8Length* = PlainAuthMessageEIP8Length + eciesOverheadLength + 2
|
||||||
|
AuthMessageMaxEIP8* = AuthMessageEIP8Length + 255 # with padding
|
||||||
|
|
||||||
|
# Ack message sizes
|
||||||
|
|
||||||
|
# Pre EIP8
|
||||||
PlainAckMessageV4Length* = 97
|
PlainAckMessageV4Length* = 97
|
||||||
AckMessageV4Length* = 210
|
AckMessageV4Length* = 210
|
||||||
|
|
||||||
|
# EIP 8
|
||||||
|
# pubkey + nounce + version + rlp encoding overhead
|
||||||
|
# 64 + 32 + 1 + 5 = 102
|
||||||
PlainAckMessageEIP8Length* = 102
|
PlainAckMessageEIP8Length* = 102
|
||||||
PlainAckMessageMaxEIP8* = PlainAckMessageEIP8Length + 255
|
PlainAckMessageMaxEIP8* = PlainAckMessageEIP8Length + 255 # with padding
|
||||||
AckMessageEIP8Length* = 215 + 2
|
# Min. encrypted message + size prefix = 217
|
||||||
AckMessageMaxEIP8* = AckMessageEIP8Length + 255
|
AckMessageEIP8Length* = PlainAckMessageEIP8Length + eciesOverheadLength + 2
|
||||||
|
AckMessageMaxEIP8* = AckMessageEIP8Length + 255 # with padding
|
||||||
|
|
||||||
type
|
type
|
||||||
Nonce* = array[KeyLength, byte]
|
Nonce* = array[KeyLength, byte]
|
||||||
|
@ -361,12 +379,18 @@ proc decodeAuthMessageV4(h: var Handshake, m: openArray[byte]): AuthResult[void]
|
||||||
proc decodeAuthMessageEIP8(h: var Handshake, m: openArray[byte]): AuthResult[void] =
|
proc decodeAuthMessageEIP8(h: var Handshake, m: openArray[byte]): AuthResult[void] =
|
||||||
## Decodes EIP-8 AuthMessage.
|
## Decodes EIP-8 AuthMessage.
|
||||||
let size = uint16.fromBytesBE(m)
|
let size = uint16.fromBytesBE(m)
|
||||||
h.expectedLength = int(size) + 2
|
h.expectedLength = 2 + int(size)
|
||||||
|
|
||||||
|
# Check if the prefixed size is => than the minimum
|
||||||
|
if h.expectedLength < AuthMessageEIP8Length:
|
||||||
|
return err(AuthError.IncompleteError)
|
||||||
|
|
||||||
if h.expectedLength > len(m):
|
if h.expectedLength > len(m):
|
||||||
return err(AuthError.IncompleteError)
|
return err(AuthError.IncompleteError)
|
||||||
|
|
||||||
var buffer = newSeq[byte](eciesDecryptedLength(int(size)))
|
var buffer = newSeq[byte](eciesDecryptedLength(int(size)))
|
||||||
if eciesDecrypt(toa(m, 2, int(size)), buffer, h.host.seckey,
|
if eciesDecrypt(
|
||||||
toa(m, 0, 2)).isErr:
|
toa(m, 2, int(size)), buffer, h.host.seckey, toa(m, 0, 2)).isErr:
|
||||||
return err(AuthError.EciesError)
|
return err(AuthError.EciesError)
|
||||||
try:
|
try:
|
||||||
var reader = rlpFromBytes(buffer)
|
var reader = rlpFromBytes(buffer)
|
||||||
|
@ -408,13 +432,18 @@ proc decodeAuthMessageEIP8(h: var Handshake, m: openArray[byte]): AuthResult[voi
|
||||||
proc decodeAckMessageEIP8*(h: var Handshake, m: openArray[byte]): AuthResult[void] =
|
proc decodeAckMessageEIP8*(h: var Handshake, m: openArray[byte]): AuthResult[void] =
|
||||||
## Decodes EIP-8 AckMessage.
|
## Decodes EIP-8 AckMessage.
|
||||||
let size = uint16.fromBytesBE(m)
|
let size = uint16.fromBytesBE(m)
|
||||||
|
|
||||||
h.expectedLength = 2 + int(size)
|
h.expectedLength = 2 + int(size)
|
||||||
|
|
||||||
|
# Check if the prefixed size is => than the minimum
|
||||||
|
if h.expectedLength < AckMessageEIP8Length:
|
||||||
|
return err(AuthError.IncompleteError)
|
||||||
|
|
||||||
if h.expectedLength > len(m):
|
if h.expectedLength > len(m):
|
||||||
return err(AuthError.IncompleteError)
|
return err(AuthError.IncompleteError)
|
||||||
|
|
||||||
var buffer = newSeq[byte](eciesDecryptedLength(int(size)))
|
var buffer = newSeq[byte](eciesDecryptedLength(int(size)))
|
||||||
if eciesDecrypt(toa(m, 2, int(size)), buffer, h.host.seckey,
|
if eciesDecrypt(
|
||||||
toa(m, 0, 2)).isErr:
|
toa(m, 2, int(size)), buffer, h.host.seckey, toa(m, 0, 2)).isErr:
|
||||||
return err(AuthError.EciesError)
|
return err(AuthError.EciesError)
|
||||||
try:
|
try:
|
||||||
var reader = rlpFromBytes(buffer)
|
var reader = rlpFromBytes(buffer)
|
||||||
|
@ -456,7 +485,8 @@ proc decodeAckMessageV4(h: var Handshake, m: openArray[byte]): AuthResult[void]
|
||||||
|
|
||||||
proc decodeAuthMessage*(h: var Handshake, input: openArray[byte]): AuthResult[void] =
|
proc decodeAuthMessage*(h: var Handshake, input: openArray[byte]): AuthResult[void] =
|
||||||
## Decodes AuthMessage from `input`.
|
## Decodes AuthMessage from `input`.
|
||||||
if len(input) < AuthMessageV4Length:
|
# Using the smallest min. message length of the two types
|
||||||
|
if len(input) < AuthMessageEIP8Length:
|
||||||
return err(AuthError.IncompleteError)
|
return err(AuthError.IncompleteError)
|
||||||
|
|
||||||
if len(input) == AuthMessageV4Length:
|
if len(input) == AuthMessageV4Length:
|
||||||
|
@ -470,8 +500,10 @@ proc decodeAuthMessage*(h: var Handshake, input: openArray[byte]): AuthResult[vo
|
||||||
|
|
||||||
proc decodeAckMessage*(h: var Handshake, input: openArray[byte]): AuthResult[void] =
|
proc decodeAckMessage*(h: var Handshake, input: openArray[byte]): AuthResult[void] =
|
||||||
## Decodes AckMessage from `input`.
|
## Decodes AckMessage from `input`.
|
||||||
|
# Using the smallest min. message length of the two types
|
||||||
if len(input) < AckMessageV4Length:
|
if len(input) < AckMessageV4Length:
|
||||||
return err(AuthError.IncompleteError)
|
return err(AuthError.IncompleteError)
|
||||||
|
|
||||||
if len(input) == AckMessageV4Length:
|
if len(input) == AckMessageV4Length:
|
||||||
let res = h.decodeAckMessageV4(input)
|
let res = h.decodeAckMessageV4(input)
|
||||||
if res.isOk(): return res
|
if res.isOk(): return res
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#
|
#
|
||||||
# Ethereum P2P
|
# Ethereum P2P
|
||||||
# (c) Copyright 2018
|
# (c) Copyright 2018-2022
|
||||||
# Status Research & Development GmbH
|
# Status Research & Development GmbH
|
||||||
#
|
#
|
||||||
# Licensed under either of
|
# Licensed under either of
|
||||||
|
@ -22,6 +22,11 @@ export results
|
||||||
const
|
const
|
||||||
emptyMac* = array[0, byte]([])
|
emptyMac* = array[0, byte]([])
|
||||||
|
|
||||||
|
eciesOverheadLength* =
|
||||||
|
# Data overhead size for ECIES encrypted message
|
||||||
|
# pubkey + IV + MAC = 65 + 16 + 32 = 113
|
||||||
|
1 + sizeof(PublicKey) + aes128.sizeBlock + sha256.sizeDigest
|
||||||
|
|
||||||
type
|
type
|
||||||
EciesError* = enum
|
EciesError* = enum
|
||||||
BufferOverrun = "ecies: output buffer size is too small"
|
BufferOverrun = "ecies: output buffer size is too small"
|
||||||
|
@ -42,17 +47,13 @@ type
|
||||||
proc mapErrTo[T](r: SkResult[T], v: static EciesError): EciesResult[T] =
|
proc mapErrTo[T](r: SkResult[T], v: static EciesError): EciesResult[T] =
|
||||||
r.mapErr(proc (e: cstring): EciesError = v)
|
r.mapErr(proc (e: cstring): EciesError = v)
|
||||||
|
|
||||||
template eciesOverheadLength*(): int =
|
|
||||||
## Return data overhead size for ECIES encrypted message
|
|
||||||
1 + sizeof(PublicKey) + aes128.sizeBlock + sha256.sizeDigest
|
|
||||||
|
|
||||||
template eciesEncryptedLength*(size: int): int =
|
template eciesEncryptedLength*(size: int): int =
|
||||||
## Return size of encrypted message for message with size `size`.
|
## Return size of encrypted message for message with size `size`.
|
||||||
size + eciesOverheadLength()
|
size + eciesOverheadLength
|
||||||
|
|
||||||
template eciesDecryptedLength*(size: int): int =
|
template eciesDecryptedLength*(size: int): int =
|
||||||
## Return size of decrypted message for encrypted message with size `size`.
|
## Return size of decrypted message for encrypted message with size `size`.
|
||||||
size - eciesOverheadLength()
|
size - eciesOverheadLength
|
||||||
|
|
||||||
template eciesMacLength(size: int): int =
|
template eciesMacLength(size: int): int =
|
||||||
## Return size of authenticated data
|
## Return size of authenticated data
|
||||||
|
@ -175,9 +176,9 @@ proc eciesDecrypt*(input: openArray[byte],
|
||||||
var header = cast[ptr EciesHeader](unsafeAddr input[0])
|
var header = cast[ptr EciesHeader](unsafeAddr input[0])
|
||||||
if header.version != 0x04:
|
if header.version != 0x04:
|
||||||
return err(WrongHeader)
|
return err(WrongHeader)
|
||||||
if len(input) <= eciesOverheadLength():
|
if len(input) <= eciesOverheadLength:
|
||||||
return err(IncompleteError)
|
return err(IncompleteError)
|
||||||
if len(input) - eciesOverheadLength() > len(output):
|
if len(input) - eciesOverheadLength > len(output):
|
||||||
return err(BufferOverrun)
|
return err(BufferOverrun)
|
||||||
|
|
||||||
var
|
var
|
||||||
|
@ -192,7 +193,7 @@ proc eciesDecrypt*(input: openArray[byte],
|
||||||
sha256.digest(material.toOpenArray(KeyLength div 2, material.high))
|
sha256.digest(material.toOpenArray(KeyLength div 2, material.high))
|
||||||
burnMem(material)
|
burnMem(material)
|
||||||
|
|
||||||
let macsize = eciesMacLength(len(input) - eciesOverheadLength())
|
let macsize = eciesMacLength(len(input) - eciesOverheadLength)
|
||||||
ctx.init(macKey.data)
|
ctx.init(macKey.data)
|
||||||
burnMem(macKey)
|
burnMem(macKey)
|
||||||
ctx.update(toOpenArray(input, eciesIvPos(), eciesIvPos() + macsize - 1))
|
ctx.update(toOpenArray(input, eciesIvPos(), eciesIvPos() + macsize - 1))
|
||||||
|
|
|
@ -466,3 +466,81 @@ suite "Ethereum P2P handshake test suite":
|
||||||
check:
|
check:
|
||||||
csecInitiator.aesKey == csecResponder.aesKey
|
csecInitiator.aesKey == csecResponder.aesKey
|
||||||
csecInitiator.macKey == csecResponder.macKey
|
csecInitiator.macKey == csecResponder.macKey
|
||||||
|
|
||||||
|
test "Invalid AuthMessage - Minimum input size":
|
||||||
|
var responder = newTestHandshake({Responder})
|
||||||
|
|
||||||
|
# 1 byte short on minimum AuthMessage size
|
||||||
|
var m = newSeq[byte](AuthMessageEIP8Length - 1)
|
||||||
|
|
||||||
|
let res = responder.decodeAuthMessage(m)
|
||||||
|
check:
|
||||||
|
res.isErr()
|
||||||
|
res.error == AuthError.IncompleteError
|
||||||
|
|
||||||
|
test "Invalid AuthMessage - Minimum size prefix":
|
||||||
|
var responder = newTestHandshake({Responder})
|
||||||
|
|
||||||
|
# Minimum size for EIP8 AuthMessage
|
||||||
|
var m = newSeq[byte](AuthMessageEIP8Length)
|
||||||
|
# size prefix size of 281, 1 byte short
|
||||||
|
m[0] = 1
|
||||||
|
m[1] = 25
|
||||||
|
|
||||||
|
let res = responder.decodeAuthMessage(m)
|
||||||
|
check:
|
||||||
|
res.isErr()
|
||||||
|
res.error == AuthError.IncompleteError
|
||||||
|
|
||||||
|
test "Invalid AuthMessage - Size prefix bigger than input":
|
||||||
|
var responder = newTestHandshake({Responder})
|
||||||
|
|
||||||
|
# Minimum size for EIP8 AuthMessage
|
||||||
|
var m = newSeq[byte](AuthMessageEIP8Length)
|
||||||
|
# size prefix size of 283, 1 byte too many
|
||||||
|
m[0] = 1
|
||||||
|
m[1] = 27
|
||||||
|
|
||||||
|
let res = responder.decodeAuthMessage(m)
|
||||||
|
check:
|
||||||
|
res.isErr()
|
||||||
|
res.error == AuthError.IncompleteError
|
||||||
|
|
||||||
|
test "Invalid AckMessage - Minimum input size":
|
||||||
|
var initiator = newTestHandshake({Initiator, EIP8})
|
||||||
|
|
||||||
|
# 1 byte short on minimum size
|
||||||
|
let m = newSeq[byte](AckMessageV4Length - 1)
|
||||||
|
|
||||||
|
let res = initiator.decodeAckMessage(m)
|
||||||
|
check:
|
||||||
|
res.isErr()
|
||||||
|
res.error == AuthError.IncompleteError
|
||||||
|
|
||||||
|
test "Invalid AckMessage - Minimum size prefix":
|
||||||
|
var initiator = newTestHandshake({Initiator, EIP8})
|
||||||
|
|
||||||
|
# Minimum size for EIP8 AckMessage
|
||||||
|
var m = newSeq[byte](AckMessageEIP8Length)
|
||||||
|
# size prefix size of 214, 1 byte short
|
||||||
|
m[0] = 0
|
||||||
|
m[1] = 214
|
||||||
|
|
||||||
|
let res = initiator.decodeAckMessage(m)
|
||||||
|
check:
|
||||||
|
res.isErr()
|
||||||
|
res.error == AuthError.IncompleteError
|
||||||
|
|
||||||
|
test "Invalid AckMessage - Size prefix bigger than input":
|
||||||
|
var initiator = newTestHandshake({Initiator, EIP8})
|
||||||
|
|
||||||
|
# Minimum size for EIP8 AckMessage
|
||||||
|
var m = newSeq[byte](AckMessageEIP8Length)
|
||||||
|
# size prefix size of 216, 1 byte too many
|
||||||
|
m[0] = 0
|
||||||
|
m[1] = 216
|
||||||
|
|
||||||
|
let res = initiator.decodeAckMessage(m)
|
||||||
|
check:
|
||||||
|
res.isErr()
|
||||||
|
res.error == AuthError.IncompleteError
|
||||||
|
|
Loading…
Reference in New Issue