Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exercise fuzzers during CI builds #579

Merged
merged 16 commits into from
Mar 5, 2020
Merged

Conversation

ilammy
Copy link
Collaborator

@ilammy ilammy commented Jan 27, 2020

We have had some kind of a fuzzing framework for close to a year, but it did not get enough attention since only nobody cares about that. Let’s improve the situation a bit.

  • Make sure that fuzzers can be built with sanitizers. This is the most interesting case since sanitizers report bugs by crashing and AFL reacts to crashes. Building and running ASAN tests was a quest on its own.

  • Build and run fuzzers on CI. Every time. For 30 seconds each. Hopefully we’ll catch some low-hanging bugs with that, given a sufficient number of builds.

  • There is at least one known bug which is caught by fuzzers+sanitizers, so hopefully the build will fail this time and you can see the report.

  • Improve reporting and crash analysis tool so that they are more useful.

There are more implementation details in commit messages.

P.S. Yep, the build does fail right now.
fuzzing scell_seal_roundtrip...
afl-fuzz 2.52b by <[email protected]>
[+] Looks like we're not running on a tty, so I'll be a bit less verbose.
[+] You have 36 CPU cores and 19 runnable tasks (utilization: 53%).
[+] Try parallel jobs - see /usr/share/doc/afl-doc/docs/parallel_fuzzing.txt.
[*] Checking CPU core loadout...
[+] Found a free CPU core, binding to #0.
[*] Checking core_pattern...
[*] Setting up output directories...
[+] Output directory exists but deemed OK to reuse.
[*] Deleting old session data...
[+] Output dir cleanup successful.
[*] Scanning 'tools/afl/input/scell_seal_roundtrip'...
[+] No auto-generated dictionary tokens to reuse.
[*] Creating hard links for all input files...
[*] Validating target binary...
[*] Attempting dry run with 'id:000000,orig:ok_short_message.dat'...
[*] Spinning up the fork server...
[+] All right - fork server is up.
    len = 30, map size = 314, exec speed = 3706 us
[+] All test cases processed.

[+] Here are some useful stats:

    Test case count : 1 favored, 0 variable, 1 total
       Bitmap range : 314 to 314 bits (average: 314.00 bits)
        Exec timing : 3706 to 3706 us (average: 3706 us)

[*] No -t option specified, so I'll use exec timeout of 20 ms.
[+] All set and ready to roll!
[*] Entering queue cycle 1.
[*] Fuzzing test case #0 (1 total, 0 uniq crashes found)...
[*] Fuzzing test case #1 (8 total, 0 uniq crashes found)...
[*] Fuzzing test case #3 (8 total, 0 uniq crashes found)...
[*] Fuzzing test case #4 (8 total, 0 uniq crashes found)...
[*] Fuzzing test case #6 (8 total, 0 uniq crashes found)...


+++ Testing aborted by user +++
[+] We're done here. Have a nice day!

see build/afl/output/scell_seal_roundtrip/2020-01-27_08-00-03 for results

Analyzing results...

# scell_seal_decrypt -- 2020-01-27_07-59-33

## id:000000 -- SIGABRT

Run:

    build/afl/scell_seal_decrypt build/afl/output/scell_seal_decrypt/2020-01-27_07-59-33/crashes/id:000000,sig:06,src:000000,op:flip32,pos:27

Input (base64):

AAAAA2tleQAAAAdjb250ZXh0AAAAMwABAUAM////7wAAAAcAAACbMpUEngYcUNl6
KWhBWxWP8Qp1AC4xdvnfM1NJYB5sxBGHsA==

Debugger output:

=================================================================
==3989==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xf60015bc at pc 0xf7a1e8be bp 0xff8a27f8 sp 0xff8a23c8
READ of size 16 at 0xf60015bc thread T0
    #0 0xf7a1e8bd  (/usr/lib32/libasan.so.4+0x778bd)
    #1 0xf7813a23  (/usr/lib/i386-linux-gnu/libcrypto.so.1.1+0x118a23)
    #2 0xf781f05e in EVP_CIPHER_CTX_ctrl (/usr/lib/i386-linux-gnu/libcrypto.so.1.1+0x12405e)
    #3 0x56659e62 in soter_sym_aead_decrypt_final src/soter/openssl/soter_sym.c:433
    #4 0x5664dcb9 in themis_auth_sym_plain_decrypt src/themis/sym_enc_message.c:109
    #5 0x5664dcb9 in themis_auth_sym_decrypt_message_ src/themis/sym_enc_message.c:332
    #6 0x5664dcb9 in themis_auth_sym_decrypt_message src/themis/sym_enc_message.c:439
    #7 0x566490ad in themis_secure_cell_decrypt_seal src/themis/secure_cell.c:92
    #8 0x56646c12 in main tools/afl/src/scell_seal_decrypt.c:99
    #9 0xf7537e80 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18e80)
    #10 0x56647a82  (/home/user/project/build/afl/scell_seal_decrypt+0x3a82)

