mirror of https://github.com/status-im/nim-eth.git
Add very basic fuzzing setup for discovery
This commit is contained in:
parent
7cb5ac050b
commit
d535bb1ade
|
@ -0,0 +1,68 @@
|
|||
import
|
||||
streams, posix, sequtils, strutils, chronicles, chronos, byteutils,
|
||||
eth/p2p/[discovery, kademlia, enode], eth/[keys, rlp],
|
||||
../../p2p/p2p_test_helper
|
||||
|
||||
template fuzz(body) =
|
||||
# For code we want to fuzz.
|
||||
try:
|
||||
body
|
||||
except:
|
||||
let e = getCurrentException()
|
||||
debug "Fuzzer input created exception", exception=e.name, trace=e.repr
|
||||
discard kill(getpid(), SIGSEGV)
|
||||
|
||||
template noFuzz(body) =
|
||||
# For code not in the scope of the test.
|
||||
# Lets not have false negatives due to possible issues in this code.
|
||||
try:
|
||||
body
|
||||
except:
|
||||
let e = getCurrentException()
|
||||
debug "Exception out of scope of the fuzzing target",
|
||||
exception=e.name, trace=e.repr
|
||||
return
|
||||
|
||||
const DefaultListeningPort = 30303
|
||||
|
||||
proc fuzzTest() =
|
||||
var
|
||||
msg: seq[byte]
|
||||
address: Address
|
||||
targetNode: DiscoveryProtocol
|
||||
|
||||
noFuzz:
|
||||
# Set up a discovery node, this is the node we target with fuzzing
|
||||
let
|
||||
targetNodeKey = initPrivateKey("a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a617")
|
||||
targetNodeAddr = localAddress(DefaultListeningPort)
|
||||
targetNode = newDiscoveryProtocol(targetNodeKey, targetNodeAddr, @[])
|
||||
# Create the transport as else replies on the messages send will fail.
|
||||
targetNode.open()
|
||||
|
||||
# Read input from stdin (fastest for AFL)
|
||||
let s = newFileStream(stdin)
|
||||
# We use binary files as with hex we can get lots of "not hex" failures
|
||||
var input = s.readAll()
|
||||
s.close()
|
||||
# Remove newline if it is there
|
||||
input.removeSuffix
|
||||
# TODO: is there a better/faster way?
|
||||
let payload = input.mapIt(it.byte)
|
||||
|
||||
# Sending raw payload is possible but won't find us much. We need a hash and
|
||||
# a signature, and without it there is a big chance it will always result in
|
||||
# "Wrong msg mac from" error.
|
||||
let nodeKey = initPrivateKey("a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a618")
|
||||
msg = packData(payload, nodeKey)
|
||||
address = localAddress(DefaultListeningPort + 1)
|
||||
|
||||
try:
|
||||
targetNode.receive(address, msg)
|
||||
# These errors are also catched in `processClient` in discovery.nim
|
||||
# TODO: move them a layer down in discovery so we can do a cleaner test there?
|
||||
except RlpError, DiscProtocolError:
|
||||
debug "Receive failed", err = getCurrentExceptionMsg()
|
||||
|
||||
fuzz:
|
||||
fuzzTest()
|
|
@ -0,0 +1,73 @@
|
|||
import
|
||||
chronos, times, byteutils, stint, chronicles, streams, nimcrypto, os,
|
||||
strformat, strutils, eth/p2p/[discovery, kademlia], eth/[keys, rlp],
|
||||
../../p2p/p2p_test_helper
|
||||
|
||||
template sourceDir: string = currentSourcePath.rsplit(DirSep, 1)[0]
|
||||
const inputsDir = &"{sourceDir}{DirSep}generated-input{DirSep}"
|
||||
|
||||
proc toFile(data: Bytes, fn: string) =
|
||||
var s = newFileStream(fn, fmWrite)
|
||||
for x in data:
|
||||
s.write(x)
|
||||
s.close()
|
||||
|
||||
const EXPIRATION = 3600 * 24 * 365 * 10
|
||||
proc expiration(): uint32 = uint32(epochTime() + EXPIRATION)
|
||||
|
||||
proc generate() =
|
||||
## Generate some valid inputs where one can start fuzzing with
|
||||
let
|
||||
fromAddr = localAddress(30303)
|
||||
toAddr = localAddress(30304)
|
||||
peerKey = initPrivateKey("a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a617")
|
||||
|
||||
# valid data for a Ping packet
|
||||
block:
|
||||
let payload = rlp.encode((4, fromAddr, toAddr, expiration())).toRange
|
||||
let encodedData = @[1.byte] & payload.toSeq()
|
||||
debug "Ping", data=byteutils.toHex(encodedData)
|
||||
|
||||
encodedData.toFile(inputsDir & "ping")
|
||||
|
||||
# valid data for a Pong packet
|
||||
block:
|
||||
let token = keccak256.digest(@[0])
|
||||
let payload = rlp.encode((toAddr, token , expiration())).toRange
|
||||
let encodedData = @[2.byte] & payload.toSeq()
|
||||
debug "Pong", data=byteutils.toHex(encodedData)
|
||||
|
||||
encodedData.toFile(inputsDir & "pong")
|
||||
|
||||
# valid data for a FindNode packet
|
||||
block:
|
||||
var data: array[64, byte]
|
||||
data[32 .. ^1] = peerKey.getPublicKey.toNodeId().toByteArrayBE()
|
||||
let payload = rlp.encode((data, expiration())).toRange
|
||||
let encodedData = @[3.byte] & payload.toSeq()
|
||||
debug "FindNode", data=byteutils.toHex(encodedData)
|
||||
|
||||
encodedData.toFile(inputsDir & "findnode")
|
||||
|
||||
# valid data for a Neighbours packet
|
||||
block:
|
||||
let
|
||||
n1Addr = localAddress(30305)
|
||||
n2Addr = localAddress(30306)
|
||||
n1Key = initPrivateKey("a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a618")
|
||||
n2Key = initPrivateKey("a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a619")
|
||||
|
||||
type Neighbour = tuple[ip: IpAddress, udpPort, tcpPort: Port, pk: PublicKey]
|
||||
var nodes = newSeqOfCap[Neighbour](2)
|
||||
|
||||
nodes.add((n1Addr.ip, n1Addr.udpPort, n1Addr.tcpPort, n1Key.getPublicKey()))
|
||||
nodes.add((n2Addr.ip, n2Addr.udpPort, n2Addr.tcpPort, n2Key.getPublicKey()))
|
||||
|
||||
let payload = rlp.encode((nodes, expiration())).toRange
|
||||
let encodedData = @[4.byte] & payload.toSeq()
|
||||
debug "Neighbours", data=byteutils.toHex(encodedData)
|
||||
|
||||
encodedData.toFile(inputsDir & "neighbours")
|
||||
|
||||
discard existsOrCreateDir(inputsDir)
|
||||
generate()
|
|
@ -0,0 +1,18 @@
|
|||
# TODO: make this configurable when more fuzzing targets
|
||||
cd "discovery"
|
||||
|
||||
if not dirExists("generated-input"):
|
||||
exec "nim c -r generate"
|
||||
|
||||
if not fileExists("fuzz"):
|
||||
# Requires afl-gcc to be installed
|
||||
# TODO: add + test option for clang
|
||||
exec "nim c --cc=gcc --gcc.exe=afl-gcc --gcc.linkerexe=afl-gcc fuzz"
|
||||
|
||||
if dirExists("output"):
|
||||
exec "afl-fuzz -i - -o output -M fuzzer01 -- ./fuzz"
|
||||
else:
|
||||
exec "afl-fuzz -i generated-input -o output -M fuzzer01 -- ./fuzz"
|
||||
|
||||
# TODO: how to add slaves for multiple cores in nimscript?
|
||||
# afl-fuzz -i generated-input -o output -S fuzzer02 -- ./fuzz
|
|
@ -1,5 +1,5 @@
|
|||
import
|
||||
unittest, chronos, eth/[keys, p2p], eth/p2p/[discovery, enode]
|
||||
unittest, chronos, nimcrypto, eth/[keys, p2p], eth/p2p/[discovery, enode]
|
||||
|
||||
var nextPort = 30303
|
||||
|
||||
|
@ -32,3 +32,9 @@ template asyncTest*(name, body: untyped) =
|
|||
test name:
|
||||
proc scenario {.async.} = body
|
||||
waitFor scenario()
|
||||
|
||||
proc packData*(payload: seq[byte], pk: PrivateKey): seq[byte] =
|
||||
let
|
||||
signature = @(pk.signMessage(payload).getRaw())
|
||||
msgHash = keccak256.digest(signature & payload)
|
||||
result = @(msgHash.data) & signature & payload
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#
|
||||
|
||||
import
|
||||
sequtils, unittest, chronos, byteutils, nimcrypto,
|
||||
sequtils, unittest, chronos, byteutils,
|
||||
eth/[keys, rlp], eth/p2p/[discovery, kademlia, enode],
|
||||
./p2p_test_helper
|
||||
|
||||
|
@ -16,12 +16,6 @@ proc nodeIdInNodes(id: NodeId, nodes: openarray[Node]): bool =
|
|||
for n in nodes:
|
||||
if id == n.id: return true
|
||||
|
||||
proc packData(payload: seq[byte], pk: PrivateKey): seq[byte] =
|
||||
let
|
||||
signature = @(pk.signMessage(payload).getRaw())
|
||||
msgHash = keccak256.digest(signature & payload)
|
||||
result = @(msgHash.data) & signature & payload
|
||||
|
||||
proc test() {.async.} =
|
||||
suite "Discovery Tests":
|
||||
let
|
||||
|
@ -96,4 +90,4 @@ proc test() {.async.} =
|
|||
# msg mac
|
||||
bootNode.receive(address, packData(@[], nodeKey))
|
||||
|
||||
waitFor test()
|
||||
waitFor test()
|
||||
|
|
Loading…
Reference in New Issue