Fuzz testing#

This chapter describes how to perform automated fuzz testing.

Fuzz testing or fuzzing is an automated software testing technique that involves providing invalid, unexpected, or random data as inputs to a computer program. The program is then monitored for exceptions such as crashes, failing built-in code assertions, or potential memory leaks.

icalendar uses OSS-Fuzz and GitHub Actions for fuzz testing.

Identify uncaught fuzzing errors#

Occasionally, fuzz testing creates uncaught errors for which icalendar needs a test case.

In a GitHub Actions log, calendars are printed out as base64-encoded lines of text with a leading timestamp. A sample log file is available at fuzz-testing-log.txt. The following examples come from that log file. For simplicity, the timestamp is omitted.

A typical calendar example consists of a start and end delimiter surrounding the base64 encoded calendar as shown.

--- start calendar ---
QkVHSU46CkJpaWloOgpCaWlpMDoKRU5kOg==
--- end calendar ---

To decode this calendar, use the following command.

$ echo "QkVHSU46CkJpaWloOgpCaWlpMDoKRU5kOg==" | base64 -d

Observe all kinds of random data in the output that the fuzzer generates, and which may trigger errors.

BEGIN:
Biiih:
Biiih0:
END:

When fuzz testing fails, it appears as an error in the log file. To identify uncaught fuzzing errors for which icalendar needs a test case, look for a start delimiter, followed by the base64 encoded calendar, and finally a line with an error code followed by the text libFuzzer: fuzz target exited.

--- start calendar ---
QkVHSU46VgpSUlVMRTolbjtCWU1PTlRIPQ==
==28== ERROR: libFuzzer: fuzz target exited

Create a fuzz test case#

To create the fuzz test case locally, use the following template, filling in the <base64> string and adding a descriptive <filename>.

echo "<base64>" | base64 -d | tee src/icalendar/tests/calendars/fuzz_testcase_<filename>.ics

Then, you can run the tests and see that the error is reproduced.

tox -e py313 -- src/icalendar/tests/fuzzed/

Ignore valid errors. Fix invalid errors.

Reproduction example#

In the sample log file fuzz-testing-log.txt, find the fuzz test error by searching for the string libFuzzer: fuzz target exited. Copy the immediately preceding base64 encoded calendar.

QkVHSU46VgpSUlVMRTolbjtCWU1PTlRIPQ==

The log shows the Python traceback usually a few hundred lines above this calendar.

--- start calendar ---
=== Uncaught Python exception: ===
ValueError: Invalid month: ''
Traceback (most recent call last):

This provides a clue for how to write a test case. Generate the test case file by running the following command from the root of the repository, using the copied calendar.

$ echo "QkVHSU46VgpSUlVMRTolbjtCWU1PTlRIPQ==" | base64 -d | tee src/icalendar/tests/calendars/fuzz_testcase_invalid_month.ics
BEGIN:V
RRULE:%n;BYMONTH=

Next, run the tests.

$ tox -e py313 -- src/icalendar/tests/fuzzed/

The output shows that the error is reproduced.

src/icalendar/tests/fuzzed/test_fuzzed_calendars.py .F
# ...
ValueError: Invalid month: ''

Some tests cases point to code to fix. This one points to a valid error for a wrong month value. An empty month is invalid by the specification. As such, it is correct to have this error. Commit the test case and push it to a pull request, so that the error can be safely ignored during fuzz testing.

Reproduce fuzzing issues locally#

To reproduce fuzzing issues locally, install OSS-Fuzz locally, following its setup instructions.

Then, you can run the script generate_python_test_cases_from_downloaded_clusterfuzz_test_cases.sh in src/icalendar/tests/fuzzed/ to generate python test cases from the downloaded fuzzer test cases.

src/icalendar/tests/fuzzed/generate_python_test_cases_from_downloaded_clusterfuzz_test_cases.sh