Address 0xf60015bc is a wild pointer.
SUMMARY: AddressSanitizer: heap-buffer-overflow (/usr/lib32/libasan.so.4+0x778bd) 
Shadow bytes around the buggy address:
  0x3ec00260: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x3ec00270: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x3ec00280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x3ec00290: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x3ec002a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x3ec002b0: fa fa fa fa fa fa fa[fa]fa fa fa fa fa fa fa fa
  0x3ec002c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x3ec002d0: fa fa fa fa 00 00 00 00 00 00 03 fa fa fa fa fa
  0x3ec002e0: 00 00 00 00 00 00 00 01 fa fa fa fa fa fa fa fa
  0x3ec002f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x3ec00300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==3989==ABORTING

Exited with code exit status 1

Failures are also stored in artifacts, example.

Checklist

  • Change is covered by automated tests
  • The coding guidelines are followed
  • Public API has proper documentation
  • Example projects and code samples are updated
  • Changelog is updated (maybe later)
  • Parsing bug is fixed and the build is not red

ilammy and others added 10 commits January 26, 2020 13:03
Instead of using our special AFL_CFLAGS and AFL_LDFLAGS, just set them
per-target as we do in other places. We'd like to build AFL fuzzers
with sanitizers, and we'd like to be aware when AFL toolchain does not
support a particular compilation flag. Currently we can WITH_ASAN=1,
but it silently drops sanitizer flags instead of complaining, which
gives an impression that we use sanitizer when in fact we do not.
Pull in Soter dependencies when building Themis so that it triggers
BoringSSL build when ENGINE is set.
AFL does not support 64-bit builds so build 32-bit binaries instead.
Note that we have to do in two places: during original make invocation
to build fuzzer tools, and during recursive make invocation to build
Themis library with AFL instrumentation.
It turns out that afl-gcc actually uses AFL_CC environment variable
(who would have though, eh?) And if we set it to afl-gcc then afl-gcc
tries to invoke afl-gcc in an infinite recursive loop. Avoid that by
unexporting the variable, keeping it only inside make.
Introduce a new "fuzz" job that builds, tests, and runs fuzzers.
We check that we can build all interesting configurations and run
the most interesting ones for a bit to ensure that there are no
obvious mistakes in the code. We'd like to run longer, but CircleCI
has around 20-minute limit which does not fit what we want to eat.

AFL is meant to be used interactively (primarily) but it is possible
to script its execution too. We use a bit peculiar spell for that:

    timeout -s INT 30s make fuzz | cat -u || true

with the following rationale:

- timeout limits execution time of "make fuzz" which is by default
  unbounded. Put a 30-second limit on execution to keep it civil.
  We've got quite a few fuzzing tools to run.

- AFL expects to be cleanly stopped by SIGINT. However, it will stop
  with non-zero exit code, so we eat it with "|| true"

- Piping into cat disconnects AFL from terminal so it won't be
  screaming about "terminal window is too small, plz resize it
  to at least 80x25, kthx"). Everything is better with cats.
CircleCI runs stuff in unprivileged Docker containers and gdb requires
ptrace capabilities to attach to processes. Unfortunately, we'll get
no stack traces on CI.

Well, add an option to skip debugger analysis and just try crashing,
maybe that will output something interesting.
Often fuzzing finds some length field, inverts it, and this results in
a gigabyte-sized allocation that malloc() refuses to fulfill. However,
it does not set "errno" which we use for error reporting at call site
resulting in silly errors: "failed to read file.dat: Success". Let's
set errno to something sensible before exiting.

Also, limit the input buffer sizes to 50 MB. AFL can bit-flip those
lengths too which is not what we really want. Since we use 32-bit
lengths those are mostly fine on 64-bit systems, but when running
32-bit binaries with ASAN malloc() tends to panic when allocating
more than 2 GB. Don't let it do that and abort the test (cleanly).
We don't have direct access to reports on CI so let's dump the input
into logs so that it's readily accessible. It's usually something
small so it's okay to write it out.
It is possible for report directory to contain multiple reports, with
some not having crashes. "print_banner" gets reset for each report set
so we cannot use. Introduce a dedicated flag to note that we have had
a crash and should report that.
@ilammy ilammy added core Themis Core written in C, its packages infrastructure Automated building and packaging labels Jan 27, 2020
Copy link
Collaborator

