1# Background 2 3This document seeks to be a crash-course and cheat-sheet for running the NNAPI 4fuzz tests. 5 6The purpose of fuzz testing is to find crashes, assertions, memory violations, 7or general undefined behavior in the code under test due to factors such as 8unexpected inputs. For NNAPI fuzz testing, Android uses tests based on 9`libFuzzer`, which are efficient at fuzzing because they use line coverage of 10previous test cases to generate new random inputs. For example, `libFuzzer` 11favors test cases that run on uncovered lines of code. This greatly reduces the 12amount of time tests take to find problematic code. 13 14Currently, there are two NNAPI fuzz test targets: `libneuralnetworks_fuzzer` 15which tests at the NNAPI NDK layer (testing libneuralnetworks as a static 16library) and `libneuralnetworks_driver_fuzzer` which tests an in-process driver 17at the NNAPI HAL layer (the sample driver, unless the test is modified to do 18otherwise). To simplify development of future tests, this directory also 19defines an NNAPI fuzzing test harness and packages it in a blueprint default 20`libneuralnetworks_fuzzer_defaults`. 21 22Useful background reading and reference documents: 23* libFuzzer overview: http://llvm.org/docs/LibFuzzer.html 24* Android-specific libFuzzer documentation: 25 https://source.android.com/devices/tech/debug/libfuzzer 26* Android Security Testing (sanitizers, fuzzer, etc.): 27 https://source.android.com/devices/tech/debug/fuzz-sanitize 28* Sanitizer flags: 29 https://github.com/google/sanitizers/wiki/SanitizerCommonFlags 30* Address Sanitizer flags: 31 https://github.com/google/sanitizers/wiki/AddressSanitizerFlags 32* libprotobuf-mutator: 33 https://github.com/google/libprotobuf-mutator#libprotobuf-mutator 34 35# Setting up the test 36 37## Developing an NNAPI fuzz test 38 39### Creating a new fuzz test using `libneuralnetworks_fuzzer_defaults` 40 41To create a new fuzz test: 421. Create code that implements the function 43 `void nnapiFuzzTest(const TestModel& testModel)` (examples: [1][1], [2][2]) 442. Create a blueprint `cc_fuzz` target that includes 45 `libneuralnetworks_fuzzer_defaults` as a default (examples: [1][3], [2][4]) 46 47### Modifying `libneuralnetworks_driver_fuzzer` to test custom driver 48 49Alter the `libneuralnetworks_driver_fuzzer` code locally to test your own 50driver. In the section `“TODO: INSERT CUSTOM DEVICE HERE”`, replace 51`“new nn::sample_driver::SampleDriverFull(…);”` ([link][5]) with your own 52driver. 53 54This code employs an in-process driver (as opposed to retrieving it on the 55device via `IDevice::getService(...))` for three reasons. First, the test runs 56faster because it does not need to communicate with the driver via IPC because 57the driver is created in the same process. Second, it ensures that the 58`libFuzzer` can use the coverage from the driver to guide the test 59appropriately, as everything is built as one unit. Finally, whenever a crash 60occurs, only one stacktrace needs to be analyzed to debug the problem. 61 62The current version of the test assumes a 1.3 driver and uses the methods 63`IDevice::prepareModel_1_3` and `IDevice::executeSynchronously_1_3` 64([link][6]). Change the test locally to test different methods or different 65driver versions. 66 67## Preparing a device 68 69Because the test is self-contained, you should be able to just use a regular 70device image without any modifications. The next section 71[Building and uploading fuzz test](#building-and-uploading-fuzz-test) describes 72how to build the test binary itself. If you need to have the entire image 73fuzzed (for example, if you want to sanitize a shared library), you can build a 74sanitized image with one of the following two sequences of commands depending 75on your needs: 76 77### You can build a pre-configured sanitized device image with: 78```bash 79$ . build/envsetup.sh 80$ lunch <sanitized_target> # e.g., <TARGET_PRODUCT>_hwasan-userdebug 81$ mma -j 82``` 83 84### Alternatively, you can build other (read: non-sanitized) targets with the following command: 85```bash 86$ . build/envsetup.sh 87$ lunch <non-sanitized_target> # e.g., <TARGET_PRODUCT>-userdebug 88$ SANITIZE_TARGET=hwaddress mma -j 89``` 90 91## Building and uploading fuzz test 92 93For simplicity and clarity, the rest of the code here will use the following 94environment variables: 95``` 96$ FUZZER_NAME=libneuralnetworks_driver_fuzzer 97$ FUZZER_TARGET_ARCH=$(get_build_var TARGET_ARCH) 98$ FUZZER_TARGET_DIR=/data/fuzz/$FUZZER_TARGET_ARCH/$FUZZER_NAME 99$ FUZZER_TARGET=$FUZZER_TARGET_DIR/$FUZZER_NAME 100``` 101 102When using a sanitized lunch target, build the fuzz test with the following 103command: 104```bash 105$ m $FUZZER_NAME -j 106``` 107 108When building with a non-sanitized lunch target, build the fuzz test with the 109following command: 110```bash 111$ SANITIZE_TARGET=hwaddress m $FUZZER_NAME -j 112``` 113 114Note that the above commands use `hwaddress` sanitization, but other sanitizers 115can be used in place of or in addition to `hwaddress`. More command options for 116building with other sanitizers can be found [here][7], and they are explained 117more in depth in the Android background reading [here][8]. 118 119Once the test is built, it can be pushed to the device via: 120```bash 121$ adb root 122$ adb sync data 123$ adb shell mkdir -p $FUZZER_TARGET_DIR/dump 124``` 125 126The directory `$FUZZER_TARGET_DIR/` is now as follows: 127* `$FUZZER_NAME` -- fuzz test binary 128* `corpus/` -- directory for reference/example “good” test cases, used to speed 129 up fuzz tests 130* `dump/` -- sandbox directory used by the fuzz test; this can be ignored 131* `crash-*` -- any future problematic test cases will be dumped to the directory 132 133# Running the test 134 135## Running the full fuzz test 136 137The fuzz test can be launched with the following command, and will continue 138running until the user terminates the process (e.g., ctrl+c) or until the test 139crashes. 140 141```bash 142$ adb shell HWASAN_OPTIONS=handle_sigill=2:handle_sigfpe=2:handle_sigbus=2:handle_abort=2:handle_segv=2 $FUZZER_TARGET $FUZZER_TARGET_DIR/dump/ $FUZZER_TARGET_DIR/corpus/ -artifact_prefix=$FUZZER_TARGET_DIR/ 143``` 144 145(When using a non-hwasan build, you need to change the `HWASAN_OPTIONS` 146variable to match whatever build you’re using, e.g., `ASAN_OPTIONS`.) 147 148When something unexpected occurs (e.g., a crash or a very slow test case), the 149test case that causes it will be dumped to a file in the directory specified by 150“`-artifact_prefix`”. The generated file will appear as 151`slow-unit-<unique_identifier>`, `crash-<unique_identifier>`, 152`oom-<unique_identifier>`, or `timeout-<unique_identifier>`. Normally, 153`libFuzzer` crash files will contain unreadable binary data; however, 154`libneuralnetworks_driver_fuzzer`‘s output is formatted in a human readable way 155because it uses `libprotobuf-mutator`, so it’s fine to inspect the file to get 156more information on the test case that caused the problem. For more 157information, refer to the [Fuzz test case format](#fuzz-test-case-format) 158section below. 159 160## Reproducing crash case 161 162When a crash occurs, the crash test case can be re-run with the following 163command: 164 165```bash 166$ adb shell HWASAN_OPTIONS=handle_sigill=2:handle_sigfpe=2:handle_sigbus=2:handle_abort=2:handle_segv=2 $FUZZER_TARGET $FUZZER_TARGET_DIR/<test_case_name> 167``` 168(Note that the execution parameters for `HWASAN_OPTIONS` are the same as those 169above.) 170 171E.g., `<test_case_name>` could be: 172* `minimized-from-15b1dae0d2872d8dccf4f35fbf4ecbecee697a49` 173* `slow-unit-cad88bd58853b71b875ac048001b78f7a7501dc3` 174* `crash-07cb8793bbc65ab010382c0f8d40087897826129` 175 176# Finding minimal crash case 177 178When a crash occurs, sometimes the offending test case is large and 179complicated. `libFuzzer` has a way to minimize the crashing case to simplify 180debugging with the following command: 181 182```bash 183$ adb shell HWASAN_OPTIONS=handle_sigill=2:handle_sigfpe=2:handle_sigbus=2:handle_abort=2:handle_segv=2 $FUZZER_TARGET $FUZZER_TARGET_DIR/<test_case_name> -artifact_prefix=$FUZZER_TARGET_DIR/ -minimize_crash=1 -max_total_time=60 184``` 185(Note that the execution parameters for `HWASAN_OPTIONS` are the same as those 186above.) 187 188Note that the `<test_case_name>` must be some sort of crash for the 189minimization to work. For example, minimization will not work on something like 190`slow_unit-*` cases. Increasing the `max_total_time` value may yield a more 191minimal test crash, but will take longer. 192 193## Fuzz test case format 194 195By itself, `libFuzzer` will generate a random collection of bytes as input to 196the fuzz test. The test developer then needs to convert this random data to 197some structured testing format (e.g., a syntactically correct NNAPI model). 198Doing this conversion can be slow and difficult, and can lead to inefficient 199mutations and tests. Additionally, whenever the fuzz test finds a crashing test 200case, it will dump this test case as an unreadable binary chunk of data in a 201file (e.g., `crash-*` files described above). 202 203To help with both of these issues, the NNAPI fuzz tests additionally use a 204library called [`libprotobuf-mutator`][9] to handle the conversions from the 205random `libFuzzer` input to a protobuf format used for NNAPI fuzz testing. The 206conversion from this protobuf format to a model format is much more 207straightforward and efficient. As another useful utility, `libprotobuf-mutator` 208provides the option to represent this data as human-readable text. This means 209that whenever the fuzz test finds a crash, the resultant test case that is 210dumped to a file will be in a human-readable format. 211 212Here is one example of a crash case that was found: 213``` 214model { 215 operands { 216 operand { 217 type: TENSOR_INT32 218 dimensions { 219 dimension: 1 220 } 221 scale: 0 222 zero_point: 0 223 lifetime: TEMPORARY_VARIABLE 224 channel_quant { 225 scales { 226 } 227 channel_dim: 0 228 } 229 data { 230 random_seed: 4 231 } 232 } 233 operand { 234 type: TENSOR_FLOAT32 235 dimensions { 236 dimension: 2 237 dimension: 4 238 } 239 scale: 0 240 zero_point: 0 241 lifetime: TEMPORARY_VARIABLE 242 channel_quant { 243 scales { 244 } 245 channel_dim: 0 246 } 247 data { 248 random_seed: 0 249 } 250 } 251 operand { 252 type: TENSOR_FLOAT32 253 dimensions { 254 } 255 scale: 0 256 zero_point: 0 257 lifetime: SUBGRAPH_OUTPUT 258 channel_quant { 259 scales { 260 } 261 channel_dim: 27 262 } 263 data { 264 random_seed: 0 265 } 266 } 267 } 268 operations { 269 operation { 270 type: EMBEDDING_LOOKUP 271 inputs { 272 index: 0 273 index: 1 274 } 275 outputs { 276 index: 2 277 } 278 } 279 } 280 input_indexes { 281 index: 0 282 index: 1 283 } 284 output_indexes { 285 index: 2 286 } 287 is_relaxed: true 288} 289``` 290 291This format is largely based on the format defined in [NNAPI HAL][10]. The one 292major exception is that the contents of an operand's data are replaced by data 293generated from “random_seed” (except for `TEMPORARY_VARIABLE` and `NO_VALUE` 294operands, in which cases there is no data, so "random_seed" is ignored). This 295is done for a practical reason: `libFuzzer` (and by extension 296`libprotobuf-mutator`) converge slower when the amount of randomly generated 297input is large. For the fuzz tests, the contents of the operand data are not as 298interesting as the structure of the graph itself, so the data was replaced by 299a seed to a random number generator instead. 300 301[1]: https://cs.android.com/android/platform/superproject/+/master:packages/modules/NeuralNetworks/runtime/test/android_fuzzing/DriverFuzzTest.cpp;l=307-324;drc=34aee872d5dc317ad8a32377e9114c0c606d8afe 302[2]: https://cs.android.com/android/platform/superproject/+/master:packages/modules/NeuralNetworks/runtime/test/android_fuzzing/FuzzTest.cpp;l=130-151;drc=34aee872d5dc317ad8a32377e9114c0c606d8afe 303[3]: https://cs.android.com/android/platform/superproject/+/master:packages/modules/NeuralNetworks/runtime/test/Android.bp;l=195-216;drc=60823f07172e6b5bbc06b2fac25a15ab91c80b25 304[4]: https://cs.android.com/android/platform/superproject/+/master:packages/modules/NeuralNetworks/runtime/test/Android.bp;l=218-240;drc=60823f07172e6b5bbc06b2fac25a15ab91c80b25 305[5]: https://cs.android.com/android/platform/superproject/+/master:packages/modules/NeuralNetworks/runtime/test/android_fuzzing/DriverFuzzTest.cpp;l=48-52;drc=34aee872d5dc317ad8a32377e9114c0c606d8afe 306[6]: https://cs.android.com/android/platform/superproject/+/master:packages/modules/NeuralNetworks/runtime/test/android_fuzzing/DriverFuzzTest.cpp;l=291-292,302;drc=34aee872d5dc317ad8a32377e9114c0c606d8afe 307[7]: https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/sanitize.go;l=140-187;drc=b5b2aba43b5bb6305ea69d60f9bf580f711d7c96 308[8]: https://source.android.com/devices/tech/debug/libfuzzer 309[9]: https://cs.android.com/android/platform/superproject/+/master:external/libprotobuf-mutator/ 310[10]: https://cs.android.com/android/platform/superproject/+/master:hardware/interfaces/neuralnetworks/ 311