141 lines
5.0 KiB
Markdown
141 lines
5.0 KiB
Markdown
# OEMCrypto fuzzing
|
||
|
||
ClusterFuzz and Google Cloud infrastructure continuously runs OEMCrypto fuzz
|
||
tests and reports crashes. To create a new automated fuzzing setup, refer to
|
||
[*ClusterFuzz setup*][1].
|
||
|
||
## Run fuzz tests locally
|
||
|
||
1. Build the fuzz tests:
|
||
|
||
```shell
|
||
$ cd <cdm_repo_path>
|
||
$ oemcrypto/test/fuzz_tests/build_oemcrypto_fuzztests
|
||
```
|
||
|
||
2. Run the fuzz test:
|
||
|
||
```shell
|
||
$ out/Default/<fuzz_test> [<corpus_dir>]
|
||
```
|
||
|
||
The corpus directory is optional and can either be a seed corpus from the
|
||
`corpus` subdirectory or be an empty directory. The corpus will be extended
|
||
with new inputs while the fuzz test is running.
|
||
|
||
## Triage crashes
|
||
|
||
To reproduce a crash locally for debugging:
|
||
|
||
1. Download the minimized testcase from the ClusterFuzz report.
|
||
|
||
2. Build the fuzz tests:
|
||
|
||
```shell
|
||
$ oemcrypto/test/fuzz_tests/build_oemcrypto_fuzztests
|
||
```
|
||
|
||
3. Debug the crash:
|
||
|
||
```shell
|
||
$ gdb --args <fuzz_test_path> -timeout=0 <testcase_path>
|
||
```
|
||
|
||
Example after substituting fuzz test and test case paths:
|
||
|
||
```shell
|
||
$ gdb --args out/Default/oemcrypto_opk_decrypt_cenc_fuzz -timeout=0 \
|
||
clusterfuzz-testcase-minimized-oemcrypto_v17_opk_decrypt_cenc_fuzz-6727459932078080
|
||
```
|
||
|
||
4. If reproducing the crash is unsuccessful, download the unminimized testcase
|
||
from the ClusterFuzz report and try again. If still unsuccessful, this may
|
||
indicate there is a persistent state issue with the fuzz test.
|
||
|
||
Once the root cause of the crash is identified, its severity and complexity
|
||
should be assessed. The [*SEI CERT C Coding Standard*][2] is a good resource for
|
||
risk assessment. The ClusterFuzz report will also provide input in the Security
|
||
field. For complex fixes with a longer timeline, ClusterFuzz may report
|
||
duplicate crashes with the same root cause.
|
||
|
||
## Write fuzz tests
|
||
|
||
While fuzzing has random elements, input data mutations are heavily influenced
|
||
by coverage feedback. Since discovering new control flow edges is a time
|
||
consuming process, input bytes should map to control flow edges in a simple,
|
||
predictable way. [`FuzzedDataProvider`][3], a class supplied with LLVM’s
|
||
libFuzzer, can be used to easily split input data:
|
||
|
||
```cpp
|
||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||
FuzzedDataProvider fuzzed_data(data, size);
|
||
|
||
// One bit of input data maps to this control flow edge:
|
||
if (fuzzed_data.ConsumeBool()) {
|
||
// ...
|
||
}
|
||
|
||
// ...
|
||
}
|
||
```
|
||
|
||
Fuzzing API methods with complex, structured input, may benefit from a seed
|
||
corpus containing a representative set of starting inputs. Unfortunately,
|
||
`FuzzedDataProvider` is not suitable for fuzz tests utilizing a seed corpus
|
||
since there is no equivalent serialization functionality for generating the
|
||
corpus. OEMCrypto fuzz tests have previously used struct-based serialization,
|
||
but this is no longer recommended due to portability issues. Protocol Buffers or
|
||
another portable serialization format should be considered instead.
|
||
|
||
Fuzz tests must be deterministic to reproduce and debug a crash. A common
|
||
pitfall is not resetting the OEMCrypto API state between calls to
|
||
`LLVMFuzzerTestOneInput`. Fully terminating OEMCrypto between inputs is
|
||
preferred, but in some cases, it may be necessary to implement careful
|
||
optimizations to achieve acceptable performance. Candidates for optimization
|
||
typically have less than 1000 executions per second (exec/s).
|
||
`LLVMFuzzerInitialize` can be used for global initialization, but there is no
|
||
corresponding termination method.
|
||
|
||
A good starting example is [`oemcrypto_install_oem_private_key_fuzz.cc`][4].
|
||
Targets should be added to `oemcrypto_opk_fuzztests.gyp` and, if the fuzz test
|
||
applies to partner OEMCrypto implementations, `partner_oemcrypto_fuzztests.gyp`.
|
||
The infrastructure expects that the target name starts with *oemcrypto* and ends
|
||
with *fuzz*.
|
||
|
||
For additional information about writing fuzz tests, see
|
||
[*What makes a good fuzz target*][5].
|
||
|
||
## Generate corpus with OEMCrypto unit tests
|
||
|
||
1. Build the unit tests:
|
||
|
||
```shell
|
||
$ cd <cdm_repo_path>
|
||
$ export CDM_DIR=${PWD}
|
||
$ export PATH_TO_CDM_DIR=..
|
||
$ gyp --format=ninja --depth=${PWD} oemcrypto/oemcrypto_unittests.gyp
|
||
$ ninja -C out/Default
|
||
```
|
||
|
||
2. Run the unit tests with the `--generate_corpus` flag:
|
||
|
||
```shell
|
||
$ mkdir oemcrypto/test/fuzz_tests/corpus/<fuzz_test>_seed_corpus
|
||
$ out/Default/oemcrypto_unittests --generate_corpus --gtest_filter='-*Huge*'
|
||
```
|
||
|
||
3. The unit tests can generate many duplicate corpus files. To minimize the
|
||
corpus to only the subset of inputs that cover unique paths within the API:
|
||
|
||
```shell
|
||
$ oemcrypto/test/fuzz_tests/build_oemcrypto_fuzztests
|
||
$ mkdir /tmp/minimized_corpus
|
||
$ out/Default/<fuzz_test> -merge=1 /tmp/minimized_corpus <full_corpus_dir>
|
||
```
|
||
|
||
[1]: clusterfuzz_setup.md
|
||
[2]: https://wiki.sei.cmu.edu/confluence/display/c/SEI+CERT+C+Coding+Standard
|
||
[3]: https://github.com/llvm/llvm-project/blob/main/compiler-rt/include/fuzzer/FuzzedDataProvider.h
|
||
[4]: oemcrypto_install_oem_private_key_fuzz.cc
|
||
[5]: https://github.com/google/fuzzing/blob/master/docs/good-fuzz-target.md
|