1
1
libssh/tests/fuzz
Jakub Jelen f97ff7c2e8 fuzz: Provide host and ssh dir for the fuzzer
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
2022-02-15 11:34:11 +01:00
..
ssh_client_fuzzer_corpus fuzz: Add instructions for corpus creation and first corpus files 2020-05-05 14:29:13 +02:00
ssh_known_hosts_fuzzer_corpus fuzz: Add known_hosts file fuzzer 2022-02-10 11:21:19 +01:00
ssh_server_fuzzer_corpus fuzz: Add instructions for corpus creation and first corpus files 2020-05-05 14:29:13 +02:00
CMakeLists.txt fuzz: Add a testcase for each fuzzer with a corpus 2022-02-10 11:21:19 +01:00
fuzzer.c fuzz: Simplify definition of fuzzing targets and build them also with gcc 2020-05-05 14:23:06 +02:00
README.md fuzz: Extend readme for reproducing and debugging tips 2020-12-08 14:54:25 +01:00
ssh_bind_config_fuzzer.c fuzz: Get rid of cpp mess in fuzzers 2022-02-08 19:13:45 +01:00
ssh_client_config_fuzzer.c fuzz: Provide host and ssh dir for the fuzzer 2022-02-15 11:34:11 +01:00
ssh_client_fuzzer.c fuzz: Get rid of cpp mess in fuzzers 2022-02-08 19:13:45 +01:00
ssh_known_hosts_fuzzer.c fuzz: Add known_hosts file fuzzer 2022-02-10 11:21:19 +01:00
ssh_server_fuzzer.c fuzz: Get rid of cpp mess in fuzzers 2022-02-08 19:13:45 +01:00

Simple fuzzers for libssh

This directory contains fuzzer programs, that are usable either in oss-fuzz infrastructure or suitable for running fuzzing locally or even for reproducing crashes with given trace files.

When building with clang, fuzzers are automatically built with address sanitizer. With gcc, they are built as they are without instrumentation, but they are suitable for debugging.

Background

Fuzzing ssh protocol is complicated by the way that all the communication between client and server is encrypted and authenticated using keys based on random data, making it impossible to fuzz the actual underlying protocol as every change in the encrypted data causes integrity errors. For that reason, libssh needs to implement "none" cipher and MAC as described in RFC 4253 and these need to be used during fuzzing to be able to accomplish reproducibility and for fuzzers to be able to progress behind key exchange.

Corpus creation

For effective fuzzing, we need to provide corpus of initial (valid) inputs that can be used for deriving other inputs. libssh already supports creation of pcap files (packet capture), which include all the information we need for fuzzing. This file is also created from date before encryption and after decryption so it is in plain text as we expect it, but we still need to adjust configuration to use none cipher for the key exchange to be plausible.

Creating packet capture using example libssh client

  • Compile libssh with support for none cipher and pcap:

    cmake -DWITH_INSECURE_NONE=ON -DWITH_PCAP=ON ../

  • Create a configuration file enabling none cipher and mac:

    printf 'Ciphers none\nMACs none' > /tmp/ssh_config

  • Generate test host key:

    ./examples/keygen2 -f /tmp/hostkey -t rsa

  • Run example libssh server:

    ./examples/samplesshd-cb -f /tmp/ssh_config -k /tmp/hostkey -p 22222 127.0.0.1

  • In other terminal, run the example libssh client with pcap enabled (use mypassword for password):

    ./examples/ssh-client -F /tmp/ssh_config -l myuser -P /tmp/ssh.pcap -p 22222 127.0.0.1

  • Kill the server (in the first terminal, press Ctrl+C)

  • Convert the pcap file to raw traces (separate client and server messages) usable by fuzzer:

    tshark -r /tmp/ssh.pcap -T fields -e data -Y "tcp.dstport==22222" | tr -d '\n',':' | xxd -r -ps > /tmp/ssh_server tshark -r /tmp/ssh.pcap -T fields -e data -Y "tcp.dstport!=22222" | tr -d '\n',':' | xxd -r -ps > /tmp/ssh_client

  • Now we should be able to "replay" the sessions in respective fuzzers, getting some more coverage:

    LIBSSH_VERBOSITY=9 ./tests/fuzz/ssh_client_fuzzer /tmp/ssh_client LIBSSH_VERBOSITY=9 ./tests/fuzz/ssh_server_fuzzer /tmp/ssh_server

    (note, that the client fuzzer fails now because of invalid hostkey signature; TODO)

  • Store the appropriately named traces in the fuzers directory:

    cp /tmp/ssh_client tests/fuzz/ssh_client_fuzzer_corpus/$(sha1sum /tmp/ssh_client | cut -d ' ' -f 1) cp /tmp/ssh_server tests/fuzz/ssh_server_fuzzer_corpus/$(sha1sum /tmp/ssh_server | cut -d ' ' -f 1)

Debugging issues reported by oss-fuzz

OSS Fuzz provides helper scripts to reproduce issues locally. Even though the fuzzing scripts can ran anywhere, the best bet for reproducing is to use their container infrastructure. There is a complete documentation but I will try to focus here on the workflow I use and libssh specifics.

Environment

The helper scripts are written in Python and use docker to run containers so these needs to be installed. I am using podman instead of docker for some time, but it has some quirks that needs to be addressed in advance and that I describe in the rejected PR. You can either pick up my branch or workaround them locally:

  • Package podman-docker installs symlink from /bin/docker to /bin/podman
  • The directories mounted to the containers need to have container_file_t SELinux labels -- this is needed for the build directory that is created under the oss-fuzz repository, for testcases and for source files
  • podman does not like combination of --privileged and --cap-add SYS_PTRACE flags. Podman can work with non-privileged containers so you can just remove the --privileged from the infra/helper.py

Reproduce locally

Clone the above repository from https://github.com/google/oss-fuzz/, apply changes from previous secion if needed, setup local clone of libssh repository and build the fuzzers locally (where ~/devel/libssh is path to local libssh checkout):

python infra/helper.py build_fuzzers libssh ~/devel/libssh/

Now, download the testcase from oss-fuzz.com (the file under ~/Downloads) and we are ready to reproduce the issue locally (replace the ssh_client_fuzzer with the fuzzer name if the issue happens in other fuzzer):

python infra/helper.py reproduce libssh ssh_client_fuzzer ~/Downloads/clusterfuzz-testcase-ssh_client_fuzzer-4637376441483264

This should give you the same error/leak/crash as you see on the testcase detail in oss-fuzz.com.

I find it very useful to run libssh in debug mode, to see what happened and what exit path was taken to get to the error. Fortunatelly, we can simply pass environment variables to the container:

python infra/helper.py reproduce -eLIBSSH_VERBOSITY=9 libssh ssh_client_fuzzer ~/Downloads/clusterfuzz-testcase-ssh_client_fuzzer-4637376441483264

Fix the issue and verify the fix

Now, we can properly investigate the issue and once we have a fix, we can make changes in our local checkout and repeat the steps above (from building fuzzers) to verify the issue is no longer present.

Fuzzing locally

We can use the oss-fuzz tools even further and run the fuzzing process locally, to verify there are no similar issues happening very close to existing code paths and which would cause more reports very soon after we would fix the current issue. The following command will run fuzzer until it finds an issue or until killed:

python infra/helper.py run_fuzzer libssh ssh_client_fuzzer