aboutsummaryrefslogtreecommitdiffstats
path: root/tools/fuzz-test.sh
blob: d763611d3b118c935996b4b2069145c15857c59e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
#!/bin/bash
#
# Fuzz-testing script for TShark
#
# This script uses Editcap to add random errors ("fuzz") to a set of
# capture files specified on the command line.  It runs TShark on
# each fuzzed file and checks for errors.  The files are processed
# repeatedly until an error is found.
#
# Copyright 2013 Gerald Combs <gerald@wireshark.org>
#
# Wireshark - Network traffic analyzer
# By Gerald Combs <gerald@wireshark.org>
# Copyright 1998 Gerald Combs
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

TEST_TYPE="fuzz"
# shellcheck source=tools/test-common.sh
. `dirname $0`/test-common.sh || exit 1

# Sanity check to make sure we can find our plugins. Zero or less disables.
MIN_PLUGINS=0

# Did we catch a signal?
DONE=0

# Perform a two pass analysis on the capture file?
TWO_PASS=

# Specific config profile ?
CONFIG_PROFILE=

# Run under valgrind ?
VALGRIND=0

# Run under AddressSanitizer ?
ASAN=$CONFIGURED_WITH_ASAN

# Don't skip any byte from being changed
CHANGE_OFFSET=0

# The maximum permitted amount of memory leaked. Eventually this should be
# worked down to zero, but right now that would fail on every single capture.
# Only has effect when running under valgrind.
MAX_LEAK=`expr 1024 \* 100`

# To do: add options for file names and limits
while getopts "2b:C:d:e:agp:P:o:" OPTCHAR ; do
    case $OPTCHAR in
        a) ASAN=1 ;;
        2) TWO_PASS="-2 " ;;
        b) WIRESHARK_BIN_DIR=$OPTARG ;;
        C) CONFIG_PROFILE="-C $OPTARG " ;;
        d) TMP_DIR=$OPTARG ;;
        e) ERR_PROB=$OPTARG ;;
        g) VALGRIND=1 ;;
        p) MAX_PASSES=$OPTARG ;;
        P) MIN_PLUGINS=$OPTARG ;;
        o) CHANGE_OFFSET=$OPTARG ;;
    esac
done
shift $(($OPTIND - 1))

### usually you won't have to change anything below this line ###

ws_bind_exec_paths
ws_check_exec "$TSHARK" "$EDITCAP" "$CAPINFOS" "$DATE" "$TMP_DIR"

COMMON_ARGS="${CONFIG_PROFILE}${TWO_PASS}"
if [ $VALGRIND -eq 1 ]; then
    RUNNER="`dirname $0`/valgrind-wireshark.sh"
    COMMON_ARGS="-b $WIRESHARK_BIN_DIR $COMMON_ARGS"
    declare -a RUNNER_ARGS=("" "-T")
    # Valgrind requires more resources, so permit 1.5x memory and 3x time
    # (1.5x time is too small for a few large captures in the menagerie)
    MAX_CPU_TIME=`expr 3 \* $MAX_CPU_TIME`
    MAX_VMEM=`expr 3 \* $MAX_VMEM / 2`
else
    # Not using valgrind, use regular tshark.
    # TShark arguments (you won't have to change these)
    # n Disable network object name resolution
    # V Print a view of the details of the packet rather than a one-line summary of the packet
    # x Cause TShark to print a hex and ASCII dump of the packet data after printing the summary or details
    # r Read packet data from the following infile
    RUNNER="$TSHARK"
    declare -a RUNNER_ARGS=("-nVxr" "-nr")
    # Running with a read filter but without generating the tree exposes some
    # "More than 100000 items in tree" bugs.
    # Not sure if we want to add even more cycles to the fuzz bot's work load...
    #declare -a RUNNER_ARGS=("${CONFIG_PROFILE}${TWO_PASS}-nVxr" "${CONFIG_PROFILE}${TWO_PASS}-nr" "-Yframe ${CONFIG_PROFILE}${TWO_PASS}-nr")
fi


# Make sure we have a valid test set
FOUND=0
for CF in "$@" ; do
    if [ "$OSTYPE" == "cygwin" ] ; then
        CF=`cygpath --windows "$CF"`
    fi
    "$CAPINFOS" "$CF" > /dev/null 2>&1 && FOUND=1
    if [ $FOUND -eq 1 ] ; then break ; fi
done

if [ $FOUND -eq 0 ] ; then
    cat <<FIN
Error: No valid capture files found.

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]...
FIN
    exit 1
fi

PLUGIN_COUNT=`$TSHARK -G plugins | grep dissector | wc -l`
if [ $MIN_PLUGINS -gt 0 -a $PLUGIN_COUNT -lt $MIN_PLUGINS ] ; then
    echo "Warning: Found fewer plugins than expected ($PLUGIN_COUNT vs $MIN_PLUGINS)."
    exit 1
fi

if [ $ASAN -ne 0 ]; then
    echo -n "ASan enabled. Virtual memory limit is "
    ulimit -v
else
    echo "ASan disabled. Virtual memory limit is $MAX_VMEM"
fi

HOWMANY="forever"
if [ $MAX_PASSES -gt 0 ]; then
    HOWMANY="$MAX_PASSES passes"
fi
echo -n "Running $RUNNER $COMMON_ARGS with args: "
printf "\"%s\" " "${RUNNER_ARGS[@]}"
echo "($HOWMANY)"
echo ""

# Clean up on <ctrl>C, etc
trap "DONE=1; echo 'Caught signal'" HUP INT TERM