@Lagovas Lagovas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

soon Bash and Makefile languages will be at top 5 of Themis languages on github )

#include <stdlib.h>

#define MAX_SANE_LENGTH (50 * 1024 * 1024)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

max 50 MBytes, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, 50 MB. This is default memory limit of AFL and our test data is unlikely to exceed it. There is no gain in making a huge test case as AFL will try minimizing it anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, thanx!

@vixentael
Copy link
Contributor

So,

  1. Every CI check is 30sec slower.
  2. If fuzzer finds smth, CI tests will fail.
  3. What is our procedure on fixing things that fuzzer found? I mean, what PR author should do, if fuzzer found an issue that is not related to their PR (most things won't be related).

@vixentael vixentael added the tests Themis test suite label Jan 27, 2020
@ilammy
Copy link
Collaborator Author

ilammy commented Jan 27, 2020

@Lagovas,

soon Bash and Makefile languages will be at top 5 of Themis languages on github )

YAML programmers 🤣

@vixentael,

Every CI check is 30sec slower.

They are running in parallel, and most of the time Android builds and integration tests are the slowest, so we're unlikely to be blocked by this. (And it will be about 2 minutes and counting in fact: there are 2 fuzzing tools and 2 fuzzing modes, each of those 4 runs for 30 seconds.)

If fuzzer finds smth, CI tests will fail.

That's correct.

What is our procedure on fixing things that fuzzer found?

I guess manually inspect the failure and use our best judgement. I'd probably rerun the job, if the failure persists and is definitely not related to PR, they we might note the failure, approve the merge with it, and fix it later separately.

Best practice would be to run shadow builds and send alerts about fuzzing failures only to maintainers, without exposing PR authors to them. But I'm not really sure how this can be easily done with CircleCI. I did not notice any ready-made so we'd have to hack something (e.g., note the failure, send an alert via API integration, let the build succeed if the failure is found by fuzzers).

We'd been building 32-bit binaries when ASAN was enabled even withou
AFL. ASAN works fine on general 64-bit systems, we need to build 32-bit
binaries only when running AFL.

Use -m32 when building fuzzing tools. Variable adjuments when
AFL_USE_ASAN=1 will take care of the modified Themis build.
@Lagovas
Copy link
Collaborator

Lagovas commented Jan 27, 2020

I guess manually inspect the failure and use our best judgement. I'd probably rerun the job, if the failure persists and is definitely not related to PR, they we might note the failure, approve the merge with it, and fix it later separately.

does fuzzer can save test data to some file? if so we can save it as test artifacts to reproduce it later on failures

@ilammy
Copy link
Collaborator Author

ilammy commented Jan 27, 2020

does fuzzer can save test data to some file?

Yes. The results are saved in the build tree, along with all the intermediate data. We use this data to generate reports (which are dumped into build logs). There should be enough data in the logs to reproduce the crashes:

  • what mutation AFL applied (e.g., flipped 1 bit at offset 27)
  • name of the tool that failed
  • full input to the tool
  • backtrace & register data, if available
  • anything else printed by the sanitizer, usually pretty detailed description

We do not save the original report data since I thought all of the above should be sufficient. But it should be easy to store full reports on CircleCI as builds artifacts. Who's for it?

In addition to dumping information to log, save full reports in CircleCI
artifacts for each build so that they may be retrieved later. There are
quite a few small files in there so zip them into a single archive.
@ilammy
Copy link
Collaborator Author

ilammy commented Jan 27, 2020

@vixentael
Copy link
Contributor

In my perfect world (🦄) we'd have some artifact (logs in file, result of AFL) that we can grab from CI and post into internal issue tracker. So, smth that can be downloaded and attached.

Artifacts should work i think

@ilammy ilammy mentioned this pull request Jan 29, 2020
3 tasks
Yeah, okay, git, I get it, you're great at merge conflicts.
I mean, resolve merge conflicts because evidently I can't resolve them
on the first attempt.
@ilammy
Copy link
Collaborator Author

ilammy commented Feb 12, 2020

PR #592 contains fixes which should resolve issues found by AFL in this PR. Once that is merged, master should be merged into this PR and the build is expected to go green.

@ilammy ilammy merged commit a079d5c into cossacklabs:master Mar 5, 2020
@ilammy ilammy deleted the afl-fuzz branch March 5, 2020 12:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core Themis Core written in C, its packages infrastructure Automated building and packaging tests Themis test suite
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants