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
|
import
|
||||||
unittest, chronos, eth/[keys, p2p], eth/p2p/[discovery, enode]
|
unittest, chronos, nimcrypto, eth/[keys, p2p], eth/p2p/[discovery, enode]
|
||||||
|
|
||||||
var nextPort = 30303
|
var nextPort = 30303
|
||||||
|
|
||||||
|
@ -32,3 +32,9 @@ template asyncTest*(name, body: untyped) =
|
||||||
test name:
|
test name:
|
||||||
proc scenario {.async.} = body
|
proc scenario {.async.} = body
|
||||||
waitFor scenario()
|
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
|
import
|
||||||
sequtils, unittest, chronos, byteutils, nimcrypto,
|
sequtils, unittest, chronos, byteutils,
|
||||||
eth/[keys, rlp], eth/p2p/[discovery, kademlia, enode],
|
eth/[keys, rlp], eth/p2p/[discovery, kademlia, enode],
|
||||||
./p2p_test_helper
|
./p2p_test_helper
|
||||||
|
|
||||||
|
@ -16,12 +16,6 @@ proc nodeIdInNodes(id: NodeId, nodes: openarray[Node]): bool =
|
||||||
for n in nodes:
|
for n in nodes:
|
||||||
if id == n.id: return true
|
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.} =
|
proc test() {.async.} =
|
||||||
suite "Discovery Tests":
|
suite "Discovery Tests":
|
||||||
let
|
let
|
||||||
|
|
Loading…
Reference in New Issue