README.md
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