)
96 |
97 | Address 0x000102e03ce2 is a wild pointer inside of access range of size 0x000000000001.
98 | SUMMARY: AddressSanitizer: heap-buffer-overflow upng.c:546 in inflate_huffman
99 | Shadow bytes around the buggy address:
100 | 0x0070205e0740: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
101 | 0x0070205e0750: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
102 | 0x0070205e0760: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
103 | 0x0070205e0770: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
104 | 0x0070205e0780: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
105 | =>0x0070205e0790: fa fa fa fa fa fa fa fa fa fa fa fa[fa]fa fa fa
106 | 0x0070205e07a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
107 | 0x0070205e07b0: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
108 | 0x0070205e07c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
109 | 0x0070205e07d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04
110 | 0x0070205e07e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
111 | Shadow byte legend (one shadow byte represents 8 application bytes):
112 | Addressable: 00
113 | Partially addressable: 01 02 03 04 05 06 07
114 | Heap left redzone: fa
115 | Freed heap region: fd
116 | Stack left redzone: f1
117 | Stack mid redzone: f2
118 | Stack right redzone: f3
119 | Stack after return: f5
120 | Stack use after scope: f8
121 | Global redzone: f9
122 | Global init order: f6
123 | Poisoned by user: f7
124 | Container overflow: fc
125 | Array cookie: ac
126 | Intra object redzone: bb
127 | ASan internal: fe
128 | Left alloca redzone: ca
129 | Right alloca redzone: cb
130 | ==17672==ABORTING
131 | ```
132 |
133 | **Note:** Corpus majoritarily taken from [go-fuzz-corpus](https://github.com/dvyukov/go-fuzz-corpus/tree/master/png) which is under an [Apache License 2.0](https://github.com/dvyukov/go-fuzz-corpus/blob/master/LICENSE).
134 |
135 | ## Tracing a Testcase
136 |
137 | It is also possible to trace the target with the following command:
138 |
139 | ```
140 | make trace /path/to/testcase
141 | ```
142 |
143 | The resulting trace file can be found in `tmp/trace.txt`.
144 |
145 | ```console
146 | $ head tmp/trace.txt
147 | 0x21efe4: stp x29, x30, [sp, #-0x20]!
148 | 0x21efe8: stp x20, x19, [sp, #0x10]
149 | 0x21efec: mov x29, sp
150 | 0x21eff0: mov x20, x0
151 | 0x21eff4: mov w0, #0x50
152 | 0x21eff8: mov x19, x1
153 | 0x21effc: bl #0x257338
154 | 0x257338: paciasp
155 | 0x21f000: cbz x0, #0x21f030
156 | 0x21f004: adrp x8, #0x206000
157 | ```
158 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! Apple Silicon hypervisor-based fuzzer for ARM64 binaries.
2 | //!
3 | //! ## Disclaimer
4 | //!
5 | //! The idea behind this project was to create an efficient and fast fuzzer that would leverage
6 | //! Apple Silicon's features. However, at this stage, while the fuzzer works, it is still mostly a
7 | //! proof of concept and requires tons of enhancement to provide better features, usability and
8 | //! performances.
9 | //!
10 | //! It might be enough for your use cases, but keep in mind that you might encounter limitations
11 | //! that weren't factored in while designing the project. In any case, feel free to
12 | //! [open an issue](https://github.com/impalabs/hyperpom/issues) and we'll try to address your
13 | //! problem.
14 | //!
15 | //! ## Hyperpom Internals & Usage
16 | //!
17 | //! If you want an in-depth guide on how to use this fuzzer, you can directly jump to the
18 | //! chapter about the [`Loader`](loader::Loader), which provides different examples.
19 | //!
20 | //! Otherwise, if you want a better understanding of the fuzzer's implementation and the
21 | //! interactions between its components, it is recommended to read the documentation in the
22 | //! following order.
23 | //!
24 | //! 1. Memory Management
25 | //! 1. [Physical Memory Allocator](memory::PhysMemAllocator)
26 | //! 2. [Slab Allocator](memory::SlabAllocator)
27 | //! 3. [Page Table Manager](memory::PageTableManager)
28 | //! 4. [Virtual Memory Allocator](memory::VirtMemAllocator)
29 | //! 2. [Exception Handling](exceptions::Exceptions)
30 | //! 3. [Cache Maintenance](caches::Caches)
31 | //! 4. [Hooks](hooks::Hooks)
32 | //! 5. [Coverage](coverage::GlobalCoverage)
33 | //! 6. [Tracing](tracer::Tracer)
34 | //! 7. [Corpus](corpus::Corpus)
35 | //! 8. [Mutator](mutator::Mutator)
36 | //! 9. Fuzzer's Core
37 | //! 1. [HyperPom](core::HyperPom)
38 | //! 2. [Worker](core::Worker)
39 | //! 3. [Executor](core::Executor)
40 | //! 10. [Config](config::Config)
41 | //! 11. [Loader](loader::Loader)
42 | //!
43 | //! ## Getting Started
44 | //!
45 | //! ### Prerequisites
46 | //!
47 | //! 1. Install Rust and `rustup` using the
48 | //! [official guide](https://www.rust-lang.org/tools/install).
49 | //! 2. Install the [nightly channel](https://rust-lang.github.io/rustup/concepts/channels.html).
50 | //!
51 | //! ```
52 | //! rustup toolchain install nightly
53 | //! ```
54 | //!
55 | //! 3. To use this channel when compiling you can either:
56 | //!
57 | //! - set it as default using `rustup default nightly`;
58 | //! - or add `+nightly` everytime you compile a binary with `cargo`.
59 | //!
60 | //! 4. Install Cmake, using `brew` for example:
61 | //!
62 | //! ```console
63 | //! brew install cmake
64 | //! ```
65 | //!
66 | //! ### Self-Signed Binaries and Hypervisor Entitlement
67 | //!
68 | //! To be able to reach the Hypervisor Framework, a binary executable has to have been granted the [hypervisor entitlement](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_hypervisor).
69 | //!
70 | //! You can add this entitlement to a binary located at `/path/to/binary` by using the `entitlements.xml` file found at the root of the Hyperpom repository and the following command:
71 | //!
72 | //! ```
73 | //! codesign --sign - --entitlements entitlements.xml --deep --force /path/to/binary
74 | //! ```
75 | //!
76 | //!
77 | //! ### Compilation Workflow
78 | //!
79 | //! Create a Rust project and add Hyperpom as a dependency in `Cargo.toml`. You can either pull it
80 | //! from [crates.io](https://crates.io/crates/hyperpom) ...
81 | //!
82 | //! ```toml
83 | //! # Check which version is the latest, this part of the README might not be updated
84 | //! # in future releases.
85 | //! hyperpom = "0.1.0"
86 | //! ```
87 | //!
88 | //! ... or directly from the [GitHub repository](https://github.com/impalabs/hyperpom).
89 | //!
90 | //! ```toml
91 | //! hyperpom = { git="https://github.com/impalabs/hyperpom", branch="master" }
92 | //! ```
93 | //!
94 | //! Create a file called `entitlements.txt` in the project's root directory and add the following:
95 | //!
96 | //! ```xml
97 | //!
98 | //!
99 | //!
100 | //!
101 | //! com.apple.security.hypervisor
102 | //!
103 | //!
104 | //!
105 | //! ```
106 | //!
107 | //! Write code and then build the project.
108 | //!
109 | //! ```
110 | //! cargo build --release
111 | //! ```
112 | //!
113 | //! Sign the binary and grant the hypervisor entitlement.
114 | //!
115 | //! ```
116 | //! codesign --sign - --entitlements entitlements.xml --deep --force target/release/${PROJECT_NAME}
117 | //! ```
118 | //!
119 | //! Run the binary.
120 | //!
121 | //! ```
122 | //! target/release/${PROJECT_NAME}
123 | //! ```
124 | //!
125 | //! ## Examples
126 | //!
127 | //! Four examples are provided to give you a better understanding of how the framework operates and
128 | //! get you started:
129 | //!
130 | //! * [simple_executor](https://github.com/impalabs/hyperpom/tree/master/examples/simple_executor):
131 | //! showcases how to run arbitrary code in a VM using an `Executor`.
132 | //! * [simple_tracer](https://github.com/impalabs/hyperpom/tree/master/examples/simple_tracer):
133 | //! runs a program while tracing its instructions.
134 | //! * [simple_fuzzer](https://github.com/impalabs/hyperpom/tree/master/examples/simple_fuzzer):
135 | //! fuzzes a simple program.
136 | //! * [upng_fuzzer](https://github.com/impalabs/hyperpom/tree/master/examples/upng_fuzzer): fuzzer
137 | //! for the [uPNG](https://github.com/elanthis/upng/) library.
138 | //!
139 | //! You can also have a look at the
140 | //! [tests](https://github.com/impalabs/hyperpom/tree/master/tests/tests.rs).
141 |
142 | #![feature(exclusive_range_pattern)]
143 | #![feature(iterator_try_collect)]
144 | #![feature(map_try_insert)]
145 | #![feature(portable_simd)]
146 | #![feature(slice_partition_dedup)]
147 |
148 | pub mod backtrace;
149 | pub mod caches;
150 | pub mod config;
151 | pub mod core;
152 | pub mod corpus;
153 | pub mod coverage;
154 | pub mod crash;
155 | pub mod error;
156 | pub mod exceptions;
157 | pub mod hooks;
158 | pub mod loader;
159 | pub mod memory;
160 | pub mod mutator;
161 | pub mod tracer;
162 | pub mod utils;
163 |
164 | pub extern crate applevisor;
165 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | HYPERPOM
6 |
7 | AArch64 fuzzing library based on the Apple Silicon hypervisor
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | ## Table of contents
25 |
26 | * [Disclaimer](#warning-disclaimer)
27 | * [Getting Started](#getting-started)
28 | * [Prerequisites](#prerequisites)
29 | * [Self-Signed Binaries and Hypervisor Entitlement](#self-signed-binaries-and-hypervisor-entitlement)
30 | * [Compilation Workflow](#compilation-workflow)
31 | * [Documentation](#documentation)
32 | * [Examples](#examples)
33 | * [Running the Tests](#running-the-tests)
34 | * [Authors](#authors)
35 |
36 |
37 | Hyperpom is a coverage-guided mutation-based fuzzing framework built on top of the [Apple Silicon Hypervisor](https://developer.apple.com/documentation/hypervisor/apple_silicon). It has been designed to easily instrument and fuzz AArch64 userland binaries.
38 |
39 | ## :warning: Disclaimer
40 |
41 | The idea behind this project was to create an efficient and fast fuzzer that would leverage Apple Silicon's features. However, at this stage, while the fuzzer works, it is still mostly a proof of concept and requires tons of enhancement to provide better features, usability and performances.
42 |
43 | It might be enough for your use cases, but keep in mind that you might encounter limitations that weren't factored in while designing the project. In any case, feel free to open an issue and we'll try to address your problem.
44 |
45 | ## Getting Started
46 |
47 | ### Prerequisites
48 |
49 | 1. Install Rust and `rustup` using the [official guide](https://www.rust-lang.org/tools/install).
50 | 2. Install the [nightly channel](https://rust-lang.github.io/rustup/concepts/channels.html).
51 |
52 | ```
53 | rustup toolchain install nightly
54 | ```
55 |
56 | 3. To use this channel when compiling you can either:
57 |
58 | - set it as default using `rustup default nightly`;
59 | - or add `+nightly` everytime you compile a binary with `cargo`.
60 |
61 | 4. Install Cmake, using `brew` for example:
62 |
63 | ```console
64 | brew install cmake
65 | ```
66 |
67 | ### Self-Signed Binaries and Hypervisor Entitlement
68 |
69 | To be able to reach the Hypervisor Framework, a binary executable has to have been granted the [hypervisor entitlement](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_hypervisor).
70 |
71 | You can add this entitlement to a binary located at `/path/to/binary` by using the `entitlements.xml` file found at the root of the Hyperpom repository and the following command:
72 |
73 | ```
74 | codesign --sign - --entitlements entitlements.xml --deep --force /path/to/binary
75 | ```
76 |
77 | ### Compilation Workflow
78 |
79 | Create a Rust project and add Hyperpom as a dependency in `Cargo.toml`. You can either pull it from [crates.io](https://crates.io/crates/hyperpom) ...
80 |
81 | ```toml
82 | # Check which version is the latest, this part of the README might not be updated
83 | # in future releases.
84 | hyperpom = "0.1.0"
85 | ```
86 |
87 | ... or directly from the [GitHub repository](https://github.com/impalabs/hyperpom).
88 |
89 | ```toml
90 | hyperpom = { git="https://github.com/impalabs/hyperpom", branch="master" }
91 | ```
92 |
93 | Create a file called `entitlements.txt` in the project's root directory and add the following:
94 |
95 | ```xml
96 |
97 |
98 |
99 |
100 | com.apple.security.hypervisor
101 |
102 |
103 |
104 | ```
105 |
106 | Write code and then build the project.
107 |
108 | ```
109 | cargo build --release
110 | ```
111 |
112 | Sign the binary and grant the hypervisor entitlement.
113 |
114 | ```
115 | codesign --sign - --entitlements entitlements.xml --deep --force target/release/${PROJECT_NAME}
116 | ```
117 |
118 | Run the binary.
119 |
120 | ```
121 | target/release/${PROJECT_NAME}
122 | ```
123 |
124 | ## Documentation
125 |
126 | The documentation is available online at the following address: [https://docs.rs/hyperpom](https://docs.rs/hyperpom)
127 |
128 | Alternatively, you can generate it using `cargo`:
129 |
130 | ```
131 | cargo doc --open
132 | ```
133 |
134 | The documentation contains information on using the framework and its internals. For an in-depth guide, have a look at the `Loader` chapter, which provides examples on how to use the fuzzer and harness your targets.
135 |
136 | ## Examples
137 |
138 | Four examples are provided to give you a better understanding of how the framework operates and get you started:
139 |
140 | * [simple_executor](examples/simple_executor): showcases how to run arbitrary code in a VM using an `Executor`.
141 | * [simple_tracer](examples/simple_tracer): runs a program while tracing its instructions.
142 | * [simple_fuzzer](examples/simple_fuzzer): fuzzes a simple program.
143 | * [upng_fuzzer](examples/upng_fuzzer): fuzzer for the [uPNG](https://github.com/elanthis/upng/) library.
144 |
145 | You can also have a look at the [tests](tests/tests.rs).
146 |
147 | ## Running the Tests
148 |
149 | To run tests using the `Makefile` provided with the project, you'll first need to install [`jq`](https://stedolan.github.io/jq/download/). You can do so using `brew`:
150 |
151 | ```
152 | brew install jq
153 | ```
154 |
155 | You can then run the tests with the provided `Makefile` using the following command:
156 |
157 | ```
158 | make tests
159 | ```
160 |
161 | ## Authors
162 |
163 | * [**Maxime Peterlin**](https://twitter.com/lyte__) - hyperpom@impalabs.com
164 |
--------------------------------------------------------------------------------
/examples/upng_fuzzer/bin/src/README:
--------------------------------------------------------------------------------
1 | uPNG -- derived from LodePNG version 20100808
2 | ==========================================
3 |
4 | Copying
5 | -------
6 |
7 | Copyright (c) 2005-2010 Lode Vandevenne
8 | Copyright (c) 2010 Sean Middleditch
9 |
10 | This software is provided 'as-is', without any express or implied
11 | warranty. In no event will the authors be held liable for any damages
12 | arising from the use of this software.
13 |
14 | Permission is granted to anyone to use this software for any purpose,
15 | including commercial applications, and to alter it and redistribute it
16 | freely, subject to the following restrictions:
17 |
18 | 1. The origin of this software must not be misrepresented; you must not
19 | claim that you wrote the original software. If you use this software
20 | in a product, an acknowledgment in the product documentation would be
21 | appreciated but is not required.
22 |
23 | 2. Altered source versions must be plainly marked as such, and must not be
24 | misrepresented as being the original software.
25 |
26 | 3. This notice may not be removed or altered from any source
27 | distribution.
28 |
29 | Features
30 | --------
31 |
32 | uPNG supports loading and decoding PNG images into a simple byte buffer, suitable
33 | for passing directly to OpenGL as texture data.
34 |
35 | uPNG does NOT support interlaced images, paletted images, and fixed-transparency.
36 | Checksums are NOT verified and corrupt image data may be undetected.
37 |
38 | It DOES support RGB, RGBA, greyscale, and greyscale-with-alpha images. RGB and
39 | RGBA are currently only supported in 8-bit color depths, and greyscale images
40 | are supported in either 1-, 2-, 4-, or 8-bit color depths.
41 |
42 | WARNING: the source project that uPNG is derived from, LodePNG, did not have
43 | the cleanest or best documented code. Several potential buffer overflows in the
44 | original source have been fixed in uPNG, but there may be more. Do NOT use uPNG
45 | to load data from untrusted sources, e.g. the Web. Doing so may open a
46 | remotely exploitable buffer overflow attack in your application.
47 |
48 | Installation
49 | ------------
50 |
51 | Copy the upng.c and upng.h files into your project, and add them to your build
52 | system. upng.c will compile as C++ if necessary.
53 |
54 | Usage
55 | -----
56 |
57 | To load a PNG, you must create an upng_t instance, load the raw PNG into the
58 | decoder, and then you can query the upng_t for image properties and the
59 | decoded image buffer.
60 |
61 | upng_t* upng;
62 |
63 | upng = upng_new_from_file("image.png");
64 | if (upng != NULL) {
65 | upng_decode(upng);
66 | if (upng_get_error(upng) == UPNG_EOK) {
67 | /* do stuff with image */
68 | }
69 |
70 | upng_free(upng);
71 | }
72 |
73 | You can load a PNG either from an in-memory buffer of bytes or from a file
74 | specified by file path.
75 |
76 | upng_new_from_bytes(const unsigned char*, unsigned long length)
77 | upng_new_from_file(const char*)
78 |
79 | Once an upng_t object is created, you can read just its header properties,
80 | decode the entire file, and release its resources.
81 |
82 | upng_header(upng_t*) Reads just the header, sets image properties
83 | upng_decode(upng_t*) Decodes image data
84 | upng_free(upng_t*) Frees the resources attached to a upng_t object
85 |
86 | The query functions are:
87 |
88 | upng_get_width(upng_t*) Returns width of image in pixels
89 | upng_get_height(upng_t*) Returns height of image in pixels
90 | upng_get_size(upng_t*) Returns the total size of the image buffer in bytes
91 | upng_get_bpp(upng_t*) Returns the number of bits per pixel (e.g., 32 for 8-bit RGBA)
92 | upng_get_bitdepth(upng_t*) Returns the number of bits per component (e.g., 8 for 8-bit RGBA)
93 | upng_get_pixelsize(upng_t*) Returns the number of bytes per pixel (e.g., 4 for 8-bit RGBA)
94 | upng_get_components(upng_t*) Returns the number of components per pixel (e.g., 4 for 8-bit RGBA)
95 | upng_get_format(upng_t*) Returns the format of the image buffer (see below)
96 | upng_get_buffer(upng_t*) Returns a pointer to the image buffer
97 |
98 | Additionally, for error handling, you can use:
99 |
100 | upng_get_error(upng_t*) Returns the error state of the upng object (UPNG_EOK means no error)
101 | upng_get_error_line(upng_t*) Returns the line in the upng.c file where the error state was set
102 |
103 | The formats supported are:
104 |
105 | UPNG_RGB8 24-bit RGB
106 | UPNG_RGB16 48-bit RGB
107 | UPNG_RGBA8 32-bit RGBA
108 | UPNG_RGBA16 64-bit RGBA
109 | UPNG_LUMINANCE8 8-bit Greyscale
110 | UPNG_LUMINANCEA8 8-bit Greyscale w/ 8-bit Alpha
111 |
112 | Possible error states are:
113 |
114 | UPNG_EOK No error (success)
115 | UPNG_ENOMEM Out of memory
116 | UPNG_ENOTFOUND Resource not found
117 | UPNG_ENOTPNG Invalid file header (not a PNG image)
118 | UPNG_EMALFORMED PNG image data does not follow spec and is malformed
119 | UPNG_EUNSUPPORTED PNG image data is well-formed but not supported by uPNG
120 |
121 | TODO
122 | ----
123 |
124 | - Audit the code (particularly the Huffman decoder) for buffer overflows. Make sure
125 | uPNG is safe to use even with image data from untrusted sources.
126 |
127 | - Make the decompressor work in a streaming/buffered manner, so that we don't need
128 | to stitch together the PNG IDATA chunks before decompressing, shaving off one
129 | unnecessary allocation.
130 |
131 | - Update the unfiltering code to work on the decompressed image buffer, rather than
132 | needing a separate output buffer. The removal of the Adam7 de-interlacing support
133 | makes this easier. Removes another unnecessary allocation.
134 |
135 | - Update the decoder API to work in a stream/buffered manner, so files can be read
136 | without needing to allocate a temporary buffer. This removes yet another
137 | unnecessary allocation.
138 |
139 | - Update the decoder API to allow the user to provide an output buffer, so that
140 | PNG images can be decoded directly to mapped texture memory. Removes the need
141 | for the last unnecessary allocation.
142 |
143 | - Test that greyscale images with less than 8-bits of depth actually work, fix
144 | or remove if they do not.
145 |
146 | - Provide optional format conversion (as an extension to byte swizzling) to
147 | convert input PNGs in one format to one of a (limited) set of target output
148 | formats commonly used for texturing.
149 |
150 | - Provide floating-point conversion, at least for 16-bit source images, for
151 | HDR textures.
152 |
153 | - Provide vertical flipping of decoded image data for APIs that prefer textures
154 | with an origin in the lower-left instead of upper-left.
155 |
--------------------------------------------------------------------------------
/examples/upng_fuzzer/src/upngzz.rs:
--------------------------------------------------------------------------------
1 | use std::io::Write;
2 | use std::path::PathBuf;
3 | use std::time::Duration;
4 |
5 | use clap::{Args, Parser, Subcommand};
6 | use eyre::Result;
7 | use hyperpom as hp;
8 | use hyperpom::applevisor as av;
9 | use hyperpom::config::*;
10 | use hyperpom::core::*;
11 | use hyperpom::corpus::*;
12 | use hyperpom::crash::*;
13 | use hyperpom::hooks::*;
14 |
15 | use crate::loader::*;
16 |
17 | /// The configuration object created from the CLI arguments passed to upngzz.
18 | #[derive(Parser, Debug)]
19 | #[clap(
20 | author = "lyte ",
21 | version = "1.0",
22 | about = "upng fuzzer",
23 | long_about = None)]
24 | pub struct Config {
25 | /// Subcommands.
26 | #[clap(subcommand)]
27 | command: Commands,
28 | }
29 |
30 | /// Fuzzer's CLI subcommands.
31 | #[derive(Debug, Subcommand)]
32 | enum Commands {
33 | /// Fuzzing subcommand.
34 | #[clap(arg_required_else_help = true)]
35 | Fuzz(FuzzSubCommand),
36 |
37 | /// Tracing subcommand.
38 | #[clap(arg_required_else_help = true)]
39 | Trace(TraceSubCommand),
40 | }
41 |
42 | #[derive(Debug, Args)]
43 | pub struct FuzzSubCommand {
44 | /// Path to the binary to fuzz.
45 | #[clap(short = 'b', long = "binary", value_name = "BINARY", required = true,
46 | value_hint = clap::ValueHint::FilePath)]
47 | binary: PathBuf,
48 |
49 | /// Path to the directory containing the shared libraries.
50 | #[clap(short = 'l', long = "libs", value_name = "LIBS",
51 | value_hint = clap::ValueHint::DirPath)]
52 | libs: Option,
53 |
54 | /// Path to the corpus.
55 | #[clap(short = 'c', long = "corpus", value_name = "CORPUS", required = true,
56 | value_hint = clap::ValueHint::DirPath)]
57 | corpus_dir: PathBuf,
58 |
59 | /// Path to the work directory.
60 | #[clap(short = 'd', long = "workdir", value_name = "WORKDIR", required = true,
61 | value_hint = clap::ValueHint::DirPath)]
62 | work_dir: PathBuf,
63 |
64 | /// Number of workers to spawn.
65 | #[clap(
66 | short = 'w',
67 | long = "workers",
68 | value_name = "WORKERS",
69 | default_value = "1"
70 | )]
71 | nb_workers: u32,
72 |
73 | /// Number of testcases executed by the worker before stopping.
74 | #[clap(short = 'i', long = "iterations", value_name = "ITERATIONS")]
75 | nb_iterations: Option,
76 |
77 | /// Physical address space size available to the fuzzer.
78 | #[clap(
79 | short = 's',
80 | long = "size",
81 | value_name = "SIZE",
82 | required = true,
83 | value_parser=clap_num::maybe_hex::,
84 | )]
85 | as_size: usize,
86 | }
87 |
88 | #[derive(Debug, Args)]
89 | pub struct TraceSubCommand {
90 | /// Path to the binary to fuzz.
91 | #[clap(short = 'b', long = "binary", value_name = "BINARY", required = true,
92 | value_hint = clap::ValueHint::FilePath)]
93 | binary: PathBuf,
94 |
95 | /// Path to the directory containing the shared libraries.
96 | #[clap(short = 'l', long = "libs", value_name = "LIBS",
97 | value_hint = clap::ValueHint::DirPath)]
98 | libs: Option,
99 |
100 | /// Path to the testcase to trace.
101 | #[clap(short = 't', long = "testcase", value_name = "TESTCASE", required = true,
102 | value_hint = clap::ValueHint::FilePath)]
103 | testcase: PathBuf,
104 |
105 | /// Path to the trace output file.
106 | #[clap(short = 'o', long = "trace", value_name = "TRACE", required = true,
107 | value_hint = clap::ValueHint::FilePath)]
108 | trace: PathBuf,
109 |
110 | /// Physical address space size available to the fuzzer.
111 | #[clap(
112 | short = 's',
113 | long = "size",
114 | value_name = "SIZE",
115 | required = true,
116 | value_parser=clap_num::maybe_hex::,
117 | )]
118 | as_size: usize,
119 | }
120 |
121 | pub fn from_hex(hex: &str) -> usize {
122 | let hex_stripped = hex.trim_start_matches("0x");
123 | usize::from_str_radix(hex_stripped, 16).expect("could not parse hex number")
124 | }
125 |
126 | pub fn entry(config: Config) -> Result<()> {
127 | match config.command {
128 | Commands::Fuzz(sub) => fuzz(sub),
129 | Commands::Trace(sub) => trace(sub),
130 | }
131 | }
132 |
133 | pub fn fuzz(config: FuzzSubCommand) -> Result<()> {
134 | // Instanciates global and local data.
135 | let gdata = GlobalData::new(None::<&std::path::Path>);
136 | let ldata = LocalData::new();
137 | // Creates a loader for the target binary.
138 | let loader = PngLoader::new(config.binary)?;
139 | // Creates a config for the fuzzer.
140 | let config = FuzzConfig::::builder(
141 | config.as_size,
142 | config.work_dir,
143 | config.corpus_dir,
144 | )
145 | .nb_workers(config.nb_workers)
146 | .seed(0xdeadbeefdeadbeef)
147 | .max_nb_mutations(0x800)
148 | .max_testcase_size(PngLoader::TESTCASE_MAX_SIZE)
149 | .timeout(Duration::new(60, 0))
150 | .iterations(config.nb_iterations)
151 | .build();
152 | // Creates an instance of the fuzzer.
153 | let mut hp =
154 | HyperPom::<_, _, _>::new(config, loader, ldata, gdata).expect("could not create fuzzer");
155 | // Start fuzzing!
156 | hp.fuzz()?;
157 | Ok(())
158 | }
159 |
160 | pub fn trace(config: TraceSubCommand) -> Result<()> {
161 | let _vm = av::VirtualMachine::new();
162 | // Instanciates global and local data.
163 | let gdata = GlobalData::new(Some(&config.trace));
164 | let ldata = LocalData::new();
165 | // Reads the testcase from disk.
166 | let testcase = Testcase::from_file(config.testcase)?;
167 | // Creates a loader for the target binary.
168 | let loader = PngLoader::new(config.binary)?;
169 | // Creates a config for the fuzzer.
170 | let config = ExecConfig::::builder(config.as_size)
171 | .nb_workers(1)
172 | .timeout(Duration::new(60, 0))
173 | .iterations(Some(1))
174 | .tracer(true)
175 | .tracer_hook(tracer_hook)
176 | .build();
177 | // Creates an instance of the fuzzer.
178 | let mut executor =
179 | Executor::::new(config, loader, ldata, gdata)
180 | .expect("could not create the executor");
181 | // Start tracing!
182 | executor.init()?;
183 | executor.run(Some(&testcase)).expect("execution failed");
184 | println!("{}", executor.vcpu);
185 | println!("Testcase covered {:?} paths", executor.cdata.set.len());
186 | Ok(())
187 | }
188 |
189 | /// Handles tracing hooks and displays the current instruction to `stdout`.
190 | pub fn tracer_hook(args: &mut HookArgs) -> hp::error::Result {
191 | let gd = args.gdata.write().unwrap();
192 | let mut trace = std::fs::OpenOptions::new()
193 | .create(true)
194 | .write(true)
195 | .append(true)
196 | .open(gd.path.as_ref().unwrap())
197 | .unwrap();
198 | CSE.with(|cs| {
199 | let insns = cs
200 | .disasm_count(args.insn, args.addr, 1)
201 | .expect("could not disassemble while adding coverage hooks");
202 | let insn = insns.as_ref().first().unwrap();
203 | writeln!(trace, "{}", insn).expect("could append instructions to the trace");
204 | });
205 | Ok(ExitKind::Continue)
206 | }
207 |
--------------------------------------------------------------------------------
/src/utils.rs:
--------------------------------------------------------------------------------
1 | //! Miscellaneous functions used by different modules of the fuzzer.
2 |
3 | use std::arch::asm;
4 |
5 | // -----------------------------------------------------------------------------------------------
6 | // Code ranges
7 |
8 | /// A range of virtual addresses that contains instructions.
9 | #[derive(Clone, Eq, PartialEq, Hash, Debug)]
10 | pub struct CodeRange(pub(crate) std::ops::Range);
11 |
12 | impl CodeRange {
13 | /// Creates a new code range.
14 | ///
15 | /// Since we can't just instrument everything, because of data sections found in code ranges
16 | /// that could be interpreted as instructions. The user is responsible for identifying which
17 | /// ranges are actual code ranges.
18 | pub fn new(start: u64, end: u64) -> Self {
19 | Self(start..end)
20 | }
21 | }
22 |
23 | // -----------------------------------------------------------------------------------------------
24 | // Random generator
25 |
26 | /// Random number generator based on the xorshift algorithm.
27 | #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
28 | pub struct Random {
29 | /// The seed used for random generation.
30 | seed: u64,
31 | }
32 |
33 | impl Random {
34 | /// Set of alphanumeric characters that can be used when generating random strings.
35 | const ALPHANUM: &'static str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
36 |
37 | /// Creates a new random number generator.
38 | #[inline]
39 | pub fn new(seed: u64) -> Self {
40 | assert_ne!(seed, 0);
41 | Self { seed }
42 | }
43 |
44 | /// Splits the current random number generator into a second one in a deterministic manner.
45 | #[inline]
46 | pub fn split(&mut self) -> Self {
47 | Self::new(self.u64() ^ 0x43e47ca448538d19)
48 | }
49 |
50 | /// Updates the PRNG's internal seed.
51 | #[inline]
52 | fn update(&mut self) {
53 | self.seed ^= self.seed << 13;
54 | self.seed ^= self.seed >> 7;
55 | self.seed ^= self.seed << 17;
56 | }
57 |
58 | /// Retrieves the PRNG's state without updating it.
59 | #[inline]
60 | pub fn get_state(&self) -> u64 {
61 | self.seed
62 | }
63 |
64 | /// Generates a random `u64` using a uniform distribution.
65 | #[inline]
66 | pub fn u64(&mut self) -> u64 {
67 | self.update();
68 | self.seed
69 | }
70 |
71 | /// Generates a random `u64` in the range `[start; end[` using a uniform distribution.
72 | #[inline]
73 | pub fn u64_range(&mut self, start: u64, end: u64) -> Option {
74 | self.update();
75 | Some(start + self.seed % end.checked_sub(start)?)
76 | }
77 |
78 | /// Generates a random `u64` in the range `[start; end[` using an exponential distribution.
79 | // TODO: check start and end, make sure they are valid.
80 | #[inline]
81 | pub fn exp_range(&mut self, start: u64, end: u64) -> Option {
82 | self.update();
83 | let (start, end) = (start as f64, end as f64);
84 | let rand = start + (end - start) * self.seed as f64 / u64::MAX as f64;
85 | let (exp_start, exp_end, exp_rand) = (
86 | (-start / 10.0).exp(),
87 | (-end / 10.0).exp(),
88 | (-rand / 10.0).exp(),
89 | );
90 | let res = start + (end - start) * (exp_rand - exp_end) / (exp_start - exp_end);
91 | Some(res as u64)
92 | }
93 |
94 | /// Generates a random alphanumeric string of length `len`.
95 | pub fn str(&mut self, len: usize) -> String {
96 | (0..len).step_by(8).fold(String::new(), |s, i| {
97 | let size = std::cmp::min(8, len - i);
98 | let random = self.u64();
99 | (0..size).fold(s, |mut t, j| {
100 | let idx = ((random >> j) & 0xff) % Self::ALPHANUM.len() as u64;
101 | let c = Self::ALPHANUM.as_bytes()[idx as usize] as char;
102 | t.push(c);
103 | t
104 | })
105 | })
106 | }
107 |
108 | /// Generates a random vector of bytes of length `len`.
109 | #[allow(clippy::uninit_vec)]
110 | #[inline]
111 | pub fn bytes(&mut self, len: usize) -> Vec {
112 | // let len = if len % 8 != 0 { len + (8 - len % 8) } else { len };
113 | let mut v = Vec::with_capacity(len);
114 | // SAFETY: we can directly set the length, since the allocation is large enough and
115 | // we fill the vector entirely, so no unitialized values will leak.
116 | unsafe { v.set_len(len) };
117 | (0..len).step_by(8).fold(v, |mut v, i| {
118 | let size = std::cmp::min(8, len - i);
119 | let random = self.u64();
120 | v[i..i + size].copy_from_slice(&random.to_le_bytes()[..size]);
121 | v
122 | })
123 | }
124 |
125 | /// Generates a random vector of bytes of length `len`.
126 | #[inline]
127 | pub fn bytes_into_slice(&mut self, slice: &mut [u8], offset: usize, len: usize) {
128 | for i in (offset..offset + len).step_by(8) {
129 | let size = std::cmp::min(8, offset + len - i);
130 | slice[i..i + size].copy_from_slice(&self.u64().to_le_bytes()[..size]);
131 | }
132 | }
133 |
134 | /// Crates an iterator yielding random bytes.
135 | #[inline]
136 | pub fn bytes_iter(&mut self) -> RandomBytesIterator {
137 | RandomBytesIterator {
138 | rand: self.split(),
139 | current: [0u8; 8],
140 | offset: 0,
141 | }
142 | }
143 | }
144 |
145 | /// Iterator yielding random bytes.
146 | #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
147 | pub struct RandomBytesIterator {
148 | /// The iterator's PRNG.
149 | rand: Random,
150 | /// Array from which random bytes are yielded. It contains 8 random bytes and is refilled
151 | /// once all values have been used.
152 | current: [u8; 8],
153 | /// The current offset in the random bytes array.
154 | offset: usize,
155 | }
156 |
157 | impl Iterator for RandomBytesIterator {
158 | type Item = u8;
159 |
160 | #[inline]
161 | fn next(&mut self) -> Option {
162 | if self.offset % 8 == 0 {
163 | self.offset = 0;
164 | self.current = self.rand.u64().to_le_bytes();
165 | }
166 | let b = self.current[self.offset % 8];
167 | self.offset += 1;
168 | Some(b)
169 | }
170 | }
171 |
172 | // -----------------------------------------------------------------------------------------------
173 | // Misc functions
174 |
175 | /// A fast log2 implementation for `usize` equivalent to `(x as f64).log2().ceil()`.
176 | #[inline]
177 | pub fn log2(x: usize) -> usize {
178 | let (orig_x, mut x, mut log) = (x, x, 0);
179 | while x != 0 {
180 | x >>= 1;
181 | log += 1;
182 | }
183 | log - 1 + ((orig_x & (orig_x - 1)) != 0) as usize
184 | }
185 |
186 | /// A fast log2 implementation for `usize` equivalent to `(x as f64).log2().floor()`.
187 | #[inline]
188 | pub fn log2_floor(x: usize) -> usize {
189 | let mut x = x;
190 | let mut log = 0;
191 | while x != 0 {
192 | x >>= 1;
193 | log += 1;
194 | }
195 | log - 1_usize
196 | }
197 |
198 | /// Returns the value of the Counter-timer Physical Count register (CNTPCT_EL0).
199 | #[inline]
200 | pub fn get_phys_counter() -> u64 {
201 | let mut count;
202 | unsafe {
203 | asm!(
204 | "mrs {}, cntpct_el0",
205 | out(reg) count
206 | );
207 | }
208 | count
209 | }
210 |
211 | #[cfg(test)]
212 | mod tests {
213 | use super::*;
214 | use std::collections::HashSet;
215 |
216 | #[test]
217 | fn utils_random_u64() {
218 | let mut rand = Random::new(0xa5a5a5a5a5a5a5);
219 | assert_eq!(
220 | (0..1000)
221 | .map(|_| rand.u64())
222 | .collect::>()
223 | .len(),
224 | 1000
225 | );
226 | }
227 |
228 | #[test]
229 | fn utils_random_range() {
230 | let mut rand = Random::new(0xa5a5a5a5a5a5a5);
231 | assert_eq!(
232 | (0..1000).all(|_| {
233 | let r = rand.u64_range(123, 456).unwrap();
234 | 123 <= r && r < 456
235 | }),
236 | true
237 | );
238 | }
239 |
240 | #[test]
241 | fn utils_random_exp_range() {
242 | let mut rand = Random::new(0xa5a5a5a5a5a5a5);
243 | let mut distribution = [0; 100];
244 | (0..10000000).for_each(|_| {
245 | let r = rand.exp_range(0, distribution.len() as u64).unwrap();
246 | distribution[r as usize] += 1;
247 | });
248 | println!("{:?}", distribution);
249 | }
250 |
251 | #[test]
252 | fn utils_random_strings() {
253 | let mut rand = Random::new(0xa5a5a5a5a5a5a5);
254 | assert_eq!(
255 | (0..1000)
256 | .map(|_| rand.str(100))
257 | .collect::>()
258 | .len(),
259 | 1000
260 | );
261 | }
262 |
263 | #[test]
264 | fn utils_random_bytes_iter() {
265 | let mut rand = Random::new(0xa5a5a5a5a5a5a5);
266 | println!("{:?}", rand.bytes_iter().take(25).collect::>());
267 | println!("{:?}", rand.bytes_iter().take(25).collect::>());
268 | println!("{:?}", rand.bytes_iter().take(25).collect::>());
269 | println!("{:?}", rand.bytes_iter().take(25).collect::>());
270 | println!("{:?}", rand.bytes_iter().take(25).collect::>());
271 | assert_eq!(
272 | (0..1000)
273 | .map(|_| rand.bytes_iter().take(10).collect::>())
274 | .collect::>>()
275 | .len(),
276 | 1000
277 | );
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/src/caches.rs:
--------------------------------------------------------------------------------
1 | //! Handles everything related to ARMv8 cache and TLB maintenance.
2 |
3 | use applevisor as av;
4 |
5 | use crate::core::*;
6 | use crate::error::*;
7 | use crate::memory::*;
8 |
9 | /// Size of a cache maintenance handler.
10 | pub const HANDLER_SIZE: usize = 0x80;
11 | /// Total size available to map the handlers.
12 | pub const HANDLERS_MEM_SIZE: usize = 0x1000;
13 | /// Maximum number of handlers.
14 | pub const HANDLERS_COUNT: usize = HANDLERS_MEM_SIZE / HANDLER_SIZE;
15 | /// Cache maintenant handler location in memory.
16 | pub const HANDLERS_ADDR: u64 = 0xffff_ffff_fffe_0000;
17 | /// Handlers stack size.
18 | pub const STACK_SIZE: usize = 0x1000;
19 | /// Handlers stack address.
20 | pub const STACK_ADDR: u64 = 0xffff_ffff_fffe_1000;
21 |
22 | /// Cache maintenance functions.
23 | ///
24 | /// # Role of Cache Maintenance in the Fuzzer
25 | ///
26 | /// The fuzzer does a lot of modifications on the virtual address space of fuzzed programs at
27 | /// runtime (e.g. adding breakpoints for hooks) and for these changes to be taken into account,
28 | /// we need to flush caches.
29 | ///
30 | /// # Fuzzer Cache Maintenance
31 | ///
32 | /// The hypervisor doesn't allow cache maintenance outside of the [`applevisor::Vcpu`], which
33 | /// forces us to create handlers for this purpose in the Vcpu's address space.
34 | ///
35 | /// These handlers are found at address [`HANDLERS_ADDR`].
36 | ///
37 | /// ```text
38 | /// +---------------+ <--- HANDLERS_ADDR + 0x000
39 | /// | |
40 | /// | IC IVAU |
41 | /// | handler |
42 | /// | |
43 | /// +---------------+ <--- HANDLERS_ADDR + 0x080
44 | /// | |
45 | /// | TLBI VAAE1 |
46 | /// | handler |
47 | /// | |
48 | /// +---------------+ <--- HANDLERS_ADDR + 0x100
49 | /// | |
50 | /// • •
51 | /// • •
52 | /// • •
53 | /// ```
54 | ///
55 | /// In order to keep the number of context-switches between the fuzzer and the hypervisor to a
56 | /// minimum, the handlers are written in such a way that they jump back to where we originally
57 | /// stopped the exectution.
58 | ///
59 | /// ```text
60 | ///
61 | /// +-----------------+
62 | /// | NORMAL |
63 | /// | EXECUTION |<-----------------+
64 | /// +--------+--------+ |
65 | /// EL0 | |
66 | /// -------------------|---------------------------|-----------------------
67 | /// EL1 | |
68 | /// v |
69 | /// +--------+--------+ +--------+--------+
70 | /// | EXCEPTION FROM | | CACHE |
71 | /// | THE GUEST | | MAINTENANCE |
72 | /// +--------+--------+ +--------+--------+
73 | /// | ^
74 | /// FUZZER | |
75 | /// -------------------|---------------------------|-----------------------
76 | /// HYPERVISOR | |
77 | /// v |
78 | /// +--------+--------+ +--------+--------+
79 | /// | EXCEPTION |-------->| SETUP CACHE |
80 | /// | HANDLING | | MAINTENANCE |
81 | /// +-----------------+ +-----------------+
82 | /// ```
83 | ///
84 | /// Before resuming the execution of the Vcpu and entering the cache maintenance handler, we
85 | /// store the address we want to return to, as well as other state registers, on a dedicated stack
86 | /// mapped at address [`STACK_ADDR`]. The handler is entered at EL1, the cache maintenance
87 | /// operation is performed, the original state is loaded and we perform an `eret` instruction to
88 | /// resume the execution at the original exception level.
89 | pub struct Caches;
90 |
91 | impl Caches {
92 | /// Maps and writes the cache maintenance handlers at address [`HANDLERS_ADDR`].
93 | pub fn init(vcpu: &mut av::Vcpu, vma: &mut VirtMemAllocator) -> Result<()> {
94 | // EL1 stack initialization
95 | vma.map_privileged(STACK_ADDR, STACK_SIZE, av::MemPerms::RW)?;
96 | vcpu.set_sys_reg(av::SysReg::SP_EL1, STACK_ADDR)?;
97 | // Executable page containing the cache maintenance handlers.
98 | vma.map_privileged(HANDLERS_ADDR, HANDLERS_MEM_SIZE, av::MemPerms::RX)?;
99 | let handlers = vec![
100 | // IC IVAU - 0xffff_ffff_fffe_0000
101 | String::from(
102 | "msr tpidr_el1, x0
103 | mov x0, 1
104 | msr spsel, x0
105 | ldr x0, [sp, #0x8]
106 | msr spsr_el1, x0
107 | ldr x0, [sp]
108 | msr elr_el1, x0
109 | ic ivau, x0
110 | dsb ish
111 | isb
112 | mrs x0, tpidr_el1
113 | eret",
114 | ),
115 | // TLBI VMALLE1 + IC ALLUIS - 0xffff_ffff_fffe_0080
116 | String::from(
117 | "tlbi vmalle1
118 | dsb ish
119 | ic ialluis
120 | dsb ish
121 | isb
122 | msr tpidr_el1, x0
123 | mov x0, 1
124 | msr spsel, x0
125 | ldr x0, [sp, #0x8]
126 | msr spsr_el1, x0
127 | ldr x0, [sp]
128 | msr elr_el1, x0
129 | mrs x0, tpidr_el1
130 | eret",
131 | ),
132 | // TLBI VAAE1 (on fault) - 0xffff_ffff_fffe_0180
133 | String::from(
134 | "msr tpidr_el1, x0
135 | mov x0, 1
136 | msr spsel, x0
137 | ldr x0, [sp, #0x8]
138 | msr spsr_el1, x0
139 | ldr x0, [sp]
140 | msr elr_el1, x0
141 | mrs x0, far_el1
142 | lsr x0, x0, 12
143 | dsb ishst
144 | tlbi vaae1, x0
145 | dsb ish
146 | isb
147 | mrs x0, tpidr_el1
148 | eret",
149 | ),
150 | // TLBI VAAE1 - 0xffff_ffff_fffe_0200
151 | String::from(
152 | "msr tpidr_el1, x0
153 | mov x0, 1
154 | msr spsel, x0
155 | ldr x0, [sp, #0x8]
156 | msr spsr_el1, x0
157 | ldr x0, [sp]
158 | msr elr_el1, x0
159 | ldr x0, [sp, #0x10]
160 | lsr x0, x0, 12
161 | dsb ishst
162 | tlbi vaae1, x0
163 | dsb ish
164 | mrs x0, tpidr_el1
165 | eret",
166 | ),
167 | ];
168 | // Write all handlers
169 | assert!(handlers.len() < HANDLERS_COUNT);
170 | for (i, asm) in handlers.into_iter().enumerate() {
171 | let handler = KSE.with(|ks| ks.asm(asm, 0).expect("could not assemble"));
172 | assert!(!handler.bytes.is_empty() && handler.bytes.len() < HANDLER_SIZE);
173 | vma.write(HANDLERS_ADDR + (i * HANDLER_SIZE) as u64, &handler.bytes)?;
174 | }
175 | Ok(())
176 | }
177 |
178 | /// Instruction cache invalidation handler.
179 | ///
180 | /// Executes a `IC IVAU` instruction, which invalidates the instruction cache by virtual
181 | /// address to point of unification.
182 | #[inline]
183 | pub fn ic_ivau(vcpu: &mut av::Vcpu, vma: &mut VirtMemAllocator) -> Result<()> {
184 | // Saving the current context so the handler can use it to return from the exception
185 | // and resume the execution at the right address and exception level.
186 | vma.write_qword(STACK_ADDR, vcpu.get_reg(av::Reg::PC)?)?;
187 | vma.write_qword(STACK_ADDR + 8, vcpu.get_reg(av::Reg::CPSR)?)?;
188 | // Sets CPSR so that we switch the exception level to EL1 and mask exceptions.
189 | vcpu.set_reg(av::Reg::CPSR, 0x3c4)?;
190 | // Sets PC to the instruction cache invalidation handler.
191 | vcpu.set_reg(av::Reg::PC, HANDLERS_ADDR)?;
192 | Ok(())
193 | }
194 |
195 | /// Translation Lookaside Buffer invalidation and instruction cache invalidation handler.
196 | ///
197 | /// Executes a `TLBI VMALLE1` instruction to invalidate the whole TLB followed by a `IC ALLUIS`
198 | /// instruction to flush the entire instruction cache.
199 | #[inline]
200 | pub fn tlbi_vmalle1_ic_ialluis(vcpu: &mut av::Vcpu, vma: &mut VirtMemAllocator) -> Result<()> {
201 | // Saving the current context so the handler can use it to return from the exception
202 | // and resume the execution at the right address and exception level.
203 | vma.write_qword(STACK_ADDR, vcpu.get_reg(av::Reg::PC)?)?;
204 | vma.write_qword(STACK_ADDR + 8, vcpu.get_reg(av::Reg::CPSR)?)?;
205 | // Sets CPSR so that we switch the exception level to EL1 and mask exceptions.
206 | vcpu.set_reg(av::Reg::CPSR, 0x3c4)?;
207 | // Sets PC to the TLB invalidation handler.
208 | vcpu.set_reg(av::Reg::PC, HANDLERS_ADDR + HANDLER_SIZE as u64)?;
209 | Ok(())
210 | }
211 |
212 | /// Translation Lookaside Buffer entry invalidation handler used during a data abort.
213 | ///
214 | /// Executes a `TLBI VAAE1` instruction, which invalidates cached copies of translation table
215 | /// entries from TLBs.
216 | #[inline]
217 | pub fn tlbi_vaae1_on_fault(vcpu: &mut av::Vcpu, vma: &mut VirtMemAllocator) -> Result<()> {
218 | // Saving the current context so the handler can use it to return from the exception
219 | // and resume the execution at the right address and exception level.
220 | // We store SPSR_EL1 instead of CPSR, because the data abort that brought us here has
221 | // already changed the original CPSR.
222 | vma.write_qword(STACK_ADDR, vcpu.get_reg(av::Reg::PC)?)?;
223 | vma.write_qword(STACK_ADDR + 8, vcpu.get_sys_reg(av::SysReg::SPSR_EL1)?)?;
224 | // We should be at EL1 since this handler is only called when a data abort occurs, so
225 | // we only set PC to the tlb invalidation handler.
226 | vcpu.set_reg(av::Reg::PC, HANDLERS_ADDR + (HANDLER_SIZE * 2) as u64)?;
227 | Ok(())
228 | }
229 |
230 | /// Translation Lookaside Buffer entry invalidation handler.
231 | ///
232 | /// Executes a `TLBI VAAE1` instruction, which invalidates cached copies of translation table
233 | /// entries from TLBs.
234 | #[inline]
235 | pub fn tlbi_vaae1(vcpu: &mut av::Vcpu, vma: &mut VirtMemAllocator, addr: u64) -> Result<()> {
236 | // Saving the current context so the handler can use it to return from the exception
237 | // and resume the execution at the right address and exception level.
238 | vma.write_qword(STACK_ADDR, vcpu.get_reg(av::Reg::PC)?)?;
239 | vma.write_qword(STACK_ADDR + 8, vcpu.get_reg(av::Reg::CPSR)?)?;
240 | // Stores the address we want to flush on the stack.
241 | vma.write_qword(STACK_ADDR + 0x10, addr)?;
242 | // Sets CPSR so that we switch the exception level to EL1 and mask exceptions.
243 | vcpu.set_reg(av::Reg::CPSR, 0x3c4)?;
244 | // Sets PC to the TLB invalidation handler.
245 | vcpu.set_reg(av::Reg::PC, HANDLERS_ADDR + (HANDLER_SIZE * 3) as u64)?;
246 | Ok(())
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/src/error.rs:
--------------------------------------------------------------------------------
1 | //! Contains all error types that can be returned by this crate.
2 |
3 | use std::alloc;
4 | use std::error;
5 | use std::fmt;
6 | use std::io;
7 |
8 | /// Convenient `Result` type for custom errors.
9 | pub type Result = std::result::Result;
10 |
11 | // -----------------------------------------------------------------------------------------------
12 | // Errors - General
13 | // -----------------------------------------------------------------------------------------------
14 |
15 | /// Main error structure which is just a simple wrapper for all errors that can be returned by the
16 | /// fuzzer.
17 | #[derive(Clone, Debug, Eq, PartialEq)]
18 | pub enum Error {
19 | /// Core-related errors.
20 | Core(CoreError),
21 | /// Crash-related errors.
22 | Crash(CrashError),
23 | /// Exception-related errors.
24 | Exception(ExceptionError),
25 | /// Hook-related errors.
26 | Hook(HookError),
27 | /// Hypervisor-related errors.
28 | Hypervisor(applevisor::HypervisorError),
29 | /// Loader-related errors.
30 | Loader(LoaderError),
31 | /// Memory-related errors.
32 | Memory(MemoryError),
33 | /// Generic user-defined errors.
34 | Generic(String),
35 | }
36 |
37 | impl error::Error for Error {}
38 |
39 | impl fmt::Display for Error {
40 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 | match self {
42 | Error::Core(e) => write!(f, "[Core error] {}", e),
43 | Error::Crash(e) => write!(f, "[Crash error] {}", e),
44 | Error::Exception(e) => write!(f, "[Exception error] {}", e),
45 | Error::Hook(e) => write!(f, "[Hook error] {}", e),
46 | Error::Loader(e) => write!(f, "[Loader error] {}", e),
47 | Error::Memory(e) => write!(f, "[Memory error] {}", e),
48 | Error::Hypervisor(e) => write!(f, "[Hypervisor error] {}", e),
49 | Error::Generic(e) => write!(f, "[Error] {}", e),
50 | }
51 | }
52 | }
53 |
54 | impl From for Error {
55 | fn from(error: CoreError) -> Self {
56 | Error::Core(error)
57 | }
58 | }
59 |
60 | impl From for Error {
61 | fn from(error: CrashError) -> Self {
62 | Error::Crash(error)
63 | }
64 | }
65 |
66 | impl From for Error {
67 | fn from(error: ExceptionError) -> Self {
68 | Error::Exception(error)
69 | }
70 | }
71 |
72 | impl From for Error {
73 | fn from(error: HookError) -> Self {
74 | Error::Hook(error)
75 | }
76 | }
77 |
78 | impl From for Error {
79 | fn from(error: LoaderError) -> Self {
80 | Error::Loader(error)
81 | }
82 | }
83 |
84 | impl From for Error {
85 | fn from(error: MemoryError) -> Self {
86 | Error::Memory(error)
87 | }
88 | }
89 |
90 | impl From for Error {
91 | fn from(error: applevisor::HypervisorError) -> Self {
92 | Error::Hypervisor(error)
93 | }
94 | }
95 |
96 | impl From for Error {
97 | fn from(error: alloc::LayoutError) -> Self {
98 | Error::Memory(MemoryError::LayoutError(error))
99 | }
100 | }
101 |
102 | impl From for Error {
103 | fn from(error: std::fmt::Error) -> Self {
104 | Error::Crash(CrashError::FmtError(error))
105 | }
106 | }
107 |
108 | impl From for Error {
109 | fn from(error: io::Error) -> Self {
110 | Error::Core(CoreError::IoError(format!("{}", error)))
111 | }
112 | }
113 |
114 | // -----------------------------------------------------------------------------------------------
115 | // Errors - Core
116 | // -----------------------------------------------------------------------------------------------
117 |
118 | /// Core-related errors.
119 | #[derive(Clone, Debug, Eq, PartialEq)]
120 | pub enum CoreError {
121 | InvalidConfiguration,
122 | /// The corpus at the specified path is empty.
123 | EmptyCorpus(String),
124 | /// Corpus testcase generated a crash.
125 | CorpusCrash(std::path::PathBuf),
126 | /// The testcase provided is invalid.
127 | InvalidTestcase,
128 | /// An I/O error occured while processing the corpus.
129 | IoError(String),
130 | /// Too many workers are trying to be spawned.
131 | TooManyWorkers(u32),
132 | /// User-defined core error.
133 | Generic(String),
134 | }
135 |
136 | impl error::Error for CoreError {}
137 |
138 | impl fmt::Display for CoreError {
139 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 | match self {
141 | CoreError::InvalidConfiguration => write!(f, "invalid configuration type"),
142 | CoreError::EmptyCorpus(e) => write!(f, "corpus at {} is empty", e),
143 | CoreError::CorpusCrash(p) => {
144 | write!(f, "a corpus element crashed the fuzzer: {}", p.display())
145 | }
146 | CoreError::InvalidTestcase => write!(f, "testcase is invalid"),
147 | CoreError::IoError(e) => write!(f, "{}", e),
148 | CoreError::TooManyWorkers(n) => write!(f, "maximum number of workers reached ({})", n),
149 | CoreError::Generic(e) => write!(f, "{}", e),
150 | }
151 | }
152 | }
153 |
154 | // -----------------------------------------------------------------------------------------------
155 | // Errors - Crash
156 | // -----------------------------------------------------------------------------------------------
157 |
158 | /// Crash-related errors.
159 | #[derive(Clone, Debug, Eq, PartialEq)]
160 | pub enum CrashError {
161 | /// A format error occured.
162 | FmtError(std::fmt::Error),
163 | /// User-defined core error.
164 | Generic(String),
165 | }
166 |
167 | impl error::Error for CrashError {}
168 |
169 | impl fmt::Display for CrashError {
170 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 | match self {
172 | CrashError::FmtError(e) => write!(f, "{}", e),
173 | CrashError::Generic(e) => write!(f, "{}", e),
174 | }
175 | }
176 | }
177 |
178 | // -----------------------------------------------------------------------------------------------
179 | // Errors - Exception
180 | // -----------------------------------------------------------------------------------------------
181 |
182 | /// Exception-related errors.
183 | #[derive(Clone, Debug, Eq, PartialEq)]
184 | pub enum ExceptionError {
185 | /// The exception type is not implemented.
186 | UnimplementedException(u64),
187 | /// User-defined exception error.
188 | Generic(String),
189 | }
190 |
191 | impl error::Error for ExceptionError {}
192 |
193 | impl fmt::Display for ExceptionError {
194 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 | match self {
196 | ExceptionError::UnimplementedException(e) => {
197 | write!(f, "unimplemented exception ({:?})", e)
198 | }
199 | ExceptionError::Generic(e) => write!(f, "{}", e),
200 | }
201 | }
202 | }
203 |
204 | // -----------------------------------------------------------------------------------------------
205 | // Errors - Hook
206 | // -----------------------------------------------------------------------------------------------
207 |
208 | /// Hook-related errors.
209 | #[derive(Clone, Debug, Eq, PartialEq)]
210 | pub enum HookError {
211 | /// The hook already exists at this address.
212 | HookAlreadyExists(u64),
213 | /// The hook type is invalid.
214 | InvalidHookType(u16),
215 | /// There is no hook at the given address.
216 | UnknownHook(u64),
217 | /// User-defined hook error.
218 | Generic(String),
219 | }
220 |
221 | impl error::Error for HookError {}
222 |
223 | impl fmt::Display for HookError {
224 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225 | match self {
226 | HookError::HookAlreadyExists(a) => write!(f, "hook already exists ({:#x})", a),
227 | HookError::InvalidHookType(t) => write!(f, "invalid hook type ({:#x})", t),
228 | HookError::UnknownHook(a) => write!(f, "unknown hook ({:#x})", a),
229 | HookError::Generic(e) => write!(f, "{}", e),
230 | }
231 | }
232 | }
233 |
234 | // -----------------------------------------------------------------------------------------------
235 | // Errors - Loader
236 | // -----------------------------------------------------------------------------------------------
237 |
238 | /// Loader-related errors.
239 | #[derive(Clone, Debug, Eq, PartialEq)]
240 | pub enum LoaderError {
241 | /// The symbol is unknown.
242 | UnknownSymbol(String),
243 | /// User-defined loader error.
244 | Generic(String),
245 | }
246 |
247 | impl error::Error for LoaderError {}
248 |
249 | impl fmt::Display for LoaderError {
250 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251 | match self {
252 | LoaderError::UnknownSymbol(s) => write!(f, "unknown symbol: {}", s),
253 | LoaderError::Generic(e) => write!(f, "{}", e),
254 | }
255 | }
256 | }
257 |
258 | // -----------------------------------------------------------------------------------------------
259 | // Errors - Memory
260 | // -----------------------------------------------------------------------------------------------
261 |
262 | /// Memory-related errors.
263 | #[derive(Clone, Debug, Eq, PartialEq)]
264 | pub enum MemoryError {
265 | /// The address we're trying to map already exists in the page table.
266 | AlreadyMapped(u64),
267 | /// The slab is an unexpected state.
268 | CorruptedSlab,
269 | /// The address is invalid.
270 | InvalidAddress(u64),
271 | /// The index is invalid.
272 | InvalidIndex(usize),
273 | /// The size is invalid.
274 | InvalidSize(usize),
275 | /// Wrapper for `alloc::LayoutError`.
276 | LayoutError(alloc::LayoutError),
277 | /// The allocator is out of memory.
278 | OutOfMemory,
279 | /// The operation between an address and a size resulted in an overflow.
280 | Overflow(u64, usize),
281 | /// The address is not aligned as expected.
282 | UnalignedAddress(u64),
283 | /// The size is not aligned as expected.
284 | UnalignedSize(usize),
285 | /// The address we're trying to access has not been allocated.
286 | UnallocatedMemoryAccess(u64),
287 | /// User-defined memory error.
288 | Generic(String),
289 | }
290 |
291 | impl error::Error for MemoryError {}
292 |
293 | impl fmt::Display for MemoryError {
294 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 | match self {
296 | MemoryError::AlreadyMapped(a) => write!(f, "address is already mapped: {:#x}", a),
297 | MemoryError::CorruptedSlab => write!(f, "corrupted slab"),
298 | MemoryError::InvalidAddress(a) => write!(f, "invalid address: {:#x}", a),
299 | MemoryError::InvalidIndex(i) => write!(f, "invalid index: {:#x}", i),
300 | MemoryError::InvalidSize(s) => write!(f, "invalid size: {:#x}", s),
301 | MemoryError::LayoutError(e) => write!(f, "layout error: {}", e),
302 | MemoryError::OutOfMemory => write!(f, "the allocator ran out of memory"),
303 | MemoryError::Overflow(a, s) => write!(f, "an overflow occured: {:#x}, {:#x}", a, s),
304 | MemoryError::UnalignedAddress(a) => write!(f, "unaligned address: ({:#x})", a),
305 | MemoryError::UnalignedSize(s) => write!(f, "unaligned size: ({:#x})", s),
306 | MemoryError::UnallocatedMemoryAccess(x) => {
307 | write!(f, "access to unallocated memory at address {:#x}", x)
308 | }
309 | MemoryError::Generic(e) => write!(f, "{}", e),
310 | }
311 | }
312 | }
313 |
--------------------------------------------------------------------------------
/src/corpus.rs:
--------------------------------------------------------------------------------
1 | //! Handles the corpus and testcases sent to the fuzzed targets.
2 |
3 | use std::fs::{read_dir, OpenOptions};
4 | use std::io::{Read, Write};
5 | use std::path::{Path, PathBuf};
6 | use std::sync::{Arc, RwLock};
7 | use std::time::Duration;
8 |
9 | use rhexdump as rh;
10 |
11 | use crate::coverage::*;
12 | use crate::error::*;
13 | use crate::utils::*;
14 |
15 | /// Determines which action should be taken after a testcase has been loaded by the fuzzer.
16 | /// Because a given testcase can be reused across multiple iterations, it's possible to tell the
17 | /// fuzzer whether we want to keep it and use the remaining data for the next iteration or discard
18 | /// it and get a new one.
19 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
20 | pub enum LoadTestcaseAction {
21 | /// Once the current testcase is loaded, we discard it and get a new one for the next
22 | /// iteration. Doesn't reset the fuzzer from the snapshots.
23 | New,
24 | /// Once the current testcase is loaded, we discard it and get a new one for the next
25 | /// iteration. Resets the fuzzer using the snapshots.
26 | NewAndReset,
27 | /// If there is still data in the current testcase that can be used for the next iteration,
28 | /// we keep it. Doesn't reset the fuzzer from the snapshots.
29 | Keep,
30 | /// If there is still data in the current testcase that can be used for the next iteration,
31 | /// we keep it. Resets the fuzzer using the snapshots.
32 | KeepAndReset,
33 | /// The testcase is invalid, a new one will be fetched and loaded right away. Doesn't reset
34 | /// the fuzzer from the snapshots.
35 | Invalid,
36 | /// The testcase is invalid, a new one will be fetched and loaded right away. Resets the
37 | /// fuzzer using the snapshots.
38 | InvalidAndReset,
39 | }
40 |
41 | /// Represents the input executed during one iteration of the fuzzer.
42 | #[derive(Clone, Eq, PartialEq, Hash, Debug)]
43 | pub struct Testcase {
44 | /// The path to the testcase on disk.
45 | pub(crate) path: Option,
46 | /// The seed that helped generate the testcase (used for name generation).
47 | pub(crate) seed: Option,
48 | /// The time it took for the testcase to run.
49 | pub(crate) exec_time: Duration,
50 | /// The coverage associated to this testcase.
51 | pub(crate) coverage: Coverage,
52 | /// The testcase's content.
53 | pub(crate) data: Vec,
54 | }
55 |
56 | impl Testcase {
57 | /// Creates a new testcase from a slice.
58 | pub fn new(seed: u64, data: &[u8]) -> Self {
59 | Self {
60 | path: None,
61 | seed: Some(seed),
62 | exec_time: Duration::new(0, 0),
63 | coverage: Coverage::new(),
64 | data: data.to_vec(),
65 | }
66 | }
67 |
68 | /// Loads a testcase from the file located at `filepath`.
69 | pub fn from_file(filepath: impl AsRef) -> Result {
70 | let mut testcase = OpenOptions::new().read(true).open(&filepath)?;
71 | let mut data = vec![];
72 | testcase.read_to_end(&mut data)?;
73 | Ok(Self {
74 | path: Some(filepath.as_ref().to_owned()),
75 | seed: None,
76 | exec_time: Duration::new(0, 0),
77 | coverage: Coverage::new(),
78 | data,
79 | })
80 | }
81 |
82 | /// Writes a testcase into the `dir` directory.
83 | pub fn to_file(&self, dir: impl AsRef) -> Result<()> {
84 | let filepath = self.filepath(dir);
85 | let mut testcase = OpenOptions::new().create(true).write(true).open(filepath)?;
86 | testcase.write_all(&self.data)?;
87 | Ok(())
88 | }
89 |
90 | /// Returns the testcase's size.
91 | pub fn len(&self) -> usize {
92 | self.data.len()
93 | }
94 |
95 | /// Returns if the testcase is empty.
96 | pub fn is_empty(&self) -> bool {
97 | self.data.len() == 0
98 | }
99 |
100 | /// Returns an immutable reference to the testcase data.
101 | pub fn get_data(&self) -> &[u8] {
102 | &self.data
103 | }
104 |
105 | /// Returns a mutable reference to the testcase data.
106 | pub fn get_data_mut(&mut self) -> &mut Vec {
107 | &mut self.data
108 | }
109 |
110 | /// Sets the seed used to generate the testcase.
111 | pub fn set_seed(&mut self, seed: u64) {
112 | self.seed = Some(seed);
113 | }
114 |
115 | /// Generates a random testcase filepath in the corpus directory.
116 | fn filepath(&self, dir: impl AsRef) -> PathBuf {
117 | let fmt =
118 | time::format_description::parse("[year][month][day]-[hour][minute][second]").unwrap();
119 | // We can unwrap safely here, because testcases without a seed are those that were loaded
120 | // from the corpus, so they shouldn't be written back without having their seed set after
121 | // having been mutated.
122 | let seed = self.seed.unwrap();
123 | dir.as_ref().join(PathBuf::from(format!(
124 | "testcase_{}_{:x}",
125 | time::OffsetDateTime::now_utc().format(&fmt).unwrap(),
126 | seed,
127 | )))
128 | }
129 | }
130 |
131 | impl std::default::Default for Testcase {
132 | fn default() -> Self {
133 | Testcase::new(0, &[])
134 | }
135 | }
136 |
137 | impl std::fmt::Display for Testcase {
138 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 | write!(f, "{}", rh::hexdump(&self.data))
140 | }
141 | }
142 |
143 | /// The non-shareable inner structure of [`Corpus`] that contains the testcases.
144 | #[derive(Clone, Eq, PartialEq, Hash, Debug)]
145 | pub struct CorpusInner {
146 | /// Path to the inputs directory.
147 | pub(crate) path: PathBuf,
148 | /// Vector containing tuples of [`Testcase`]s and the number of use of each testcase.
149 | /// This vector is sorted by number of uses, from the most used testcase to the least used one.
150 | pub(crate) testcases: Vec<(usize, Testcase)>,
151 | /// The corpus random generator used to choose the next testcase.
152 | pub(crate) rand: Random,
153 | }
154 |
155 | impl CorpusInner {
156 | /// Creates a new inner corpus structure.
157 | fn new(rand: Random, path: PathBuf) -> Result {
158 | Ok(Self {
159 | path,
160 | testcases: vec![],
161 | rand,
162 | })
163 | }
164 | }
165 |
166 | /// The corpus containing testcases shared between all fuzzing workers.
167 | ///
168 | /// # Role of the Corpus in the Fuzzer.
169 | ///
170 | /// Our mutation-based fuzzer needs an initial set of testcases to start running. These testcases
171 | /// are stored in the corpus and can be loaded from a directory using [`Corpus::load_from_dir`].
172 | ///
173 | /// The fuzzer currently does not implement corpus minimization, a process that removes as many
174 | /// testcases as possible without reducing coverage. While implementing such a system would
175 | /// distill the current corpus to its essence, we would effectively lose out on information that
176 | /// might have been useful for later iterations (e.g. a testcase that sets up an internal state
177 | /// that would trigger a bug after being mutated for a few times). Instead, this fuzzer keeps all
178 | /// testcases, but favors the least used ones when picking the next testcase using
179 | /// [`Corpus::get_testcase`].
180 | #[derive(Clone, Debug)]
181 | pub struct Corpus {
182 | pub(crate) inner: Arc>,
183 | }
184 |
185 | impl Corpus {
186 | /// Creates a new shared corpus.
187 | pub fn new(
188 | rand: Random,
189 | corpus_path: impl AsRef,
190 | work_dir: impl AsRef,
191 | load_corpus: bool,
192 | ) -> Result {
193 | let mut inputs_path = work_dir.as_ref().to_owned();
194 | inputs_path.push("inputs");
195 | // Creates the directory containing the inputs queued for mutation.
196 | std::fs::create_dir_all(&inputs_path)?;
197 | // Copies the file from the corpus directory into the inputs directory if corpus loading is
198 | // enabled.
199 | if load_corpus && corpus_path.as_ref().exists() {
200 | for corpus_entry in read_dir(&corpus_path)? {
201 | let corpus_entry = corpus_entry?;
202 | let corpus_entry_path = corpus_entry.path();
203 | // Ignores subdirectories.
204 | if !corpus_entry_path.is_dir() {
205 | let mut inputs_entry_path = inputs_path.clone();
206 | inputs_entry_path.push(corpus_entry.file_name());
207 | std::fs::copy(corpus_entry_path, inputs_entry_path)?;
208 | }
209 | }
210 | }
211 | Ok(Self {
212 | inner: Arc::new(RwLock::new(CorpusInner::new(rand, inputs_path)?)),
213 | })
214 | }
215 |
216 | /// Loads all testcases stored in the `path` directory.
217 | pub fn load_from_dir(&mut self, max_size: usize) -> Result<()> {
218 | let mut inner = self.inner.write().unwrap();
219 | // Iterates over each entry in `path`.
220 | for entry in read_dir(&inner.path)? {
221 | let entry = entry?;
222 | let path = entry.path();
223 | // Ignores subdirectories.
224 | if !path.is_dir() {
225 | let testcase = Testcase::from_file(path)?;
226 | // Testcases that are too big are ignored.
227 | // TODO: maybe add a config option to decide between ignoring the testcase,
228 | // truncating it, or raising an error.
229 | if testcase.len() > max_size {
230 | continue;
231 | }
232 | inner.testcases.push((0, testcase));
233 | }
234 | }
235 | Ok(())
236 | }
237 |
238 | /// Adds a testcase to the shared corpus.
239 | pub fn add_testcase(&mut self, testcase: Testcase) -> Result<()> {
240 | let mut inner = self.inner.write().unwrap();
241 | // We write the testcase into the corpus directory.
242 | testcase.to_file(&inner.path)?;
243 | // When we push this testcase at the end we don't need to sort the array, because a
244 | // new testcase is guarenteed to be the least used one.
245 | inner.testcases.push((0, testcase));
246 | Ok(())
247 | }
248 |
249 | /// Gets one testcase from the shared corpus (the least used are more likely to be selected
250 | /// next).
251 | pub fn get_testcase(&mut self) -> Testcase {
252 | let mut inner = self.inner.write().unwrap();
253 | let corpus_len = inner.testcases.len() as u64;
254 | if corpus_len == 0 {
255 | return Testcase::default();
256 | }
257 | // Generates a random index in the corpus using an exponential distribution.
258 | // to select, on average, less used testcases that are the least used (those towards the
259 | // end of the vector)
260 | let idx = corpus_len - 1 - inner.rand.exp_range(0, corpus_len).unwrap();
261 | let testcase = if let Some((count, testcase)) = inner.testcases.get_mut(idx as usize) {
262 | *count += 1;
263 | testcase.clone()
264 | } else {
265 | // We've checked earlier that the corpus has at least one testcase.
266 | unreachable!();
267 | };
268 | // Sorts the vector by number of testcases, from the most used one to the least used one.
269 | inner.testcases.sort_unstable_by(|a, b| b.0.cmp(&a.0));
270 | // Returns the testcase was extracted.
271 | testcase
272 | }
273 |
274 | /// Returns the numbers of testcases in the corpus.
275 | pub fn nb_entries(&self) -> usize {
276 | self.inner.read().unwrap().testcases.len()
277 | }
278 | }
279 |
--------------------------------------------------------------------------------