Tests: Add fuzzing infrastructure.

Signed-off-by: Wilson Snyder <wsnyder@wsnyder.org>
This commit is contained in:
Eric Rippey 2019-10-16 22:18:35 -04:00 committed by Wilson Snyder
parent 6081c262f2
commit 77f79f0114
9 changed files with 240 additions and 0 deletions

View File

@ -716,6 +716,17 @@ enviroment can check their branches too by doing the following:
* Under a Travis CI project click More options > Settings in order to set up a
cron job on a particular branch
=== Fuzzing
There are scripts included to facilitate fuzzing of Verilator. These have
been successfully used to find a number of bugs in the frontend.
The scripts are based on using http://lcamtuf.coredump.cx/afl/[American fuzzy lop]
on a Debian-like system.
To get started, cd to "nodist/fuzzer/" and run "./all". A sudo password
may be required to setup the system for fuzzing.
== Debugging
=== --debug

4
nodist/fuzzer/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
wrapper
dictionary/
in*
lex.yy.cc

48
nodist/fuzzer/actual_fail Executable file
View File

@ -0,0 +1,48 @@
#!/usr/bin/env python3
######################################################################
# DESCRIPTION: Fuzzer result checker
#
# Copyright 2019-2019 by Eric Rippey. This package is free software; you can
# redistribute it and/or modify it under the terms of either the GNU Lesser
# General Public License Version 3 or the Perl Artistic License Version 2.0.
######################################################################
# This script is designed to rerun examples to see whether they have
# unexpected types of output besides the ones that afl-fuzz detects as
# such.
from glob import glob
from subprocess import getstatusoutput
from argparse import ArgumentParser
def interesting(s):
if 'assert' in s: return 1
if 'Assert' in s: return 1
if 'Aborted' in s: return 1
if 'terminate' in s:
if 'unterminated' in s:
return 0
return 1
if 'Segmentation' in s:
return 1
if 'internal error' in s:
return 1
return 0
def main():
p = ArgumentParser()
p.add_argument('--dir',default='out1/queue')
args = p.parse_args()
for infile in glob(args.dir+'/*'):
# Input filenames are known not to contain spaces or other unusual
# characters, therefore this works.
status,output = getstatusoutput('../../bin/verilator_bin --cc '+infile)
if interesting(output):
print(infile)
print(status)
print(output)
if __name__=='__main__':
main()

17
nodist/fuzzer/all Executable file
View File

@ -0,0 +1,17 @@
#!/bin/bash
######################################################################
# DESCRIPTION: Fuzzer one-line setup & run
#
# Copyright 2019-2019 by Eric Rippey. This package is free software; you can
# redistribute it and/or modify it under the terms of either the GNU Lesser
# General Public License Version 3 or the Perl Artistic License Version 2.0.
######################################################################
# Run all steps needed to configure and start fuzzer
# Note that this assumes the system is a Debian-like Linux distrubution
set -e
sudo ./setup_root
./setup_user
./run

View File

@ -0,0 +1,68 @@
#!/usr/bin/env python3
######################################################################
# DESCRIPTION: Fuzzer dictionary generator
#
# Copyright 2019-2019 by Eric Rippey. This package is free software; you can
# redistribute it and/or modify it under the terms of either the GNU Lesser
# General Public License Version 3 or the Perl Artistic License Version 2.0.
######################################################################
# Attempts to pull a list of keywords out of the Flex input
# These are then put in a dictionary of "interesting" sequences
# This will be used to help the fuzzer pick interesting inputs more quickly.
from subprocess import getstatusoutput
from os import system
def take_while(f,a):
# any(a) => (a->bool)->[a]->[a]
# Does the same think as Haskell's takewhile.
out = []
for elem in a:
if f(elem):
out.append(elem)
else:
return out
return out
def skip_while(f,a):
# any(a) => (a->bool)->[a]->[a]
# Basically, the opposite thing from skipwhile
while len(a) and f(a[0]):
a = a[1:]
return a
def print_lines(a):
# printable(a) => [a]->void
for elem in a:
print(elem)
def write_file(filename,contents):
# str->str->void
f = open(filename,'w')
f.write(contents)
def parse_line(s):
# str->maybe str
if len(s)==0: return
part = skip_while(lambda x: x!='"',s)
if len(part)==0 or part[0]!='"': return None
literal_part = take_while(lambda x: x!='"',part[1:])
return ''.join(filter(lambda x: x!='\\',literal_part))
def main():
status,output = getstatusoutput('flex -T ../../src/verilog.l')
assert status==0
lines = output.splitlines()
lines = take_while(lambda x: 'beginning dump of nfa' not in x,lines)
tokens = set(filter(lambda x: x,map(parse_line,lines)))
dirname = 'dictionary'
r = system('mkdir -p '+dirname)
assert(r==0)
for i,token in enumerate(tokens):
write_file(dirname+'/'+str(i),token)
if __name__=='__main__':
main()

