1# Table of contents 21. [Background](#background) 32. [Setting up the test ](#setting_up_the_test) 4 1. [Developing an NNAPI fuzz test](#developing_an_nnapi_fuzz_test) 5 2. [Preparing a device](#preparing_a_device) 6 3. [Building and uploading fuzz test](#building_and_uploading_fuzz_test) 73. [Running the test](#running_the_test) 8 1. [Running the full fuzz test](#running_the_full_fuzz_test) 9 2. [Reproducing crash case](#reproducing_crash_case) 10 3. [Finding minimal crash case](#finding_minimal_crash_case) 114. [Fuzz test case format](#fuzz_test_case_format) 125. [Changing Model.proto](#changing_model_proto) 13 1. [Adding new operations or operands](#adding_new_operations_or_operands) 14 2. [Expanding the structure](#expanding_the_structure) 15 3. [Backward-incompatible change](#backward_incompatible_change) 161. [Appendix](#appendix) 17 1. [Alternative ways to build the device image and test binary](#alternatives) 18 19# Background <a id="background"></a> 20 21This document seeks to be a crash-course and cheat-sheet for running the NNAPI 22fuzz tests. 23 24The purpose of fuzz testing is to find crashes, assertions, memory violations, 25or general undefined behavior in the code under test due to factors such as 26unexpected inputs. The NNAPI fuzz tests are based on `libFuzzer`, which is 27efficient at fuzzing because it uses line coverage of previous test cases to 28generate new random inputs. For example, `libFuzzer` favors test cases that 29run on uncovered lines of code. This greatly reduces the amount of time tests 30take to find problematic code. 31 32Currently, there are two NNAPI fuzz test targets: `libneuralnetworks_fuzzer` 33which tests at the NNAPI NDK layer (testing libneuralnetworks as a static 34library) and `libneuralnetworks_driver_fuzzer` which tests an in-process driver 35at the NNAPI HAL layer (the sample driver, unless the test is modified to do 36otherwise). To simplify development of future tests, this directory also 37defines an NNAPI fuzzing test harness and packages it in a blueprint default 38`libneuralnetworks_fuzzer_defaults`. 39 40Useful background reading and reference documents: 41* libFuzzer overview: http://llvm.org/docs/LibFuzzer.html 42* Android-specific libFuzzer documentation: 43 https://source.android.com/devices/tech/debug/libfuzzer 44* Android Security Testing (sanitizers, fuzzer, etc.): 45 https://source.android.com/devices/tech/debug/fuzz-sanitize 46* Sanitizer flags: 47 https://github.com/google/sanitizers/wiki/SanitizerCommonFlags 48* Address Sanitizer flags: 49 https://github.com/google/sanitizers/wiki/AddressSanitizerFlags 50* libprotobuf-mutator: 51 https://github.com/google/libprotobuf-mutator#libprotobuf-mutator 52 53# Setting up the test <a id="setting_up_the_test"></a> 54 55## Developing an NNAPI fuzz test <a id="developing_an_nnapi_fuzz_test"></a> 56 57### Creating a new fuzz test using `libneuralnetworks_fuzzer_defaults` 58 59To create a new fuzz test: 601. Create code that implements the function 61 `void nnapiFuzzTest(const TestModel& testModel)` (examples: [1][1], [2][2]) 622. Create a blueprint `cc_fuzz` target that includes 63 `libneuralnetworks_fuzzer_defaults` as a default (examples: [1][3], [2][4]) 64 65### Modifying `libneuralnetworks_driver_fuzzer` to test custom driver 66 67Alter the `libneuralnetworks_driver_fuzzer` code locally to test your own 68driver. In the section `“TODO: INSERT CUSTOM DEVICE HERE”`, replace 69`“std::make_shared<const sample::Device>("example-driver")”` ([link][5]) with 70your own driver. 71 72This code employs an in-process driver (as opposed to retrieving it on the 73device via `IDevice::getService(...))` for three reasons. First, the test runs 74faster because it does not need to communicate with the driver via IPC because 75the driver is created in the same process. Second, it ensures that the 76`libFuzzer` can use the coverage from the driver to guide the test 77appropriately, as everything is built as one unit. Finally, whenever a crash 78occurs, only one stacktrace needs to be analyzed to debug the problem. 79 80The current version of the test assumes a 1.3 driver and uses the methods 81`IDevice::prepareModel` and `IDevice::execute` ([link][6]). Change the test 82locally to test different methods or different driver versions. 83 84## Preparing a device <a id="preparing_a_device"></a> 85 86Because the test is self-contained, you should be able to just use a regular 87device image without any modifications. The next section 88[Building and uploading fuzz test](#building-and-uploading-fuzz-test) describes 89how to build the test binary itself. If you need to have the entire image 90fuzzed (for example, if you want to sanitize a shared library), you can build a 91sanitized image with one of the following two sequences of commands depending 92on your needs: 93 94### You can build a normal device image with: 95```bash 96$ . build/envsetup.sh 97$ lunch <target> # e.g., <TARGET_PRODUCT>-userdebug 98$ mma -j 99``` 100 101## Building and uploading fuzz test <a id="building_and_uploading_fuzz_test"></a> 102 103For simplicity and clarity, the rest of the code here will use the following 104environment variables: 105``` 106$ FUZZER_NAME=libneuralnetworks_driver_fuzzer 107$ FUZZER_TARGET_ARCH=$(get_build_var TARGET_ARCH) 108$ FUZZER_TARGET_DIR=/data/fuzz/$FUZZER_TARGET_ARCH/$FUZZER_NAME 109$ FUZZER_TARGET=$FUZZER_TARGET_DIR/$FUZZER_NAME 110``` 111 112When building with a non-sanitized lunch target, build the fuzz test with the 113following command: 114```bash 115$ SANITIZE_TARGET=hwaddress m $FUZZER_NAME -j 116``` 117 118Note that the above commands use `hwaddress` sanitization, but other sanitizers 119can be used in place of or in addition to `hwaddress`. More command options for 120building with other sanitizers can be found [here][7], and they are explained 121more in depth in the Android background reading [here][8]. 122 123Once the test is built, it can be pushed to the device via: 124```bash 125$ adb root 126$ adb sync data 127$ adb shell mkdir -p $FUZZER_TARGET_DIR/dump 128``` 129 130The directory `$FUZZER_TARGET_DIR/` is now as follows: 131* `$FUZZER_NAME` -- fuzz test binary 132* `corpus/` -- directory for reference/example “good” test cases, used to speed 133 up fuzz tests 134* `dump/` -- sandbox directory used by the fuzz test; this can be ignored 135* `crash-*` -- any future problematic test cases will be dumped to the directory 136 137# Running the test <a id="running_the_test"></a> 138 139## Running the full fuzz test <a id="running_the_full_fuzz_test"></a> 140 141The fuzz test can be launched with the following command, and will continue 142running until the user terminates the process (e.g., ctrl+c) or until the test 143crashes. 144 145```bash 146$ 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/ 147``` 148 149(When using a non-hwasan build, you need to change the `HWASAN_OPTIONS` 150variable to match whatever build you’re using, e.g., `ASAN_OPTIONS`.) 151 152When something unexpected occurs (e.g., a crash or a very slow test case), the 153test case that causes it will be dumped to a file in the directory specified by 154“`-artifact_prefix`”. The generated file will appear as 155`slow-unit-<unique_identifier>`, `crash-<unique_identifier>`, 156`oom-<unique_identifier>`, or `timeout-<unique_identifier>`. Normally, 157`libFuzzer` crash files will contain unreadable binary data; however, 158`libneuralnetworks_driver_fuzzer`‘s output is formatted in a human readable way 159because it uses `libprotobuf-mutator`, so it’s fine to inspect the file to get 160more information on the test case that caused the problem. For more 161information, refer to the [Fuzz test case format](#fuzz-test-case-format) 162section below. 163 164## Reproducing crash case <a id="reproducing_crash_case"></a> 165 166When a crash occurs, the crash test case can be re-run with the following 167command: 168 169```bash 170$ 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> 171``` 172(Note that the execution parameters for `HWASAN_OPTIONS` are the same as those 173above.) 174 175E.g., `<test_case_name>` could be: 176* `minimized-from-15b1dae0d2872d8dccf4f35fbf4ecbecee697a49` 177* `slow-unit-cad88bd58853b71b875ac048001b78f7a7501dc3` 178* `crash-07cb8793bbc65ab010382c0f8d40087897826129` 179 180## Finding minimal crash case <a id="finding_minimal_crash_case"></a> 181 182When a crash occurs, sometimes the offending test case is large and 183complicated. `libFuzzer` has a way to minimize the crashing case to simplify 184debugging with the following command: 185 186```bash 187$ 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 188``` 189(Note that the execution parameters for `HWASAN_OPTIONS` are the same as those 190above.) 191 192Note that the `<test_case_name>` must be some sort of crash for the 193minimization to work. For example, minimization will not work on something like 194`slow_unit-*` cases. Increasing the `max_total_time` value may yield a more 195minimal test crash, but will take longer. 196 197# Fuzz test case format <a id="fuzz_test_case_format"></a> 198 199By itself, `libFuzzer` will generate a random collection of bytes as input to 200the fuzz test. The test developer then needs to convert this random data to 201some structured testing format (e.g., a syntactically correct NNAPI model). 202Doing this conversion can be slow and difficult, and can lead to inefficient 203mutations and tests. Additionally, whenever the fuzz test finds a crashing test 204case, it will dump this test case as an unreadable binary chunk of data in a 205file (e.g., `crash-*` files described above). 206 207To help with both of these issues, the NNAPI fuzz tests additionally use a 208library called [`libprotobuf-mutator`][9] to handle the conversions from the 209random `libFuzzer` input to a protobuf format used for NNAPI fuzz testing. The 210conversion from this protobuf format to a model format is much more 211straightforward and efficient. As another useful utility, `libprotobuf-mutator` 212provides the option to represent this data as human-readable text. This means 213that whenever the fuzz test finds a crash, the resultant test case that is 214dumped to a file will be in a human-readable format. 215 216Here is one example of a crash case that was found: 217```protobuf 218model { 219 main { 220 operands { 221 operand { 222 type: TENSOR_QUANT8_ASYMM 223 scale: 1 224 } 225 operand { 226 type: TENSOR_INT32 227 dimensions { 228 dimension: 1 229 } 230 lifetime: CONSTANT_COPY 231 } 232 operand { 233 type: TENSOR_QUANT8_ASYMM 234 dimensions { 235 dimension: 9 236 } 237 scale: 1 238 lifetime: SUBGRAPH_OUTPUT 239 } 240 operand { 241 type: TENSOR_QUANT8_ASYMM 242 dimensions { 243 dimension: 1 244 dimension: 1 245 dimension: 3 246 dimension: 3 247 } 248 scale: 1 249 lifetime: SUBGRAPH_INPUT 250 } 251 operand { 252 type: TENSOR_QUANT8_ASYMM 253 dimensions { 254 dimension: 1 255 } 256 scale: 1 257 lifetime: CONSTANT_COPY 258 } 259 operand { 260 type: INT32 261 lifetime: CONSTANT_COPY 262 } 263 } 264 operations { 265 operation { 266 inputs { 267 index: 3 268 index: 4 269 index: 5 270 } 271 outputs { 272 index: 0 273 } 274 } 275 operation { 276 type: TILE 277 inputs { 278 index: 0 279 index: 1 280 } 281 outputs { 282 index: 2 283 } 284 } 285 } 286 input_indexes { 287 index: 3 288 } 289 output_indexes { 290 index: 2 291 } 292 } 293} 294``` 295 296This format is largely based on the format defined in [NNAPI HAL][10]. The one 297major exception is that the contents of an operand's data are replaced by data 298generated from the “Buffer” message (except for `TEMPORARY_VARIABLE`, 299`NO_VALUE`, and `SUBGRAPH_OUTPUT` operands, in which cases there is no data or 300the data is ignored, so the “Buffer” message is ignored). This is done for a 301practical reason: `libFuzzer` (and by extension `libprotobuf-mutator`) converge 302slower when the amount of randomly generated input is large. For the fuzz tests, 303the contents of the operand data are not as interesting as the structure of the 304graph itself, so the data was replaced by a “Buffer”, which is one of the 305following: 306* EmptyBuffer empty, represented no value 307* uint32_t scalar, repesenting a scalar value 308* uint32_t random_seed, used to generate random data 309 310The NNAPI `libprotobuf-mutator` implementation uses the proto3 specification. 311Note that this means whenever a field contains the default value (e.g., uint32_t 312holds a value of 0), [that field is omitted from the text][11]. For example, in 313the test case listed above, `model.main.operations[0].operation.type` is omitted 314because it holds the value `ADD`. 315 316# Changing Model.proto <a id="changing_model_proto"></a> 317 318## Adding new operations or operands <a id="adding_new_operations_or_operands"></a> 319 320When adding a new operation to the NNAPI, the fuzzer should be updated with the 321following steps: 3221. Add the new operation or operand to either `OperationType` or `OperandType`. 323in `Model.proto` 3242. Add a new `static_assert` for the new type in `StaticAssert.cpp` to make sure 325the value in `Model.proto` matches the value in `TestHarness` (e.g., 326`TestOperationType` or `TestOperandType`) 327 328## Expanding the structure <a id="expanding_the_structure"></a> 329 330For any deeper changes to the `Model.proto` type (e.g., adding a new field): 3311. Make the change to `TestHarness` 3322. Make the corresponding change in `Model.proto` to mirror `TestHarness` 3333. Make the corresponding change in `Converter.cpp` to handle the conversion 334from `Model.proto` types to `TestHarness` types. 3354. Make the corresponding change in `GenerateCorpus.cpp` to handle the 336conversion from `TestHarness` types to `Model.proto` types. 337 338## Backward-incompatible change <a id="backward_incompatible_change"></a> 339 340Making an backward-incompatible change (e.g., removing proto fields) affects the 341existing corpus, so the corpus needs to be regenerated for the test to continue 342to run efficiently. In addition to the steps in 343[Expanding the structure](#expanding_struct) above, the corpus can be 344regenerated with the following steps: 3451. run libneuralnetworks_fuzzer_seed_corpus to generate the corpus entries 3462. Unzip the generated files (>8000 test cases) 3473. [Merge][12] the cases with libneuralnetworks_fuzzer to reduce the corpus size 3484. Take and rename the top 500 cases to seedXXX 3495. Update the corpus with the new seed files 350 351# Appendix <a id="appendix"></a> 352 353## Alternative ways to build the device image and test binary <a id="alternatives"></a> 354 355You can build a pre-configured sanitized device image with: 356```bash 357$ . build/envsetup.sh 358$ lunch <sanitized_target> # e.g., <TARGET_PRODUCT>_hwasan-userdebug 359$ mma -j 360``` 361 362Alternatively, you can build other (read: non-sanitized) targets with the following command: 363```bash 364$ . build/envsetup.sh 365$ lunch <non-sanitized_target> # e.g., <TARGET_PRODUCT>-userdebug 366$ SANITIZE_TARGET=hwaddress mma -j 367``` 368 369When using a sanitized lunch target, build the fuzz test with the following 370command: 371```bash 372$ m $FUZZER_NAME -j 373``` 374 375[1]: https://cs.android.com/android/platform/superproject/+/master:packages/modules/NeuralNetworks/runtime/test/android_fuzzing/DriverFuzzTest.cpp;l=307-324;drc=34aee872d5dc317ad8a32377e9114c0c606d8afe 376[2]: https://cs.android.com/android/platform/superproject/+/master:packages/modules/NeuralNetworks/runtime/test/android_fuzzing/FuzzTest.cpp;l=130-151;drc=34aee872d5dc317ad8a32377e9114c0c606d8afe 377[3]: https://cs.android.com/android/platform/superproject/+/master:packages/modules/NeuralNetworks/runtime/test/Android.bp;l=195-216;drc=60823f07172e6b5bbc06b2fac25a15ab91c80b25 378[4]: https://cs.android.com/android/platform/superproject/+/master:packages/modules/NeuralNetworks/runtime/test/Android.bp;l=218-240;drc=60823f07172e6b5bbc06b2fac25a15ab91c80b25 379[5]: https://cs.android.com/android/platform/superproject/+/master:packages/modules/NeuralNetworks/runtime/test/android_fuzzing/DriverFuzzTest.cpp?q=example-driver&ss=android%2Fplatform%2Fsuperproject 380[6]: https://cs.android.com/search?q=prepareModel%20execute&ss=android%2Fplatform%2Fsuperproject:packages%2Fmodules%2FNeuralNetworks%2Fruntime%2Ftest%2Fandroid_fuzzing%2F 381[7]: https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/sanitize.go;l=140-187;drc=b5b2aba43b5bb6305ea69d60f9bf580f711d7c96 382[8]: https://source.android.com/devices/tech/debug/libfuzzer 383[9]: https://cs.android.com/android/platform/superproject/+/master:external/libprotobuf-mutator/ 384[10]: https://cs.android.com/android/platform/superproject/+/master:hardware/interfaces/neuralnetworks/ 385[11]: https://developers.google.com/protocol-buffers/docs/proto3#default 386[12]: https://llvm.org/docs/LibFuzzer.html#corpus 387