mirror of
https://github.com/status-im/nim-eth.git
synced 2025-02-26 20:50:41 +00:00
Merge pull request #281 from status-im/fuzz-tests
Add discv5 fuzzing test targets
This commit is contained in:
commit
4dd7ca1221
@ -19,10 +19,10 @@ const
|
||||
type
|
||||
PacketTag* = array[tagSize, byte]
|
||||
|
||||
AuthResponse = object
|
||||
version: int
|
||||
signature: array[64, byte]
|
||||
record: Option[enr.Record]
|
||||
AuthResponse* = object
|
||||
version*: int
|
||||
signature*: array[64, byte]
|
||||
record*: Option[enr.Record]
|
||||
|
||||
Codec* = object
|
||||
localNode*: Node
|
||||
@ -208,7 +208,7 @@ proc decryptGCM*(key: AesKey, nonce, ct, authData: openarray[byte]):
|
||||
|
||||
return some(res)
|
||||
|
||||
proc decodeMessage(body: openarray[byte]): DecodeResult[Message] =
|
||||
proc decodeMessage*(body: openarray[byte]): DecodeResult[Message] =
|
||||
## Decodes to the specific `Message` type.
|
||||
if body.len < 1:
|
||||
return err(PacketError)
|
||||
|
@ -230,7 +230,7 @@ proc `xor`[N: static[int], T](a, b: array[N, T]): array[N, T] =
|
||||
for i in 0 .. a.high:
|
||||
result[i] = a[i] xor b[i]
|
||||
|
||||
proc whoareyouMagic(toNode: NodeId): array[magicSize, byte] =
|
||||
proc whoareyouMagic*(toNode: NodeId): array[magicSize, byte] =
|
||||
const prefix = "WHOAREYOU"
|
||||
var data: array[prefix.len + sizeof(toNode), byte]
|
||||
data[0 .. sizeof(toNode) - 1] = toNode.toByteArrayBE()
|
||||
|
29
tests/fuzzing/discoveryv5/fuzz_decode_authresp.nim
Normal file
29
tests/fuzzing/discoveryv5/fuzz_decode_authresp.nim
Normal file
@ -0,0 +1,29 @@
|
||||
import
|
||||
testutils/fuzzing, chronicles, stew/byteutils,
|
||||
eth/rlp, eth/p2p/discoveryv5/encoding
|
||||
|
||||
test:
|
||||
block:
|
||||
# This test also includes the decoding of the ENR, so it kinda overlaps with
|
||||
# the fuzz_enr test. And it will fail to decode most of the time for the
|
||||
# same reasons.
|
||||
let decoded = try: rlp.decode(payload, AuthResponse)
|
||||
except RlpError as e:
|
||||
debug "decode failed", err = e.msg
|
||||
break
|
||||
except ValueError as e:
|
||||
debug "decode failed", err = e.msg
|
||||
break
|
||||
|
||||
let encoded = try: rlp.encode(decoded)
|
||||
except RlpError as e:
|
||||
debug "decode failed", err = e.msg
|
||||
doAssert(false, "decoding worked but encoding failed")
|
||||
break
|
||||
# This will hit assert because of issue:
|
||||
# https://github.com/status-im/nim-eth/issues/255
|
||||
# if encoded != payload.toOpenArray(0, encoded.len - 1):
|
||||
# echo "payload: ", toHex(payload.toOpenArray(0, encoded.len - 1))
|
||||
# echo "encoded: ", toHex(encoded)
|
||||
|
||||
# doAssert(false, "re-encoded result does not equal original payload")
|
27
tests/fuzzing/discoveryv5/fuzz_decode_message.nim
Normal file
27
tests/fuzzing/discoveryv5/fuzz_decode_message.nim
Normal file
@ -0,0 +1,27 @@
|
||||
import
|
||||
testutils/fuzzing, stew/byteutils,
|
||||
eth/rlp, eth/p2p/discoveryv5/[encoding, types]
|
||||
|
||||
test:
|
||||
block:
|
||||
let decoded = decodeMessage(payload)
|
||||
|
||||
if decoded.isOK():
|
||||
let message = decoded.get()
|
||||
var encoded: seq[byte]
|
||||
case message.kind
|
||||
of unused: break
|
||||
of ping: encoded = encodeMessage(message.ping, message.reqId)
|
||||
of pong: encoded = encodeMessage(message.pong, message.reqId)
|
||||
of findNode: encoded = encodeMessage(message.findNode, message.reqId)
|
||||
of nodes: encoded = encodeMessage(message.nodes, message.reqId)
|
||||
of regtopic, ticket, regconfirmation, topicquery:
|
||||
break
|
||||
|
||||
# This will hit assert because of issue:
|
||||
# https://github.com/status-im/nim-eth/issues/255
|
||||
# if encoded != payload:
|
||||
# echo "payload: ", toHex(payload)
|
||||
# echo "encoded: ", toHex(encoded)
|
||||
|
||||
# doAssert(false, "re-encoded result does not equal original payload")
|
29
tests/fuzzing/discoveryv5/fuzz_receive.nim
Normal file
29
tests/fuzzing/discoveryv5/fuzz_receive.nim
Normal file
@ -0,0 +1,29 @@
|
||||
import
|
||||
testutils/fuzzing, bearssl, stew/shims/net,
|
||||
eth/[keys, trie/db], eth/p2p/discoveryv5/[protocol, discovery_db],
|
||||
../p2p/discv5_test_helper
|
||||
|
||||
var targetNode: protocol.Protocol
|
||||
|
||||
init:
|
||||
let
|
||||
rng = newRng()
|
||||
privKey = PrivateKey.fromHex(
|
||||
"5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d")[]
|
||||
ip = some(ValidIpAddress.init("127.0.0.1"))
|
||||
port = Port(20301)
|
||||
dbb = DiscoveryDB.init(newMemoryDB())
|
||||
targetNode = newProtocol(privKey, dbb, ip, port, port, rng = rng)
|
||||
# Need to open socket else the response part will fail, would be nice if we
|
||||
# could skip that part during fuzzing.
|
||||
targetNode.open()
|
||||
|
||||
test:
|
||||
# Some dummy address
|
||||
let address = localAddress(20302)
|
||||
# This is a quick and easy, high level fuzzing test and considering that the
|
||||
# auth-response and the message gets encrypted, and that a handshake needs to
|
||||
# be done, it will not be able to reach into testing those depths. However, it
|
||||
# should still be of use hitting the more "simple" code paths (random-packet,
|
||||
# whoareyou-packet, and the beginnings of other packets).
|
||||
targetNode.receive(address, payload)
|
32
tests/fuzzing/discoveryv5/generate_auth_response.nim
Normal file
32
tests/fuzzing/discoveryv5/generate_auth_response.nim
Normal file
@ -0,0 +1,32 @@
|
||||
import
|
||||
std/[os, strutils],
|
||||
stew/shims/net,
|
||||
eth/[rlp, keys], eth/p2p/discoveryv5/[encoding, enr, types],
|
||||
../fuzzing_helpers
|
||||
|
||||
template sourceDir: string = currentSourcePath.rsplit(DirSep, 1)[0]
|
||||
const inputsDir = sourceDir / "corpus" & DirSep
|
||||
|
||||
proc generate() =
|
||||
let
|
||||
rng = keys.newRng()
|
||||
privKey = PrivateKey.random(rng[])
|
||||
pubKey = PrivateKey.random(rng[]).toPublicKey()
|
||||
var idNonce: IdNonce
|
||||
brHmacDrbgGenerate(rng[], idNonce)
|
||||
|
||||
let
|
||||
ephKeys = KeyPair.random(rng[])
|
||||
signature = signIDNonce(privKey, idNonce, ephKeys.pubkey.toRaw)
|
||||
record = enr.Record.init(1, privKey, none(ValidIpAddress), Port(9000),
|
||||
Port(9000))[]
|
||||
authResponse =
|
||||
AuthResponse(version: 5, signature: signature.toRaw, record: some(record))
|
||||
authResponseNoRecord =
|
||||
AuthResponse(version: 5, signature: signature.toRaw, record: none(enr.Record))
|
||||
|
||||
rlp.encode(authResponse).toFile(inputsDir & "auth-response")
|
||||
rlp.encode(authResponseNoRecord).toFile(inputsDir & "auth-response-no-enr")
|
||||
|
||||
discard existsOrCreateDir(inputsDir)
|
||||
generate()
|
51
tests/fuzzing/discoveryv5/generate_packet.nim
Normal file
51
tests/fuzzing/discoveryv5/generate_packet.nim
Normal file
@ -0,0 +1,51 @@
|
||||
import
|
||||
std/[os, strutils],
|
||||
stew/shims/net,
|
||||
eth/[keys, rlp, trie/db],
|
||||
eth/p2p/discoveryv5/[protocol, discovery_db, enr, node, types, encoding],
|
||||
../fuzzing_helpers
|
||||
|
||||
template sourceDir: string = currentSourcePath.rsplit(DirSep, 1)[0]
|
||||
const inputsDir = sourceDir / "corpus" & DirSep
|
||||
|
||||
proc generate() =
|
||||
let
|
||||
rng = keys.newRng()
|
||||
privKey = PrivateKey.random(rng[])
|
||||
ip = some(ValidIpAddress.init("127.0.0.1"))
|
||||
port = Port(20301)
|
||||
dbb = DiscoveryDB.init(newMemoryDB())
|
||||
d = newProtocol(privKey, dbb, ip, port, port, rng = rng)
|
||||
|
||||
# Same as the on in the fuzz test to have at least one working packet for
|
||||
# the whoareyou-packet.
|
||||
toPrivKey = PrivateKey.fromHex(
|
||||
"5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d")[]
|
||||
toRecord = enr.Record.init(1, toPrivKey,
|
||||
some(ValidIpAddress.init("127.0.0.1")), Port(9000), Port(9000))[]
|
||||
toNode = newNode(toRecord)[]
|
||||
|
||||
block: # random packet
|
||||
# No handshake done obviously so a new packet will be a random packet.
|
||||
let
|
||||
reqId = RequestId.init(d.rng[])
|
||||
message = encodeMessage(PingMessage(enrSeq: d.localNode.record.seqNum), reqId)
|
||||
(data, _) = encodePacket(d.rng[], d.codec, toNode.id, toNode.address.get(),
|
||||
message, challenge = nil)
|
||||
|
||||
data.toFile(inputsDir & "random-packet")
|
||||
|
||||
block: # whoareyou packet
|
||||
var authTag: AuthTag
|
||||
var idNonce: IdNonce
|
||||
brHmacDrbgGenerate(d.rng[], authTag)
|
||||
brHmacDrbgGenerate(d.rng[], idNonce)
|
||||
|
||||
let challenge = Whoareyou(authTag: authTag, idNonce: idNonce, recordSeq: 0)
|
||||
var data = @(whoareyouMagic(toNode.id))
|
||||
data.add(rlp.encode(challenge[]))
|
||||
|
||||
data.toFile(inputsDir & "whoareyou-packet")
|
||||
|
||||
discard existsOrCreateDir(inputsDir)
|
||||
generate()
|
@ -1,17 +1,12 @@
|
||||
import
|
||||
streams, os, strutils, options,
|
||||
std/[os, strutils, options],
|
||||
stew/shims/net,
|
||||
eth/keys, eth/p2p/discoveryv5/enr
|
||||
eth/keys, eth/p2p/discoveryv5/enr,
|
||||
../fuzzing_helpers
|
||||
|
||||
template sourceDir: string = currentSourcePath.rsplit(DirSep, 1)[0]
|
||||
const inputsDir = sourceDir / "corpus"
|
||||
|
||||
proc toFile(data: seq[byte], fn: string) =
|
||||
var s = newFileStream(fn, fmWrite)
|
||||
for x in data:
|
||||
s.write(x)
|
||||
s.close()
|
||||
|
||||
proc generate() =
|
||||
let
|
||||
rng = newRng()
|
||||
|
8
tests/fuzzing/fuzzing_helpers.nim
Normal file
8
tests/fuzzing/fuzzing_helpers.nim
Normal file
@ -0,0 +1,8 @@
|
||||
import
|
||||
std/streams
|
||||
|
||||
proc toFile*(data: seq[byte], fn: string) =
|
||||
var s = newFileStream(fn, fmWrite)
|
||||
for x in data:
|
||||
s.write(x)
|
||||
s.close()
|
32
tests/fuzzing/rlp/rlp_decode.nim
Normal file
32
tests/fuzzing/rlp/rlp_decode.nim
Normal file
@ -0,0 +1,32 @@
|
||||
import
|
||||
testutils/fuzzing, chronicles,
|
||||
eth/rlp
|
||||
|
||||
type
|
||||
TestEnum = enum
|
||||
one = 1
|
||||
two = 2
|
||||
TestObject* = object
|
||||
test1: uint32
|
||||
test2: string
|
||||
|
||||
template testDecode(payload: openarray, T: type) =
|
||||
try:
|
||||
discard rlp.decode(payload, T)
|
||||
except RlpError as e:
|
||||
debug "Decode failed", err = e.msg
|
||||
|
||||
test:
|
||||
testDecode(payload, string)
|
||||
testDecode(payload, uint)
|
||||
testDecode(payload, uint8)
|
||||
testDecode(payload, uint16)
|
||||
testDecode(payload, uint32)
|
||||
testDecode(payload, uint64)
|
||||
testDecode(payload, int)
|
||||
testDecode(payload, bool)
|
||||
testDecode(payload, float64)
|
||||
testDecode(payload, seq[byte])
|
||||
testDecode(payload, (string, int32))
|
||||
testDecode(payload, TestEnum)
|
||||
testDecode(payload, TestObject)
|
Loading…
x
Reference in New Issue
Block a user