• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..12-Dec-2023-

corpus/H12-Dec-2023-241,733241,233

Android.bpH A D12-Dec-20233.6 KiB117109

Converter.cppH A D12-Dec-20236.3 KiB167123

Converter.hH A D12-Dec-20231 KiB308

DriverFuzzTest.cppH A D12-Dec-202312.8 KiB325242

FuzzHarness.cppH A D12-Dec-20231.7 KiB5528

FuzzTest.cppH A D12-Dec-20234.8 KiB152100

GenerateCorpus.cppH A D12-Dec-20235.8 KiB186145

Model.protoH A D12-Dec-20234.3 KiB202185

README.mdH A D12-Dec-202312.4 KiB311267

StaticAssert.cppH A D12-Dec-202312 KiB184161

README.md

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