# Iterate over our capture files.
PASS=0
while [ \( $PASS -lt $MAX_PASSES -o $MAX_PASSES -lt 1 \) -a $DONE -ne 1 ] ; do
    let PASS=$PASS+1
    echo "Starting pass $PASS:"
    RUN=0

    for CF in "$@" ; do
        if [ $DONE -eq 1 ]; then
            break # We caught a signal
        fi
        RUN=$(( $RUN + 1 ))
        if [ $(( $RUN % 50 )) -eq 0 ] ; then
            echo "    [Pass $PASS]"
        fi
        if [ "$OSTYPE" == "cygwin" ] ; then
            CF=`cygpath --windows "$CF"`
        fi
        echo -n "    $CF: "

        "$CAPINFOS" "$CF" > /dev/null 2> $TMP_DIR/$ERR_FILE
        RETVAL=$?
        if [ $RETVAL -eq 1 -o $RETVAL -eq 2 ] ; then
            echo "Not a valid capture file"
            rm -f $TMP_DIR/$ERR_FILE
            continue
        elif [ $RETVAL -ne 0 -a $DONE -ne 1 ] ; then
            # Some other error
            ws_exit_error
        fi

        if [ $VALGRIND -eq 1 -a `ls -s $CF | cut -d' ' -f1` -gt 8000 ]; then
            echo "Too big for valgrind"
            continue
        fi

        DISSECTOR_BUG=0
        VG_ERR_CNT=0

        "$EDITCAP" -E $ERR_PROB -o $CHANGE_OFFSET "$CF" $TMP_DIR/$TMP_FILE > /dev/null 2>&1
        if [ $? -ne 0 ] ; then
            "$EDITCAP" -E $ERR_PROB -o $CHANGE_OFFSET -T ether "$CF" $TMP_DIR/$TMP_FILE \
                > /dev/null 2>&1
            if [ $? -ne 0 ] ; then
                echo "Invalid format for editcap"
                continue
            fi
        fi

        RUNNER_PIDS=
        RUNNER_ERR_FILES=
        for ARGS in "${RUNNER_ARGS[@]}" ; do
            if [ $DONE -eq 1 ]; then
                break # We caught a signal
            fi
            echo -n "($ARGS) "
            echo -e "Command and args: $RUNNER $ARGS\n" > $TMP_DIR/$ERR_FILE

            # Run in a child process with limits.
            (
                # Set some limits to the child processes, e.g. stop it if
                # it's running longer than MAX_CPU_TIME seconds. (ulimit
                # is not supported well on cygwin - it shows some warnings -
                # and the features we use may not all be supported on some
                # UN*X platforms.)
                ulimit -S -t $MAX_CPU_TIME -s $MAX_STACK

                # Allow core files to be generated
                ulimit -c unlimited

                # Don't enable ulimit -v when using ASAN. See
                # https://github.com/google/sanitizers/wiki/AddressSanitizer#ulimit--v
                if [ $ASAN -eq 0 ]; then
                    ulimit -S -v $MAX_VMEM
                fi

                SUBSHELL_PID=$($SHELL -c 'echo $PPID')

                "$RUNNER" $COMMON_ARGS $ARGS $TMP_DIR/$TMP_FILE \
                    > /dev/null 2>> $TMP_DIR/$ERR_FILE.$SUBSHELL_PID
            ) &
            RUNNER_PID=$!
            RUNNER_PIDS="$RUNNER_PIDS $RUNNER_PID"
            RUNNER_ERR_FILES="$RUNNER_ERR_FILES $TMP_DIR/$ERR_FILE.$RUNNER_PID"
        done

        for RUNNER_PID in $RUNNER_PIDS ; do
            wait $RUNNER_PID
            RUNNER_RETVAL=$?
            mv $TMP_DIR/$ERR_FILE.$RUNNER_PID $TMP_DIR/$ERR_FILE

            # Uncomment the next two lines to enable dissector bug
            # checking.
            #grep -i "dissector bug" $TMP_DIR/$ERR_FILE \
                #    > /dev/null 2>&1 && DISSECTOR_BUG=1

            if [ $VALGRIND -eq 1 -a $DONE -ne 1 ]; then
                VG_ERR_CNT=`grep "ERROR SUMMARY:" $TMP_DIR/$ERR_FILE | cut -f4 -d' '`
                VG_DEF_LEAKED=`grep "definitely lost:" $TMP_DIR/$ERR_FILE | cut -f7 -d' ' | tr -d ,`
                VG_IND_LEAKED=`grep "indirectly lost:" $TMP_DIR/$ERR_FILE | cut -f7 -d' ' | tr -d ,`
                VG_TOTAL_LEAKED=`expr $VG_DEF_LEAKED + $VG_IND_LEAKED`
                if [ $RUNNER_RETVAL -ne 0 ] ; then
                    echo "General Valgrind failure."
                    VG_ERR_CNT=1
                elif [ "$VG_TOTAL_LEAKED" -gt "$MAX_LEAK" ] ; then
                    echo "Definitely + indirectly ($VG_DEF_LEAKED + $VG_IND_LEAKED) exceeds max ($MAX_LEAK)."
                    VG_ERR_CNT=1
                fi
                if grep -q "Valgrind cannot continue" $TMP_DIR/$ERR_FILE; then
                    echo "Valgrind unable to continue."
                    VG_ERR_CNT=-1
                fi
            fi

            if [ $DONE -ne 1 -a \( $RUNNER_RETVAL -ne 0 -o $DISSECTOR_BUG -ne 0 -o $VG_ERR_CNT -ne 0 \) ] ; then
                rm -f $RUNNER_ERR_FILES
                ws_exit_error
            fi
        done

        echo " OK"
        rm -f $TMP_DIR/$TMP_FILE $TMP_DIR/$ERR_FILE
    done
done