Qt: Make we shut down cleanly when exiting early.
[metze/wireshark/wip.git] / tools / fuzz-test.sh
1 #!/bin/bash
2 #
3 # Fuzz-testing script for TShark
4 #
5 # This script uses Editcap to add random errors ("fuzz") to a set of
6 # capture files specified on the command line.  It runs TShark on
7 # each fuzzed file and checks for errors.  The files are processed
8 # repeatedly until an error is found.
9 #
10 # Copyright 2013 Gerald Combs <gerald@wireshark.org>
11 #
12 # Wireshark - Network traffic analyzer
13 # By Gerald Combs <gerald@wireshark.org>
14 # Copyright 1998 Gerald Combs
15 #
16 # This program is free software; you can redistribute it and/or
17 # modify it under the terms of the GNU General Public License
18 # as published by the Free Software Foundation; either version 2
19 # of the License, or (at your option) any later version.
20 #
21 # This program is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24 # GNU General Public License for more details.
25 #
26 # You should have received a copy of the GNU General Public License
27 # along with this program; if not, write to the Free Software
28 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
29
30 TEST_TYPE="fuzz"
31 # shellcheck source=tools/test-common.sh
32 . `dirname $0`/test-common.sh || exit 1
33
34 # Sanity check to make sure we can find our plugins. Zero or less disables.
35 MIN_PLUGINS=0
36
37 # Did we catch a signal?
38 DONE=0
39
40 # Perform a two pass analysis on the capture file?
41 TWO_PASS=
42
43 # Specific config profile ?
44 CONFIG_PROFILE=
45
46 # Run under valgrind ?
47 VALGRIND=0
48
49 # Run under AddressSanitizer ?
50 ASAN=$CONFIGURED_WITH_ASAN
51
52 # Don't skip any byte from being changed
53 CHANGE_OFFSET=0
54
55 # The maximum permitted amount of memory leaked. Eventually this should be
56 # worked down to zero, but right now that would fail on every single capture.
57 # Only has effect when running under valgrind.
58 MAX_LEAK=`expr 1024 \* 100`
59
60 # To do: add options for file names and limits
61 while getopts "2b:C:d:e:agp:P:o:" OPTCHAR ; do
62     case $OPTCHAR in
63         a) ASAN=1 ;;
64         2) TWO_PASS="-2 " ;;
65         b) WIRESHARK_BIN_DIR=$OPTARG ;;
66         C) CONFIG_PROFILE="-C $OPTARG " ;;
67         d) TMP_DIR=$OPTARG ;;
68         e) ERR_PROB=$OPTARG ;;
69         g) VALGRIND=1 ;;
70         p) MAX_PASSES=$OPTARG ;;
71         P) MIN_PLUGINS=$OPTARG ;;
72         o) CHANGE_OFFSET=$OPTARG ;;
73     esac
74 done
75 shift $(($OPTIND - 1))
76
77 ### usually you won't have to change anything below this line ###
78
79 ws_bind_exec_paths
80 ws_check_exec "$TSHARK" "$EDITCAP" "$CAPINFOS" "$DATE" "$TMP_DIR"
81
82 COMMON_ARGS="${CONFIG_PROFILE}${TWO_PASS}"
83 KEEP=
84 PACKET_RANGE=
85 if [ $VALGRIND -eq 1 ]; then
86     RUNNER="`dirname $0`/valgrind-wireshark.sh"
87     COMMON_ARGS="-b $WIRESHARK_BIN_DIR $COMMON_ARGS"
88     declare -a RUNNER_ARGS=("" "-T")
89     # Valgrind requires more resources, so permit 1.5x memory and 3x time
90     # (1.5x time is too small for a few large captures in the menagerie)
91     MAX_CPU_TIME=`expr 3 \* $MAX_CPU_TIME`
92     MAX_VMEM=`expr 3 \* $MAX_VMEM / 2`
93     # Valgrind is slow. Trim captures to the first 100k packets so that
94     # we don't time out.
95     KEEP=-r
96     PACKET_RANGE=1-100000
97 else
98     # Not using valgrind, use regular tshark.
99     # TShark arguments (you won't have to change these)
100     # n Disable network object name resolution
101     # V Print a view of the details of the packet rather than a one-line summary of the packet
102     # x Cause TShark to print a hex and ASCII dump of the packet data after printing the summary or details
103     # r Read packet data from the following infile
104     RUNNER="$TSHARK"
105     declare -a RUNNER_ARGS=("-nVxr" "-nr")
106     # Running with a read filter but without generating the tree exposes some
107     # "More than 100000 items in tree" bugs.
108     # Not sure if we want to add even more cycles to the fuzz bot's work load...
109     #declare -a RUNNER_ARGS=("${CONFIG_PROFILE}${TWO_PASS}-nVxr" "${CONFIG_PROFILE}${TWO_PASS}-nr" "-Yframe ${CONFIG_PROFILE}${TWO_PASS}-nr")
110 fi
111
112
113 # Make sure we have a valid test set
114 FOUND=0
115 for CF in "$@" ; do
116     if [ "$OSTYPE" == "cygwin" ] ; then
117         CF=`cygpath --windows "$CF"`
118     fi
119     "$CAPINFOS" "$CF" > /dev/null 2>&1 && FOUND=1
120     if [ $FOUND -eq 1 ] ; then break ; fi
121 done
122
123 if [ $FOUND -eq 0 ] ; then
124     cat <<FIN
125 Error: No valid capture files found.
126
127 Usage: `basename $0` [-2] [-b bin_dir] [-C config_profile] [-d work_dir] [-e error probability] [-o changes offset] [-g] [-a] [-p passes] capture file 1 [capture file 2]...
128 FIN
129     exit 1
130 fi
131
132 PLUGIN_COUNT=`$TSHARK -G plugins | grep dissector | wc -l`
133 if [ $MIN_PLUGINS -gt 0 -a $PLUGIN_COUNT -lt $MIN_PLUGINS ] ; then
134     echo "Warning: Found fewer plugins than expected ($PLUGIN_COUNT vs $MIN_PLUGINS)."
135     exit 1
136 fi
137
138 if [ $ASAN -ne 0 ]; then
139     echo -n "ASan enabled. Virtual memory limit is "
140     ulimit -v
141 else
142     echo "ASan disabled. Virtual memory limit is $MAX_VMEM"
143 fi
144
145 HOWMANY="forever"
146 if [ $MAX_PASSES -gt 0 ]; then
147     HOWMANY="$MAX_PASSES passes"
148 fi
149 echo -n "Running $RUNNER $COMMON_ARGS with args: "
150 printf "\"%s\" " "${RUNNER_ARGS[@]}"
151 echo "($HOWMANY)"
152 echo ""
153
154 # Clean up on <ctrl>C, etc
155 trap "DONE=1; echo 'Caught signal'" HUP INT TERM
156
157
158 # Iterate over our capture files.
159 PASS=0
160 while [ \( $PASS -lt $MAX_PASSES -o $MAX_PASSES -lt 1 \) -a $DONE -ne 1 ] ; do
161     let PASS=$PASS+1
162     echo "Starting pass $PASS:"
163     RUN=0
164
165     for CF in "$@" ; do
166         if [ $DONE -eq 1 ]; then
167             break # We caught a signal
168         fi
169         RUN=$(( $RUN + 1 ))
170         if [ $(( $RUN % 50 )) -eq 0 ] ; then
171             echo "    [Pass $PASS]"
172         fi
173         if [ "$OSTYPE" == "cygwin" ] ; then
174             CF=`cygpath --windows "$CF"`
175         fi
176         echo -n "    $CF: "
177
178         "$CAPINFOS" "$CF" > /dev/null 2> $TMP_DIR/$ERR_FILE
179         RETVAL=$?
180         if [ $RETVAL -eq 1 -o $RETVAL -eq 2 ] ; then
181             echo "Not a valid capture file"
182             rm -f $TMP_DIR/$ERR_FILE
183             continue
184         elif [ $RETVAL -ne 0 -a $DONE -ne 1 ] ; then
185             # Some other error
186             ws_exit_error
187         fi
188
189         DISSECTOR_BUG=0
190         VG_ERR_CNT=0
191
192         "$EDITCAP" -E $ERR_PROB -o $CHANGE_OFFSET $KEEP "$CF" $TMP_DIR/$TMP_FILE $PACKET_RANGE > /dev/null 2>&1
193         if [ $? -ne 0 ] ; then
194             "$EDITCAP" -E $ERR_PROB -o $CHANGE_OFFSET $KEEP -T ether "$CF" $TMP_DIR/$TMP_FILE $PACKET_RANGE \
195                 > /dev/null 2>&1
196             if [ $? -ne 0 ] ; then
197                 echo "Invalid format for editcap"
198                 continue
199             fi
200         fi
201
202         RUNNER_PIDS=
203         RUNNER_ERR_FILES=
204         for ARGS in "${RUNNER_ARGS[@]}" ; do
205             if [ $DONE -eq 1 ]; then
206                 break # We caught a signal
207             fi
208             echo -n "($ARGS) "
209             echo -e "Command and args: $RUNNER $ARGS\n" > $TMP_DIR/$ERR_FILE
210
211             # Run in a child process with limits.
212             (
213                 # Set some limits to the child processes, e.g. stop it if
214                 # it's running longer than MAX_CPU_TIME seconds. (ulimit
215                 # is not supported well on cygwin - it shows some warnings -
216                 # and the features we use may not all be supported on some
217                 # UN*X platforms.)
218                 ulimit -S -t $MAX_CPU_TIME -s $MAX_STACK
219
220                 # Allow core files to be generated
221                 ulimit -c unlimited
222
223                 # Don't enable ulimit -v when using ASAN. See
224                 # https://github.com/google/sanitizers/wiki/AddressSanitizer#ulimit--v
225                 if [ $ASAN -eq 0 ]; then
226                     ulimit -S -v $MAX_VMEM
227                 fi
228
229                 SUBSHELL_PID=$($SHELL -c 'echo $PPID')
230
231                 "$RUNNER" $COMMON_ARGS $ARGS $TMP_DIR/$TMP_FILE \
232                     > /dev/null 2>> $TMP_DIR/$ERR_FILE.$SUBSHELL_PID
233             ) &
234             RUNNER_PID=$!
235             RUNNER_PIDS="$RUNNER_PIDS $RUNNER_PID"
236             RUNNER_ERR_FILES="$RUNNER_ERR_FILES $TMP_DIR/$ERR_FILE.$RUNNER_PID"
237         done
238
239         for RUNNER_PID in $RUNNER_PIDS ; do
240             wait $RUNNER_PID
241             RUNNER_RETVAL=$?
242             mv $TMP_DIR/$ERR_FILE.$RUNNER_PID $TMP_DIR/$ERR_FILE
243
244             # Uncomment the next two lines to enable dissector bug
245             # checking.
246             #grep -i "dissector bug" $TMP_DIR/$ERR_FILE \
247                 #    > /dev/null 2>&1 && DISSECTOR_BUG=1
248
249             if [ $VALGRIND -eq 1 -a $DONE -ne 1 ]; then
250                 VG_ERR_CNT=`grep "ERROR SUMMARY:" $TMP_DIR/$ERR_FILE | cut -f4 -d' '`
251                 VG_DEF_LEAKED=`grep "definitely lost:" $TMP_DIR/$ERR_FILE | cut -f7 -d' ' | tr -d ,`
252                 VG_IND_LEAKED=`grep "indirectly lost:" $TMP_DIR/$ERR_FILE | cut -f7 -d' ' | tr -d ,`
253                 VG_TOTAL_LEAKED=`expr $VG_DEF_LEAKED + $VG_IND_LEAKED`
254                 if [ $RUNNER_RETVAL -ne 0 ] ; then
255                     echo "General Valgrind failure."
256                     VG_ERR_CNT=1
257                 elif [ "$VG_TOTAL_LEAKED" -gt "$MAX_LEAK" ] ; then
258                     echo "Definitely + indirectly ($VG_DEF_LEAKED + $VG_IND_LEAKED) exceeds max ($MAX_LEAK)."
259                     echo "Definitely + indirectly ($VG_DEF_LEAKED + $VG_IND_LEAKED) exceeds max ($MAX_LEAK)." >> $TMP_DIR/$ERR_FILE
260                     VG_ERR_CNT=1
261                 fi
262                 if grep -q "Valgrind cannot continue" $TMP_DIR/$ERR_FILE; then
263                     echo "Valgrind unable to continue."
264                     VG_ERR_CNT=-1
265                 fi
266             fi
267
268             if [ $DONE -ne 1 -a \( $RUNNER_RETVAL -ne 0 -o $DISSECTOR_BUG -ne 0 -o $VG_ERR_CNT -ne 0 \) ] ; then
269                 rm -f $RUNNER_ERR_FILES
270                 ws_exit_error
271             fi
272         done
273
274         echo " OK"
275         rm -f $TMP_DIR/$TMP_FILE $TMP_DIR/$ERR_FILE
276     done
277 done