12
nodist/fuzzer/run Executable file
View File

@ -0,0 +1,12 @@
#!/bin/bash
######################################################################
# DESCRIPTION: Fuzzer run script
#
# Copyright 2019-2019 by Eric Rippey. This package is free software; you can
# redistribute it and/or modify it under the terms of either the GNU Lesser
# General Public License Version 3 or the Perl Artistic License Version 2.0.
######################################################################
# Actually do the fuzzing. Note that this will not terminate in any reasonable
# amount of time. However, it will give updates on its progress.
afl-fuzz -i in1 -o out1 -x dictionary ./wrapper --cc @@

23
nodist/fuzzer/setup_root Executable file
View File

@ -0,0 +1,23 @@
#!/bin/bash
######################################################################
# DESCRIPTION: Fuzzer setup to be run as root
#
# Copyright 2019-2019 by Eric Rippey. This package is free software; you can
# redistribute it and/or modify it under the terms of either the GNU Lesser
# General Public License Version 3 or the Perl Artistic License Version 2.0.
######################################################################
# This is the portion of the fuzzer setup that must be run as root.
# Note that this assumes a Debian-like distribution.
set -e
# Get dependencies
apt-get install afl mdm
apt-get build-dep verilator
# Run a couple pieces of setup which should speed up the fuzzer
echo core >/proc/sys/kernel/core_pattern
cd /sys/devices/system/cpu
echo performance | tee cpu*/cpufreq/scaling_governor

31
nodist/fuzzer/setup_user Executable file
View File

@ -0,0 +1,31 @@
#!/bin/bash
######################################################################
# DESCRIPTION: Fuzzer setup to be run as a normal user
#
# Copyright 2019-2019 by Eric Rippey. This package is free software; you can
# redistribute it and/or modify it under the terms of either the GNU Lesser
# General Public License Version 3 or the Perl Artistic License Version 2.0.
######################################################################
# This is the portion of the setup for fuzzing that does not require root access.
set -e
# Build instrumented version of verilator
pushd ../..
autoconf
AFL_HARDEN=1 CC=afl-gcc CXX=afl-g++ ./configure $(cd ..; pwd)
make clean
make -j $(ncpus)
popd
# Create a listing of likely snippets for the fuzzer to use.
# Not essential, but makes things likely to be found faster.
./generate_dictionary
# Set up input directory
mkdir in1
echo "module m; initial \$display(\"Hello world!\n\"); endmodule" > in1/1.v
# Compile wrapper program
AFL_HARDEN=1 CXX=afl-g++ make wrapper

26
nodist/fuzzer/wrapper.cpp Normal file
View File

@ -0,0 +1,26 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator fuzzing wrapper for verilator_bin
//
// Copyright 2019 by Eric Rippey. This program is free software; you can
// redistribute it and/or modify it under the terms of either the GNU
// Lesser General Public License Version 3 or the Perl Artistic License
// Version 2.0.
//*************************************************************************
#include <unistd.h>
#include <cstdlib>
#include <cassert>
// The purpose of this script is to make sure that the results folder that
// is generated by running verilator does not change the results of
// subsequent runs.
// This does slow down the execution to some degree but makes the results
// more reliable.
int main(int argc, char **argv, char **envp) {
auto r = system("rm -rf obj_dir");
assert(r==0);
return execve("../../bin/verilator_bin",argv,envp);
}