├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── autarkie
├── Cargo.toml
└── src
│ ├── fuzzer
│ ├── afl.rs
│ ├── context.rs
│ ├── feedback
│ │ ├── mod.rs
│ │ └── register.rs
│ ├── libfuzzer.rs
│ ├── mod.rs
│ ├── mutators
│ │ ├── commons.rs
│ │ ├── mod.rs
│ │ ├── recurse_mutate.rs
│ │ ├── splice.rs
│ │ └── splice_append.rs
│ └── stages
│ │ ├── binary_mutator.rs
│ │ ├── cmp.rs
│ │ ├── generate.rs
│ │ ├── libfuzzer_cmp.rs
│ │ ├── minimization.rs
│ │ ├── mod.rs
│ │ ├── mutating.rs
│ │ ├── mutational.rs
│ │ ├── novelty_minimization.rs
│ │ ├── recursive_minimization.rs
│ │ └── stats.rs
│ ├── graph.rs
│ ├── lib.rs
│ ├── scale.rs
│ ├── serde.rs
│ ├── tree.rs
│ └── visitor.rs
├── autarkie_derive
├── Cargo.toml
└── src
│ ├── lib.rs
│ ├── trait_bounds.rs
│ └── utils.rs
├── autarkie_test
├── Cargo.toml
└── src
│ └── lib.rs
├── guides
├── rbpf.md
└── sql.md
├── libafl_libfuzzer
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── build.rs
├── runtime
│ ├── Cargo.toml.template
│ ├── build.rs
│ └── src
│ │ ├── fuzz.rs
│ │ ├── harness_wrap.cpp
│ │ ├── harness_wrap.h
│ │ └── lib.rs
└── src
│ └── lib.rs
└── libafl_libfuzzer_runtime
├── Cargo.lock
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── build.rs
├── build.sh
└── src
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | *.pdf
3 | *.aux
4 | *.run.xml
5 | *.toc
6 | *.out
7 | *.lot
8 | *.blg
9 | *.aux
10 | *.lof
11 | kitchensink
12 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | edition = "2021"
3 |
4 | members = [
5 | "autarkie",
6 | "autarkie_derive",
7 | "autarkie_test",
8 | "libafl_libfuzzer",
9 | "libafl_libfuzzer_runtime",
10 | ]
11 |
12 | [workspace.dependencies]
13 | libafl = { git = "https://github.com/AFLplusplus/LibAFL", rev = "dd0bcba103d076a15ee5231007f909980261cffc" }
14 | libafl_bolts = { git = "https://github.com/AFLplusplus/LibAFL", rev = "dd0bcba103d076a15ee5231007f909980261cffc"}
15 | libafl_targets = { git = "https://github.com/AFLplusplus/LibAFL", rev = "dd0bcba103d076a15ee5231007f909980261cffc", default-features = false }
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Autarkie - Instant Grammar Fuzzing Using Rust Macros
2 | Autarkie is a native grammar fuzzer built in Rust. Using procedural macros, it (almost completely) automatically creates a grammar fuzzer.
3 | Autarkie is heavily inspired by [nautilus](https://github.com/nautilus-fuzz/nautilus).
4 |
5 | # Features
6 | - Essentially a drop-in replacement for [arbitrary](https://github.com/rust-fuzz/arbitrary)
7 | - Actual grammar fuzzing - not "structure aware"
8 | - Supports both AFL++ (Forkserver) and cargo-fuzz (Inprocess).
9 | - As long as the grammar is defined using Rust, you can fuzz C/C++ too (using AFL++ forkserver)
10 | - Really easy to use, complexity is abstracted for you.
11 | - Trivial to integrate with other fuzzers.
12 |
13 | # Niche features
14 | Autarkie has several features that other grammar fuzzers do not have:
15 | - No grammar maintenance; because the grammar is part of the code, if the project is updated, the grammar updates too.
16 | - Grammar is completely exhaustive; the compiler will make sure that every necessary type is included. No more guesswork.
17 | - Corpus is re-usable. If you stop the fuzzer, you can re-start it and it will be able to re-use the corpus!
18 | - Can learn from other fuzzers! (TODO: almost implemented)
19 | - Has native [cmplog](https://www.ndss-symposium.org/ndss-paper/redqueen-fuzzing-with-input-to-state-correspondence/) support (TODO: almost implemented)
20 |
21 | # How to Use
22 | There are two main walkthroughs:
23 | 1. Fuzz AFL++ instrumented C/C++ project
24 |
25 | This example fuzzes ``sqlite3`` by using grammar defined in [datafusion-sqlparser-rs](https://github.com/apache/datafusion-sqlparser-rs).
26 | Personal favourite as it shows Autarkie's magic: you can build a highly sophisticated grammar fuzzer covering a language as complex as SQL in under 5 minutes.
27 | This example also shows how you can render the internal structure into a different format for the harness
28 |
29 | [Go to the walkthrough](guides/sql.md)
30 |
31 |
32 | 2. Fuzz a Rust project using cargo-fuzz
33 |
34 | This example fuzzes Solana's ``sbpf`` interpreter which is implemented in Rust. Autarkie has ``cargo-fuzz`` integration, so it is trivial to fuzz native Rust projects.
35 |
36 | [Go to the walkthrough](guides/rbpf.md)
37 |
38 |
39 | # Limitations and Caveats
40 | ### Beta
41 | Autarkie is in beta - expect issues, do not tread lightly.
42 |
43 | ### Static Lifetimes
44 | The type MUST own all it's data; it cannot use lifetimes. This is due to the use of ``std::intrinsics::type_id`` which require types to have a ``'static`` lifetime.
45 |
46 | Note: that you can simply write a wrapper type that owns all the data and converts it to the native type
47 | ### Nightly only
48 | Limited to ``nightly`` due to the usage of the ``#![feature(compiler_intrinsics)]`` feature.
49 |
50 | # Contributions
51 | Contributions, questions and feedback welcome.
52 | Please engage!
53 |
--------------------------------------------------------------------------------
/autarkie/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "autarkie"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | # different encodings
8 | bincode = { version = "1.3.3", optional = true}
9 | serde = { version = "1.0.0", features = ["derive"] }
10 |
11 | autarkie_derive = {path = "../autarkie_derive", optional = true}
12 | parity-scale-codec = { version = "3.6.12", features = ["derive"], optional = true }
13 | borsh = { version = "1.5.3", features = ["derive"], optional = true }
14 |
15 | libafl = {workspace = true}
16 | libafl_bolts = {workspace = true}
17 | libafl_targets = {workspace = true}
18 | blake3 = "1.5.5"
19 | colored = "3.0.0"
20 | petgraph = "0.7.1"
21 | clap = { version = "4.5.20", features = ["derive"] }
22 |
23 | regex = "1.11.1"
24 | num-traits = "0.2.19"
25 | serde_json = "1.0.140"
26 |
27 | [features]
28 | derive = ["autarkie_derive"]
29 | bincode = ["dep:bincode"]
30 | scale = ["dep:parity-scale-codec"]
31 | borsh = ["dep:borsh"]
32 | introspection = ["libafl/introspection"]
33 | libfuzzer = []
34 | afl = []
35 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/afl.rs:
--------------------------------------------------------------------------------
1 | #[macro_export]
2 | macro_rules! impl_converter {
3 | ($t:ty) => {
4 | #[derive(Clone)]
5 | pub struct FuzzDataTargetBytesConverter;
6 |
7 | impl FuzzDataTargetBytesConverter {
8 | pub fn new() -> Self {
9 | Self {}
10 | }
11 | }
12 |
13 | impl autarkie::TargetBytesConverter<$t> for FuzzDataTargetBytesConverter {
14 | fn to_target_bytes<'a>(&mut self, input: &'a $t) -> autarkie::OwnedSlice<'a, u8> {
15 | let bytes = autarkie::serialize(input);
16 | let bytes = if bytes.len() == 0 {
17 | vec![0, 0, 0, 0]
18 | } else {
19 | bytes
20 | };
21 | autarkie::OwnedSlice::from(bytes)
22 | }
23 | }
24 | };
25 | // We may want to render to bytes manually (eg: to_string) so we offer the possibility of a closure too.
26 | ($t:ty, $closure:expr) => {
27 | #[derive(Clone)]
28 | pub struct FuzzDataTargetBytesConverter;
29 |
30 | impl FuzzDataTargetBytesConverter {
31 | pub fn new() -> Self {
32 | Self
33 | }
34 | }
35 |
36 | impl autarkie::TargetBytesConverter<$t> for FuzzDataTargetBytesConverter {
37 | fn to_target_bytes<'a>(&mut self, input: &'a $t) -> autarkie::OwnedSlice<'a, u8> {
38 | let bytes = $closure(input);
39 | let bytes = if bytes.len() == 0 {
40 | vec![0, 0, 0, 0]
41 | } else {
42 | bytes
43 | };
44 | autarkie::OwnedSlice::from(bytes)
45 | }
46 | }
47 | };
48 | }
49 |
50 | #[macro_export]
51 | macro_rules! impl_input {
52 | ($t:ty) => {
53 | impl autarkie::Input for $t {
54 | fn to_file
(&self, path: P) -> Result<(), autarkie::LibAFLError>
55 | where
56 | P: AsRef,
57 | {
58 | let bytes = autarkie::serialize(self);
59 | std::fs::write(path, bytes)?;
60 | Ok(())
61 | }
62 |
63 | // TODO: don't serialize here
64 | fn generate_name(&self, id: Option) -> String {
65 | let bytes = autarkie::serialize(self);
66 | std::format!("{}", autarkie::hash(bytes.as_slice()))
67 | }
68 |
69 | fn from_file(path: P) -> Result
70 | where
71 | P: AsRef,
72 | {
73 | let data = std::fs::read(path)?;
74 | let res = autarkie::deserialize::<$t>(&mut data.as_slice());
75 | Ok(res)
76 | }
77 | }
78 | };
79 | }
80 |
81 | #[macro_export]
82 | macro_rules! fuzz_afl_inner {
83 | ($t: ty) => {
84 | fn main() {
85 | let harness: Option autarkie::LibAFLExitKind> = None;
86 | $crate::fuzzer::run_fuzzer(FuzzDataTargetBytesConverter::new(), harness);
87 | }
88 | };
89 | }
90 |
91 | #[macro_export]
92 | macro_rules! fuzz_afl {
93 | ($t:ty) => {
94 | $crate::impl_input!($t);
95 | $crate::impl_converter!($t);
96 | $crate::fuzz_afl_inner!($t);
97 | $crate::impl_hash!($t);
98 | };
99 | ($t:ty, $closure:expr) => {
100 | $crate::impl_input!($t);
101 | $crate::impl_converter!($t, $closure);
102 | $crate::fuzz_afl_inner!($t);
103 | $crate::impl_hash!($t);
104 | };
105 | }
106 |
107 | #[macro_export]
108 | macro_rules! impl_hash {
109 | ($t:ty) => {
110 | impl std::hash::Hash for $t {
111 | fn hash(&self, state: &mut H) {
112 | autarkie::serialize(&self).hash(state)
113 | }
114 | }
115 | };
116 | }
117 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/context.rs:
--------------------------------------------------------------------------------
1 | use crate::{FieldLocation, Id, Node, Visitor};
2 | use libafl::{corpus::CorpusId, SerdeAny};
3 | use libafl_bolts::current_time;
4 | use serde::{Deserialize, Serialize};
5 | use std::{
6 | collections::{HashMap, HashSet, VecDeque},
7 | io::ErrorKind,
8 | path::{Path, PathBuf},
9 | time::Duration,
10 | u128,
11 | };
12 |
13 | #[derive(Debug, Clone, Serialize, Deserialize)]
14 | pub enum InputCause {
15 | Default,
16 | Generated,
17 | }
18 | #[derive(Debug, Clone, SerdeAny, Serialize, Deserialize)]
19 | pub struct Context {
20 | mutations: HashSet,
21 | out_dir: PathBuf,
22 | type_input_map: HashMap>,
23 | input_cause: InputCause,
24 | }
25 |
26 | // TODO: chunk & cmp reloading
27 | impl Context {
28 | pub fn register_input(&mut self, input: &I, visitor: &mut Visitor)
29 | where
30 | I: Node,
31 | {
32 | let generated_fields = match &self.input_cause {
33 | InputCause::Default => visitor.serialized(),
34 | InputCause::Generated => {
35 | input.__autarkie_serialized(visitor);
36 | visitor.serialized()
37 | }
38 | };
39 | for field in generated_fields {
40 | let (data, ty) = field;
41 | // todo: optimize this
42 | let path = self.out_dir.join("chunks").join(ty.to_string());
43 | match std::fs::create_dir(&path) {
44 | Ok(_) => {}
45 | Err(e) => {
46 | if !matches!(e.kind(), ErrorKind::AlreadyExists) {
47 | panic!("{:?}", e)
48 | }
49 | }
50 | };
51 | let hash = blake3::hash(&data);
52 | let path = path.join(hash.to_string());
53 | if !std::fs::exists(&path).unwrap() {
54 | std::fs::write(&path, data).unwrap();
55 | if let Some(e) = self.type_input_map.get_mut(&ty) {
56 | e.push(path);
57 | } else {
58 | self.type_input_map.insert(ty, vec![path]);
59 | }
60 | }
61 | }
62 | self.input_cause = InputCause::Default;
63 | }
64 |
65 | pub fn generated_input(&mut self) {
66 | self.input_cause = InputCause::Generated;
67 | }
68 |
69 | pub fn default_input(&mut self) {
70 | self.input_cause = InputCause::Default;
71 | }
72 |
73 | pub fn add_existing_chunk(&mut self, path: PathBuf) {
74 | let ty = path
75 | .parent()
76 | .unwrap()
77 | .file_name()
78 | .unwrap()
79 | .to_str()
80 | .unwrap()
81 | .parse::()
82 | .expect("corrupt chunk ID!");
83 | if let Some(e) = self.type_input_map.get_mut(&ty) {
84 | e.push(path);
85 | } else {
86 | self.type_input_map.insert(ty, vec![path]);
87 | }
88 | }
89 |
90 | pub fn get_inputs_for_type(&self, t: &Id) -> Option<&Vec> {
91 | self.type_input_map.get(t)
92 | }
93 | }
94 |
95 | impl Context {
96 | pub fn new(out_dir: PathBuf) -> Self {
97 | let type_input_map = HashMap::default();
98 | Self {
99 | mutations: HashSet::new(),
100 | input_cause: InputCause::Default,
101 | out_dir,
102 | type_input_map,
103 | }
104 | }
105 |
106 | pub fn add_mutation(&mut self, m: MutationMetadata) {
107 | self.mutations.insert(m);
108 | }
109 |
110 | pub fn clear_mutations(&mut self) -> HashSet {
111 | let cloned = self.mutations.clone();
112 | self.mutations = HashSet::new();
113 | cloned
114 | }
115 | }
116 |
117 | /// Track why a testcase was added to the corpus.
118 | #[derive(
119 | Debug,
120 | Clone,
121 | serde::Serialize,
122 | serde::Deserialize,
123 | SerdeAny,
124 | PartialEq,
125 | Eq,
126 | Hash,
127 | PartialOrd,
128 | Ord,
129 | )]
130 | pub enum MutationMetadata {
131 | /// Splice Full Iterable
132 | SpliceFull,
133 | /// Splice Single Node (never an iterable)
134 | SpliceSingle,
135 | /// Splice Partial Iterable
136 | SpliceSubSplice,
137 | /// Splice Append
138 | SpliceAppend,
139 | /// Splice Single Node (never an iterable)
140 | RecurseMutateSingle,
141 | /// Random Generate Partial Iterable
142 | RecurseMutateSubsplice,
143 | /// RecursiveMinimization
144 | RecursiveMinimization,
145 | /// Iterable Minimization
146 | IterableMinimization,
147 | /// Novelty Minimization
148 | NoveltyMinimization,
149 | /// Afl
150 | Afl,
151 | /// Generate
152 | Generate,
153 | /// Cmplog
154 | Cmplog,
155 | /// I2S
156 | I2S,
157 | }
158 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/feedback/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod register;
2 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/feedback/register.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | borrow::Cow,
3 | cell::RefCell,
4 | collections::{BTreeMap, HashSet},
5 | marker::PhantomData,
6 | rc::Rc,
7 | };
8 |
9 | use libafl::{
10 | corpus::{Corpus, Testcase},
11 | executors::ExitKind,
12 | feedbacks::{Feedback, StateInitializer},
13 | state::{HasCorpus, HasCurrentTestcase},
14 | Error, HasMetadata,
15 | };
16 |
17 | use crate::{
18 | fuzzer::{context::MutationMetadata, stages::stats::AutarkieStats},
19 | Node, Visitor,
20 | };
21 | use libafl_bolts::Named;
22 |
23 | use crate::fuzzer::Context;
24 |
25 | pub struct RegisterFeedback {
26 | visitor: Rc>,
27 | phantom: PhantomData,
28 | }
29 |
30 | impl RegisterFeedback {
31 | pub fn new(visitor: Rc>) -> Self {
32 | Self {
33 | visitor,
34 | phantom: PhantomData,
35 | }
36 | }
37 | }
38 |
39 | impl Feedback for RegisterFeedback
40 | where
41 | I: Node,
42 | S: HasCurrentTestcase + HasCorpus + HasMetadata,
43 | {
44 | fn is_interesting(
45 | &mut self,
46 | _state: &mut S,
47 | _manager: &mut EM,
48 | _input: &I,
49 | _observers: &OT,
50 | _exit_kind: &ExitKind,
51 | ) -> Result {
52 | Ok(false)
53 | }
54 |
55 | fn append_metadata(
56 | &mut self,
57 | state: &mut S,
58 | _manager: &mut EM,
59 | _observers: &OT,
60 | testcase: &mut Testcase,
61 | ) -> Result<(), Error> {
62 | let metadata = state
63 | .metadata_mut::()
64 | .expect("we must have context!");
65 | metadata.register_input(
66 | testcase.input().as_ref().expect("we must have input!"),
67 | &mut self.visitor.borrow_mut(),
68 | );
69 | let done_mutations = metadata.clear_mutations();
70 | let metadata = state
71 | .metadata_mut::()
72 | .unwrap()
73 | .add_new_input_mutations(done_mutations);
74 | Ok(())
75 | }
76 | }
77 |
78 | impl StateInitializer for RegisterFeedback {}
79 |
80 | impl Named for RegisterFeedback {
81 | fn name(&self) -> &std::borrow::Cow<'static, str> {
82 | &Cow::Borrowed("RegisterFeedback")
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/libfuzzer.rs:
--------------------------------------------------------------------------------
1 | #[macro_export]
2 | macro_rules! fuzz_libfuzzer {
3 | ($t:ty) => {
4 | $crate::impl_input!($t);
5 | $crate::impl_converter!($t);
6 | $crate::impl_hash!($t);
7 | };
8 | ($t:ty, $closure:expr) => {
9 | $crate::impl_input!($t);
10 | $crate::impl_converter!($t, $closure);
11 | $crate::impl_hash!($t);
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/mod.rs:
--------------------------------------------------------------------------------
1 | #![allow(warnings)]
2 | #![feature(core_intrinsics)]
3 |
4 | pub mod afl;
5 | mod context;
6 | mod feedback;
7 | pub mod libfuzzer;
8 | mod mutators;
9 | mod stages;
10 |
11 | use crate::{DepthInfo, Node, Visitor};
12 | use clap::Parser;
13 | use context::{Context, MutationMetadata};
14 | use feedback::register::RegisterFeedback;
15 | #[cfg(feature = "libfuzzer")]
16 | use libafl::mutators::I2SRandReplace;
17 | use libafl::{
18 | corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus},
19 | events::{ClientDescription, EventConfig, Launcher, SimpleEventManager},
20 | executors::{
21 | ExitKind, ForkserverExecutor, InProcessExecutor, InProcessForkExecutor, ShadowExecutor,
22 | },
23 | feedback_or, feedback_or_fast,
24 | feedbacks::{
25 | CrashFeedback, MaxMapFeedback, MaxMapOneOrFilledFeedback, MaxMapPow2Feedback, TimeFeedback,
26 | TimeoutFeedback,
27 | },
28 | inputs::{BytesInput, HasTargetBytes, Input, TargetBytesConverter},
29 | monitors::{MultiMonitor, SimpleMonitor},
30 | mutators::{
31 | havoc_mutations, havoc_mutations_no_crossover, tokens_mutations, HavocScheduledMutator,
32 | },
33 | observers::{CanTrack, HitcountsMapObserver, StdMapObserver, TimeObserver},
34 | schedulers::{powersched::PowerSchedule, QueueScheduler, StdWeightedScheduler},
35 | stages::{IfStage, StdMutationalStage, StdPowerMutationalStage},
36 | state::{HasCorpus, HasCurrentTestcase, StdState},
37 | BloomInputFilter, Evaluator, Fuzzer, HasMetadata, StdFuzzer,
38 | };
39 | pub use libafl_bolts::current_nanos;
40 | use libafl_bolts::tuples::Merge;
41 | use libafl_bolts::AsSlice;
42 | use libafl_bolts::TargetArgs;
43 | use libafl_bolts::{
44 | core_affinity::{CoreId, Cores},
45 | fs::get_unique_std_input_file,
46 | ownedref::OwnedRefMut,
47 | rands::{RomuDuoJrRand, StdRand},
48 | shmem::{ShMem, ShMemProvider, StdShMemProvider, UnixShMemProvider},
49 | tuples::{tuple_list, Handled},
50 | AsSliceMut, Error,
51 | };
52 | #[cfg(feature = "libfuzzer")]
53 | use libafl::stages::ShadowTracingStage;
54 | #[cfg(feature = "libfuzzer")]
55 | use libafl_targets::{extra_counters, CmpLogObserver};
56 | #[cfg(feature = "afl")]
57 | use libafl_targets::{AFLppCmpLogMap, AFLppCmpLogObserver};
58 | use mutators::{
59 | recurse_mutate::{AutarkieRecurseMutator, RECURSE_STACK},
60 | splice::{AutarkieSpliceMutator, SPLICE_STACK},
61 | splice_append::{AutarkieSpliceAppendMutator, SPLICE_APPEND_STACK},
62 | };
63 | use regex::Regex;
64 | use stages::{
65 | binary_mutator::AutarkieBinaryMutatorStage,
66 | generate::GenerateStage,
67 | minimization::MinimizationStage,
68 | mutating::MutatingStageWrapper,
69 | mutational::AutarkieMutationalStage,
70 | novelty_minimization::NoveltyMinimizationStage,
71 | recursive_minimization::RecursiveMinimizationStage,
72 | stats::{AutarkieStats, StatsStage},
73 | };
74 |
75 | #[cfg(feature = "afl")]
76 | use stages::cmp::CmpLogStage;
77 | use std::io::{stderr, stdout, Write};
78 | use std::os::fd::AsRawFd;
79 | use std::str::FromStr;
80 | use std::{cell::RefCell, io::ErrorKind, path::PathBuf, process::Command, rc::Rc, time::Duration};
81 | use std::{env::args, ffi::c_int};
82 |
83 | use stages::generate;
84 |
85 | #[cfg(feature = "afl")]
86 | const SHMEM_ENV_VAR: &str = "__AFL_SHM_ID";
87 |
88 | #[cfg(any(feature = "libfuzzer", feature = "afl"))]
89 | pub fn run_fuzzer(bytes_converter: TC, harness: Option)
90 | where
91 | I: Node + Input,
92 | TC: TargetBytesConverter + Clone,
93 | F: Fn(&I) -> ExitKind,
94 | {
95 | #[cfg(feature = "afl")]
96 | let monitor = MultiMonitor::new(|s| println!("{s}"));
97 | // TODO: -close_fd_mask from libfuzzer
98 | #[cfg(feature = "libfuzzer")]
99 | let monitor = MultiMonitor::new(create_monitor_closure());
100 | let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
101 | #[cfg(feature = "afl")]
102 | let opt = Opt::parse();
103 | #[cfg(feature = "libfuzzer")]
104 | let opt = {
105 | let mut opt = args().collect::>();
106 | opt.remove(1);
107 | opt.remove(opt.len() - 1);
108 | Opt::parse_from(opt)
109 | };
110 |
111 | let run_client = |mut state: Option<_>,
112 | mut mgr: _,
113 | core: ClientDescription|
114 | -> Result<(), libafl_bolts::Error> {
115 | if !opt.output_dir.exists() {
116 | std::fs::create_dir(&opt.output_dir).unwrap();
117 | }
118 | #[cfg(feature = "afl")]
119 | let map_size = {
120 | let map_size = Command::new(opt.executable.clone())
121 | .env("AFL_DUMP_MAP_SIZE", "1")
122 | .output()
123 | .expect("target gave no output");
124 | let map_size = String::from_utf8(map_size.stdout)
125 | .expect("target returned illegal mapsize")
126 | .replace("\n", "");
127 | map_size.parse::().expect("illegal mapsize output") + opt.map_bias
128 | };
129 |
130 | let fuzzer_dir = opt.output_dir.join(format!("{}", core.core_id().0));
131 | match std::fs::create_dir(&fuzzer_dir) {
132 | Ok(_) => {}
133 | Err(e) => {
134 | if !matches!(e.kind(), ErrorKind::AlreadyExists) {
135 | panic!("{:?}", e)
136 | }
137 | }
138 | };
139 | #[cfg(feature = "libfuzzer")]
140 | let cmplog_observer = CmpLogObserver::new("cmplog", true);
141 | // Create the shared memory map for comms with the forkserver
142 | #[cfg(feature = "afl")]
143 | let mut shmem_provider = UnixShMemProvider::new().unwrap();
144 | #[cfg(feature = "afl")]
145 | let mut shmem = shmem_provider.new_shmem(map_size).unwrap();
146 | #[cfg(feature = "afl")]
147 | unsafe {
148 | shmem.write_to_env(SHMEM_ENV_VAR).unwrap();
149 | }
150 | #[cfg(feature = "afl")]
151 | let shmem_buf = shmem.as_slice_mut();
152 |
153 | // Create an observation channel to keep track of edges hit.
154 | #[cfg(feature = "afl")]
155 | let edges_observer = unsafe {
156 | HitcountsMapObserver::new(StdMapObserver::new("edges", shmem_buf))
157 | .track_indices()
158 | .track_novelties()
159 | };
160 | #[cfg(feature = "libfuzzer")]
161 | let edges = unsafe { extra_counters() };
162 | #[cfg(feature = "libfuzzer")]
163 | let edges_observer =
164 | StdMapObserver::from_mut_slice("edges", edges.into_iter().next().unwrap())
165 | .track_indices()
166 | .track_novelties();
167 |
168 | let seed = opt.rng_seed.unwrap_or(current_nanos());
169 |
170 | // Initialize Autarkie's visitor
171 | let mut visitor = Visitor::new(
172 | seed,
173 | DepthInfo {
174 | generate: opt.generate_depth,
175 | iterate: opt.iterate_depth,
176 | },
177 | );
178 | I::__autarkie_register(&mut visitor, None, 0);
179 | visitor.calculate_recursion();
180 | let visitor = Rc::new(RefCell::new(visitor));
181 |
182 | // Create a MapFeedback for coverage guided fuzzin'
183 | let map_feedback = MaxMapFeedback::new(&edges_observer);
184 |
185 | let time_observer = TimeObserver::new("time");
186 | let cb = |_fuzzer: &mut _,
187 | _executor: &mut _,
188 | _state: &mut StdState, I, StdRand, OnDiskCorpus>,
189 | _event_manager: &mut _|
190 | -> Result { Ok(true) };
191 | let minimization_stage = IfStage::new(
192 | cb,
193 | tuple_list!(
194 | MinimizationStage::new(Rc::clone(&visitor), &map_feedback),
195 | RecursiveMinimizationStage::new(Rc::clone(&visitor), &map_feedback)
196 | ),
197 | );
198 | let mut feedback = feedback_or!(
199 | map_feedback,
200 | TimeFeedback::new(&time_observer),
201 | RegisterFeedback::new(Rc::clone(&visitor)),
202 | );
203 |
204 | let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new(),);
205 |
206 | // Initialize our State if necessary
207 | let mut state = state.unwrap_or(
208 | StdState::new(
209 | RomuDuoJrRand::with_seed(seed),
210 | // TODO: configure testcache size
211 | CachedOnDiskCorpus::::new(fuzzer_dir.join("queue"), 4096).unwrap(),
212 | OnDiskCorpus::::new(fuzzer_dir.join("crash")).unwrap(),
213 | &mut feedback,
214 | &mut objective,
215 | )
216 | .unwrap(),
217 | );
218 |
219 | if !fuzzer_dir.join("chunks").exists() {
220 | std::fs::create_dir(fuzzer_dir.join("chunks")).unwrap();
221 | }
222 | if !fuzzer_dir.join("cmps").exists() {
223 | std::fs::create_dir(fuzzer_dir.join("cmps")).unwrap();
224 | }
225 |
226 | let mut context = Context::new(fuzzer_dir.clone());
227 |
228 | let scheduler = StdWeightedScheduler::with_schedule(
229 | &mut state,
230 | &edges_observer,
231 | Some(PowerSchedule::explore()),
232 | );
233 | let observers = tuple_list!(edges_observer, time_observer);
234 | let scheduler = scheduler.cycling_scheduler();
235 | // Create our Fuzzer
236 | let mut fuzzer =
237 | StdFuzzer::with_bloom_input_filter(scheduler, feedback, objective, 10_000, 0.0001);
238 |
239 | // Create our Executor
240 | #[cfg(feature = "afl")]
241 | let mut executor = ForkserverExecutor::builder()
242 | .program(opt.executable.clone())
243 | .coverage_map_size(map_size)
244 | .debug_child(opt.debug_child)
245 | .is_persistent(true)
246 | .is_deferred_frksrv(true)
247 | .timeout(Duration::from_millis(opt.hang_timeout))
248 | .shmem_provider(&mut shmem_provider)
249 | .target_bytes_converter(bytes_converter.clone())
250 | .build(observers)
251 | .unwrap();
252 | #[cfg(feature = "libfuzzer")]
253 | let mut harness = harness.unwrap();
254 | #[cfg(feature = "libfuzzer")]
255 | let mut executor = InProcessExecutor::with_timeout(
256 | &mut harness,
257 | observers,
258 | &mut fuzzer,
259 | &mut state,
260 | &mut mgr,
261 | Duration::from_millis(opt.hang_timeout),
262 | )?;
263 | #[cfg(feature = "libfuzzer")]
264 | let mut executor = ShadowExecutor::new(executor, tuple_list!(cmplog_observer));
265 | // Setup a tracing stage in which we log comparisons
266 | #[cfg(feature = "libfuzzer")]
267 | let tracing = ShadowTracingStage::new();
268 |
269 | if let Some(dict_file) = &opt.dict_file {
270 | let file = std::fs::read_to_string(dict_file).expect("cannot read dict file");
271 | for entry in file.split("\n") {
272 | visitor.borrow_mut().register_string(entry.to_string());
273 | }
274 | }
275 |
276 | // Read strings from the target if configured
277 | #[cfg(feature = "afl")]
278 | if opt.get_strings {
279 | let string_regex = Regex::new("^[a-zA-Z0-9_]+$").unwrap();
280 | let strings = Command::new("strings")
281 | .arg(opt.executable.clone())
282 | .output()
283 | .expect("strings gave no output!");
284 | let strings = String::from_utf8_lossy(&strings.stdout);
285 | for string in strings.lines().into_iter() {
286 | if string_regex.is_match(string) {
287 | visitor.borrow_mut().register_string(string.to_string());
288 | }
289 | }
290 | }
291 |
292 | // Reload corpus chunks if they exist
293 | for chunk_dir in std::fs::read_dir(fuzzer_dir.join("chunks"))? {
294 | let dir = chunk_dir?.path();
295 | for chunk in std::fs::read_dir(dir)? {
296 | let path = chunk?.path();
297 | context.add_existing_chunk(path);
298 | }
299 | }
300 | state.add_metadata(context);
301 | state.add_metadata(AutarkieStats::default());
302 |
303 | // Reload corpus
304 | if state.must_load_initial_inputs() {
305 | state.load_initial_inputs(
306 | &mut fuzzer,
307 | &mut executor,
308 | &mut mgr,
309 | &[fuzzer_dir.join("queue").clone()],
310 | )?;
311 | for _ in 0..opt.initial_generated_inputs {
312 | let mut metadata = state.metadata_mut::().expect("fxeZamEw____");
313 | metadata.generated_input();
314 | let mut generated = crate::fuzzer::generate::generate(&mut visitor.borrow_mut());
315 | while generated.is_none() {
316 | generated = crate::fuzzer::generate::generate(&mut visitor.borrow_mut());
317 | }
318 | fuzzer
319 | .evaluate_input(
320 | &mut state,
321 | &mut executor,
322 | &mut mgr,
323 | generated.as_ref().expect("dVoSuGRU____"),
324 | )
325 | .unwrap();
326 | }
327 | let mut metadata = state.metadata_mut::().expect("fxeZamEw____");
328 | metadata.default_input();
329 | println!("We imported {} inputs from disk.", state.corpus().count());
330 | }
331 |
332 | let splice_mutator = AutarkieSpliceMutator::new(Rc::clone(&visitor), opt.max_subslice_size);
333 | let recursion_mutator =
334 | AutarkieRecurseMutator::new(Rc::clone(&visitor), opt.max_subslice_size);
335 | let append_mutator = AutarkieSpliceAppendMutator::new(Rc::clone(&visitor));
336 | /* #[cfg(feature = "afl")]
337 | let cmplog = {
338 | // The CmpLog map shared between the CmpLog observer and CmpLog executor
339 | let mut cmplog_shmem = shmem_provider.uninit_on_shmem::().unwrap();
340 |
341 | // Let the Forkserver know the CmpLog shared memory map ID.
342 | unsafe {
343 | cmplog_shmem.write_to_env("__AFL_CMPLOG_SHM_ID").unwrap();
344 | }
345 | let cmpmap = unsafe { OwnedRefMut::from_shmem(&mut cmplog_shmem) };
346 | // Create the CmpLog observer.
347 | let cmplog_observer = AFLppCmpLogObserver::new("cmplog", cmpmap, true);
348 | let cmplog_ref = cmplog_observer.handle();
349 | // Create the CmpLog executor.
350 | // Cmplog has 25% execution overhead so we give it double the timeout
351 | let cmplog_executor = ForkserverExecutor::builder()
352 | .program(opt.executable.clone())
353 | .coverage_map_size(map_size)
354 | .debug_child(opt.debug_child)
355 | .is_persistent(true)
356 | .is_deferred_frksrv(true)
357 | .timeout(Duration::from_millis(opt.hang_timeout * 2))
358 | .shmem_provider(&mut shmem_provider)
359 | .target_bytes_converter(bytes_converter.clone())
360 | .build(tuple_list!(cmplog_observer))
361 | .unwrap();
362 |
363 | let cb = |_fuzzer: &mut _,
364 | _executor: &mut _,
365 | state: &mut StdState, I, StdRand, OnDiskCorpus>,
366 | _event_manager: &mut _|
367 | -> Result {
368 | if !opt.cmplog || core.core_id() != *opt.cores.ids.first().unwrap() {
369 | return Ok(false);
370 | }
371 | let testcase = state.current_testcase()?;
372 | if testcase.scheduled_count() > 1 {
373 | return Ok(false);
374 | }
375 | Ok(true)
376 | };
377 | IfStage::new(
378 | cb,
379 | tuple_list!(stages::cmp::CmpLogStage::new(
380 | Rc::clone(&visitor),
381 | cmplog_executor,
382 | cmplog_ref
383 | )),
384 | )
385 | }; */
386 | let generate_stage = GenerateStage::new(Rc::clone(&visitor));
387 | let afl_stage = AutarkieBinaryMutatorStage::new(
388 | havoc_mutations_no_crossover(),
389 | 7,
390 | MutationMetadata::Afl,
391 | );
392 | #[cfg(feature = "libfuzzer")]
393 | let i2s = AutarkieBinaryMutatorStage::new(
394 | tuple_list!(I2SRandReplace::new()),
395 | 7,
396 | MutationMetadata::I2S,
397 | );
398 | // TODO: I2S for AFL
399 | #[cfg(feature = "afl")]
400 | let mut stages = tuple_list!(
401 | minimization_stage,
402 | MutatingStageWrapper::new(
403 | AutarkieMutationalStage::new(append_mutator, SPLICE_APPEND_STACK),
404 | Rc::clone(&visitor)
405 | ),
406 | MutatingStageWrapper::new(
407 | AutarkieMutationalStage::new(recursion_mutator, RECURSE_STACK),
408 | Rc::clone(&visitor)
409 | ),
410 | MutatingStageWrapper::new(
411 | AutarkieMutationalStage::new(splice_mutator, SPLICE_STACK),
412 | Rc::clone(&visitor)
413 | ),
414 | MutatingStageWrapper::new(afl_stage, Rc::clone(&visitor)),
415 | MutatingStageWrapper::new(generate_stage, Rc::clone(&visitor)),
416 | StatsStage::new(fuzzer_dir),
417 | );
418 | #[cfg(feature = "libfuzzer")]
419 | let mut stages = tuple_list!(
420 | minimization_stage,
421 | tracing,
422 | MutatingStageWrapper::new(i2s, Rc::clone(&visitor)),
423 | MutatingStageWrapper::new(
424 | AutarkieMutationalStage::new(append_mutator, SPLICE_APPEND_STACK),
425 | Rc::clone(&visitor)
426 | ),
427 | MutatingStageWrapper::new(
428 | AutarkieMutationalStage::new(recursion_mutator, RECURSE_STACK),
429 | Rc::clone(&visitor)
430 | ),
431 | MutatingStageWrapper::new(
432 | AutarkieMutationalStage::new(splice_mutator, SPLICE_STACK),
433 | Rc::clone(&visitor)
434 | ),
435 | MutatingStageWrapper::new(generate_stage, Rc::clone(&visitor)),
436 | MutatingStageWrapper::new(afl_stage, Rc::clone(&visitor)),
437 | StatsStage::new(fuzzer_dir),
438 | );
439 | fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?;
440 | Err(Error::shutting_down())
441 | };
442 |
443 | Launcher::builder()
444 | .cores(&opt.cores)
445 | .monitor(monitor)
446 | .run_client(run_client)
447 | .broker_port(4444)
448 | .shmem_provider(shmem_provider)
449 | .configuration(EventConfig::from_name("default"))
450 | .build()
451 | .launch();
452 | }
453 |
454 | #[derive(Debug, Parser, Clone)]
455 | #[command(
456 | name = "autarkie",
457 | about = "autarkie",
458 | author = "aarnav "
459 | )]
460 | struct Opt {
461 | /// What we wanna fuzz
462 | #[cfg(feature = "afl")]
463 | executable: PathBuf,
464 | /// Fuzzer output dir; will also load inputs from there
465 | #[arg(short = 'o')]
466 | output_dir: PathBuf,
467 |
468 | /// Timeout in milliseconds
469 | #[arg(short = 't', default_value_t = 1000)]
470 | hang_timeout: u64,
471 |
472 | /// seed for rng
473 | #[arg(short = 's')]
474 | rng_seed: Option,
475 |
476 | /// debug the child
477 | #[arg(short = 'd')]
478 | debug_child: bool,
479 |
480 | #[cfg(feature = "afl")]
481 | /// AFL_DUMP_MAP_SIZE + x where x = map bias
482 | #[arg(short = 'm')]
483 | map_bias: usize,
484 |
485 | /// Amount of initial inputs to generate
486 | #[arg(short = 'i', default_value_t = 100)]
487 | initial_generated_inputs: usize,
488 |
489 | /// Include a generate input stage (advanced)
490 | #[arg(short = 'g')]
491 | generate: bool,
492 |
493 | #[arg(short = 'c', value_parser=Cores::from_cmdline)]
494 | cores: Cores,
495 |
496 | /// Max iterate depth when generating iterable nodes (advanced)
497 | #[arg(short = 'I', default_value_t = 5)]
498 | iterate_depth: usize,
499 |
500 | /// Max subslice length when doing partial iterable splicing (advanced)
501 | #[arg(short = 'z', default_value_t = 15)]
502 | max_subslice_size: usize,
503 |
504 | /// Max generate depth when generating recursive nodes (advanced)
505 | #[arg(short = 'G', default_value_t = 2)]
506 | generate_depth: usize,
507 |
508 | /// AFL++ LLVM_DICT2FILE
509 | #[arg(short = 'x')]
510 | dict_file: Option,
511 |
512 | /// Use AFL++'s cmplog feature
513 | #[arg(short = 'e')]
514 | cmplog: bool,
515 |
516 | /// capture strings from the binary (only useful if you have a lot of String nodes)
517 | #[arg(short = 'S')]
518 | get_strings: bool,
519 | }
520 |
521 | #[macro_export]
522 | macro_rules! debug_grammar {
523 | ($t:ty) => {
524 | use $crate::{Node, Visitor};
525 | let mut visitor = Visitor::new(
526 | $crate::fuzzer::current_nanos(),
527 | $crate::DepthInfo {
528 | generate: 105,
529 | iterate: 500,
530 | },
531 | );
532 | <$t>::__autarkie_register(&mut visitor, None, 0);
533 | visitor.calculate_recursion();
534 | let gen_depth = visitor.generate_depth();
535 | loop {
536 | /* println!(
537 | "{:?}",
538 | <$t>::__autarkie_generate(&mut visitor, &mut gen_depth.clone(), &mut 0)
539 | );
540 | println!("--------------------------------"); */
541 | }
542 | };
543 | }
544 | #[cfg(feature = "libfuzzer")]
545 | fn create_monitor_closure() -> impl Fn(&str) + Clone {
546 | #[cfg(unix)]
547 | let stderr_fd = std::os::fd::RawFd::from_str(&std::env::var(STDERR_FD_VAR).unwrap()).unwrap(); // set in main
548 | move |s| {
549 | #[cfg(unix)]
550 | {
551 | use std::os::fd::FromRawFd;
552 |
553 | // unfortunate requirement to meet Clone... thankfully, this does not
554 | // generate effectively any overhead (no allocations, calls get merged)
555 | let mut stderr = unsafe { std::fs::File::from_raw_fd(stderr_fd) };
556 | writeln!(stderr, "{s}").expect("Could not write to stderr???");
557 | std::mem::forget(stderr); // do not close the descriptor!
558 | }
559 | #[cfg(not(unix))]
560 | eprintln!("{s}");
561 | }
562 | }
563 | #[cfg(feature = "libfuzzer")]
564 | /// Communicate the stderr duplicated fd to subprocesses
565 | pub const STDERR_FD_VAR: &str = "_LIBAFL_LIBFUZZER_STDERR_FD";
566 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/mutators/commons.rs:
--------------------------------------------------------------------------------
1 | use std::ops::Range;
2 |
3 | use crate::Visitor;
4 |
5 | pub fn calculate_subslice_bounds(len: usize, max: usize, visitor: &mut Visitor) -> Range {
6 | // minus 1 because we zero index and len is always +1
7 | let start = visitor.random_range(0, len - 1);
8 | let mut end = visitor.random_range(start, len);
9 | if end - start > max {
10 | end = start + max;
11 | }
12 | start..end
13 | }
14 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/mutators/mod.rs:
--------------------------------------------------------------------------------
1 | mod commons;
2 | pub mod recurse_mutate;
3 | pub mod splice;
4 | pub mod splice_append;
5 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/mutators/recurse_mutate.rs:
--------------------------------------------------------------------------------
1 | use crate::Visitor;
2 | use crate::{MutationType, Node};
3 | use libafl::{
4 | corpus::Corpus,
5 | mutators::{MutationResult, Mutator},
6 | state::{HasCorpus, HasRand},
7 | HasMetadata,
8 | };
9 | #[cfg(feature = "introspection")]
10 | use libafl::{mark_feature_time, start_timer};
11 | use libafl_bolts::{HasLen, Named};
12 | use std::{borrow::Cow, cell::RefCell, collections::VecDeque, marker::PhantomData, rc::Rc};
13 |
14 | use crate::fuzzer::Context;
15 |
16 | use super::commons::calculate_subslice_bounds;
17 |
18 | pub const RECURSE_STACK: usize = 100;
19 | pub struct AutarkieRecurseMutator {
20 | max_subslice_size: usize,
21 | visitor: Rc>,
22 | phantom: PhantomData,
23 | }
24 |
25 | impl Mutator for AutarkieRecurseMutator
26 | where
27 | I: Node,
28 | S: HasCorpus + HasRand + HasMetadata,
29 | {
30 | fn mutate(&mut self, state: &mut S, input: &mut I) -> Result {
31 | let mut metadata = state.metadata_mut::()?;
32 | #[cfg(feature = "introspection")]
33 | start_timer!(state);
34 | input.__autarkie_fields(&mut self.visitor.borrow_mut(), 0);
35 | let mut fields = self.visitor.borrow_mut().fields();
36 | #[cfg(feature = "introspection")]
37 | mark_feature_time!(state, Data::Fields);
38 | let field_splice_index = self.visitor.borrow_mut().random_range(0, fields.len() - 1);
39 | let field = &mut fields[field_splice_index];
40 | let ((id, node_ty), ty) = field.last().unwrap();
41 | let mut bias = if self.visitor.borrow_mut().coinflip() {
42 | self.visitor.borrow().generate_depth()
43 | } else {
44 | 0
45 | };
46 | if let crate::NodeType::Iterable(is_fixed_len, field_len, inner_ty) = node_ty {
47 | if *field_len < 3 {
48 | return Ok(MutationResult::Skipped);
49 | }
50 | let mut path = VecDeque::from_iter(field.iter().map(|(i, ty)| i.0));
51 | let subslice_bounds = calculate_subslice_bounds(
52 | *field_len,
53 | self.max_subslice_size,
54 | &mut self.visitor.borrow_mut(),
55 | );
56 | for index in subslice_bounds {
57 | let mut path = VecDeque::from_iter(field.iter().map(|(i, ty)| i.0));
58 | path.push_back(index);
59 | #[cfg(debug_assertions)]
60 | println!("recursive_mutate | subslice | {:?}", field);
61 | input.__autarkie_mutate(
62 | &mut MutationType::GenerateReplace(bias),
63 | &mut self.visitor.borrow_mut(),
64 | path,
65 | );
66 | }
67 | metadata.add_mutation(crate::fuzzer::context::MutationMetadata::RecurseMutateSubsplice);
68 | } else {
69 | let mut path = VecDeque::from_iter(field.iter().map(|(i, ty)| i.0));
70 | #[cfg(debug_assertions)]
71 | println!("recursive_mutate | single | {:?}", field);
72 | input.__autarkie_mutate(
73 | &mut MutationType::GenerateReplace(bias),
74 | &mut self.visitor.borrow_mut(),
75 | path,
76 | );
77 | metadata.add_mutation(crate::fuzzer::context::MutationMetadata::RecurseMutateSingle);
78 | }
79 | Ok(MutationResult::Mutated)
80 | }
81 |
82 | fn post_exec(
83 | &mut self,
84 | _state: &mut S,
85 | _new_corpus_id: Option,
86 | ) -> Result<(), libafl::Error> {
87 | Ok(())
88 | }
89 | }
90 |
91 | impl Named for AutarkieRecurseMutator {
92 | fn name(&self) -> &std::borrow::Cow<'static, str> {
93 | &Cow::Borrowed("AutarkieRecurseMutator")
94 | }
95 | }
96 | impl AutarkieRecurseMutator {
97 | pub fn new(visitor: Rc>, max_subslice_size: usize) -> Self {
98 | Self {
99 | visitor,
100 | max_subslice_size,
101 | phantom: PhantomData,
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/mutators/splice.rs:
--------------------------------------------------------------------------------
1 | use crate::Visitor;
2 | use crate::{MutationType, Node};
3 | use libafl::{
4 | corpus::Corpus,
5 | mutators::{MutationResult, Mutator},
6 | state::{HasCorpus, HasRand},
7 | HasMetadata,
8 | };
9 | #[cfg(feature = "introspection")]
10 | use libafl::{mark_feature_time, start_timer};
11 | use libafl_bolts::{current_time, AsSlice, Named};
12 | use std::{borrow::Cow, cell::RefCell, collections::VecDeque, marker::PhantomData, rc::Rc};
13 |
14 | use crate::fuzzer::Context;
15 |
16 | use super::commons::calculate_subslice_bounds;
17 |
18 | pub const SPLICE_STACK: usize = 100;
19 | pub struct AutarkieSpliceMutator {
20 | visitor: Rc>,
21 | max_subslice_size: usize,
22 | phantom: PhantomData,
23 | }
24 |
25 | #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
26 | #[repr(u8)]
27 | pub enum Data {
28 | Fields,
29 | }
30 |
31 | impl Mutator for AutarkieSpliceMutator
32 | where
33 | I: Node,
34 | S: HasCorpus + HasRand + HasMetadata,
35 | {
36 | fn mutate(&mut self, state: &mut S, input: &mut I) -> Result {
37 | let mut metadata = state.metadata_mut::()?;
38 | let mut mutated_path = None;
39 | input.__autarkie_fields(&mut self.visitor.borrow_mut(), 0);
40 | let mut fields = self.visitor.borrow_mut().fields();
41 | let field_splice_index = self.visitor.borrow_mut().random_range(0, fields.len() - 1);
42 | let field = &fields[field_splice_index];
43 | let ((id, node_ty), ty) = field.last().unwrap();
44 | if let crate::NodeType::Iterable(is_fixed_len, field_len, inner_ty) = node_ty {
45 | let subslice = self.visitor.borrow_mut().coinflip_with_prob(0.6);
46 | if subslice && *field_len > 3 {
47 | let Some(possible_splices) = metadata.get_inputs_for_type(&inner_ty) else {
48 | return Ok(MutationResult::Skipped);
49 | };
50 | let mut path = VecDeque::from_iter(field.iter().map(|(i, ty)| i.0));
51 | mutated_path = Some(path.clone());
52 | let subslice_bounds = calculate_subslice_bounds(
53 | *field_len,
54 | self.max_subslice_size,
55 | &mut self.visitor.borrow_mut(),
56 | );
57 | for index in subslice_bounds {
58 | let mut child_path = path.clone();
59 | child_path.push_back(index);
60 | let random_splice = possible_splices
61 | .get(
62 | self.visitor
63 | .borrow_mut()
64 | .random_range(0, possible_splices.len() - 1),
65 | )
66 | .unwrap();
67 | // TODO: cache this in memory
68 | let data = std::fs::read(random_splice).unwrap();
69 | #[cfg(debug_assertions)]
70 | println!("splice | subslice | {:?}", (&field, &path));
71 | input.__autarkie_mutate(
72 | &mut MutationType::Splice(&mut data.as_slice()),
73 | &mut self.visitor.borrow_mut(),
74 | child_path,
75 | );
76 | }
77 | metadata.add_mutation(crate::fuzzer::context::MutationMetadata::SpliceSubSplice);
78 | } else {
79 | let Some(possible_splices) = metadata.get_inputs_for_type(&inner_ty) else {
80 | return Ok(MutationResult::Skipped);
81 | };
82 | // unfortunately we need to replace the exact amount.
83 | // cause we don't differentiate between vec and slice
84 | let path = VecDeque::from_iter(field.iter().map(|(i, ty)| i.0));
85 | let items = (0..*field_len)
86 | .into_iter()
87 | .map(|_| {
88 | std::fs::read(
89 | possible_splices
90 | .get(
91 | self.visitor
92 | .borrow_mut()
93 | .random_range(0, possible_splices.len() - 1),
94 | )
95 | .expect("NZkjgWib____"),
96 | )
97 | .expect("could not read splice file")
98 | })
99 | .collect::>();
100 | let mut data = if !*is_fixed_len {
101 | crate::serialize_vec_len(if *field_len > 0 { *field_len } else { 0 })
102 | } else {
103 | vec![]
104 | };
105 | data.extend(items.iter().flatten());
106 | mutated_path = Some(path.clone());
107 | #[cfg(debug_assertions)]
108 | println!("splice | full | {:?}", field);
109 | input.__autarkie_mutate(
110 | &mut MutationType::Splice(&mut data.as_slice()),
111 | &mut self.visitor.borrow_mut(),
112 | path,
113 | );
114 | metadata.add_mutation(crate::fuzzer::context::MutationMetadata::SpliceFull);
115 | }
116 | } else {
117 | let Some(possible_splices) = metadata.get_inputs_for_type(ty) else {
118 | return Ok(MutationResult::Skipped);
119 | };
120 | let mut path = VecDeque::from_iter(field.iter().map(|(i, ty)| i.0));
121 | let random_splice = possible_splices
122 | .get(
123 | self.visitor
124 | .borrow_mut()
125 | .random_range(0, possible_splices.len() - 1),
126 | )
127 | .unwrap();
128 | let data = std::fs::read(random_splice).unwrap();
129 | mutated_path = Some(path.clone());
130 | #[cfg(debug_assertions)]
131 | println!("splice | one | {:?} {:?}", field, path);
132 | input.__autarkie_mutate(
133 | &mut MutationType::Splice(&mut data.as_slice()),
134 | &mut self.visitor.borrow_mut(),
135 | path,
136 | );
137 | metadata.add_mutation(crate::fuzzer::context::MutationMetadata::SpliceSingle);
138 | }
139 | Ok(MutationResult::Mutated)
140 | }
141 |
142 | fn post_exec(
143 | &mut self,
144 | _state: &mut S,
145 | _new_corpus_id: Option,
146 | ) -> Result<(), libafl::Error> {
147 | Ok(())
148 | }
149 | }
150 |
151 | impl Named for AutarkieSpliceMutator {
152 | fn name(&self) -> &std::borrow::Cow<'static, str> {
153 | &Cow::Borrowed("AutarkieSpliceMutator")
154 | }
155 | }
156 | impl AutarkieSpliceMutator {
157 | pub fn new(visitor: Rc>, max_subslice_size: usize) -> Self {
158 | Self {
159 | visitor,
160 | max_subslice_size,
161 | phantom: PhantomData,
162 | }
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/mutators/splice_append.rs:
--------------------------------------------------------------------------------
1 | use crate::fuzzer::context::MutationMetadata;
2 | use crate::Node;
3 | use crate::Visitor;
4 | use libafl::{
5 | corpus::Corpus,
6 | mutators::{MutationResult, Mutator},
7 | state::{HasCorpus, HasRand},
8 | HasMetadata,
9 | };
10 | #[cfg(feature = "introspection")]
11 | use libafl::{mark_feature_time, start_timer};
12 | use libafl_bolts::{AsSlice, Named};
13 | use std::{borrow::Cow, cell::RefCell, collections::VecDeque, marker::PhantomData, rc::Rc};
14 |
15 | use crate::fuzzer::Context;
16 |
17 | pub const SPLICE_APPEND_STACK: usize = 5;
18 | pub struct AutarkieSpliceAppendMutator {
19 | visitor: Rc>,
20 | phantom: PhantomData,
21 | }
22 |
23 | impl Mutator for AutarkieSpliceAppendMutator
24 | where
25 | I: Node,
26 | S: HasCorpus + HasRand + HasMetadata,
27 | {
28 | fn mutate(&mut self, state: &mut S, input: &mut I) -> Result {
29 | let mut metadata = state.metadata_mut::().unwrap();
30 | #[cfg(feature = "introspection")]
31 | start_timer!(state);
32 | input.__autarkie_fields(&mut self.visitor.borrow_mut(), 0);
33 | #[cfg(feature = "introspection")]
34 | mark_feature_time!(state, Data::Fields);
35 | let mut fields = self
36 | .visitor
37 | .borrow_mut()
38 | .fields()
39 | .into_iter()
40 | .filter(|inner| {
41 | let last = inner.last().as_ref().unwrap();
42 | matches!(&crate::NodeType::Iterable, last)
43 | })
44 | .collect::>();
45 | if fields.len() == 0 {
46 | return Ok(MutationResult::Skipped);
47 | }
48 | let field_splice_index = self.visitor.borrow_mut().random_range(0, fields.len() - 1);
49 | let field = &fields[field_splice_index];
50 | let ((id, node_ty), ty) = field.last().unwrap();
51 | if let crate::NodeType::Iterable(is_fixed_len, field_len, inner_ty) = node_ty {
52 | if *is_fixed_len {
53 | return Ok(MutationResult::Skipped);
54 | }
55 | if let Some(possible_splices) = metadata.get_inputs_for_type(&inner_ty) {
56 | // calculate subsplice size
57 | let iter_count = self.visitor.borrow().iterate_depth();
58 | let append_count = self.visitor.borrow_mut().random_range(1, iter_count);
59 | let path = VecDeque::from_iter(field.iter().map(|(i, ty)| i.0));
60 | for _ in 0..append_count {
61 | let random_splice = possible_splices
62 | .get(
63 | self.visitor
64 | .borrow_mut()
65 | .random_range(0, possible_splices.len() - 1),
66 | )
67 | .unwrap();
68 | // TODO: cache this in memory
69 | let data = std::fs::read(random_splice).unwrap();
70 | #[cfg(debug_assertions)]
71 | println!("splice | splice_append | {:?}", (&field, &path));
72 | input.__autarkie_mutate(
73 | &mut crate::MutationType::SpliceAppend(&mut data.as_slice()),
74 | &mut self.visitor.borrow_mut(),
75 | path.clone(),
76 | );
77 | }
78 | metadata.add_mutation(crate::fuzzer::context::MutationMetadata::SpliceAppend);
79 | return Ok(MutationResult::Mutated);
80 | } else {
81 | return Ok(MutationResult::Skipped);
82 | }
83 | }
84 | Ok(MutationResult::Skipped)
85 | }
86 |
87 | fn post_exec(
88 | &mut self,
89 | _state: &mut S,
90 | _new_corpus_id: Option,
91 | ) -> Result<(), libafl::Error> {
92 | Ok(())
93 | }
94 | }
95 |
96 | impl Named for AutarkieSpliceAppendMutator {
97 | fn name(&self) -> &std::borrow::Cow<'static, str> {
98 | &Cow::Borrowed("AutarkieSpliceAppendMutator")
99 | }
100 | }
101 | impl AutarkieSpliceAppendMutator {
102 | pub fn new(visitor: Rc>) -> Self {
103 | Self {
104 | visitor,
105 | phantom: PhantomData,
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/stages/binary_mutator.rs:
--------------------------------------------------------------------------------
1 | //! Stage that wraps mutating stages for stats and cleanup
2 | use crate::fuzzer::context::MutationMetadata;
3 | use crate::Visitor;
4 | use crate::{fuzzer::Context, Node};
5 | use core::{marker::PhantomData, time::Duration};
6 | use libafl::inputs::BytesInput;
7 | use libafl::mutators::MutatorsTuple;
8 | use libafl::state::HasRand;
9 | use libafl_bolts::rands::Rand;
10 | use libafl_bolts::{current_time, Error};
11 | use std::cell::RefCell;
12 | use std::num::NonZero;
13 | use std::rc::Rc;
14 |
15 | use libafl::{
16 | events::EventFirer,
17 | executors::Executor,
18 | mutators::{MutationResult, Mutator},
19 | stages::{Restartable, Stage},
20 | state::HasCurrentTestcase,
21 | Evaluator, HasMetadata,
22 | };
23 |
24 | #[derive(Debug)]
25 | pub struct AutarkieBinaryMutatorStage {
26 | inner: M,
27 | stack: usize,
28 | mutation_ty: MutationMetadata,
29 | phantom: PhantomData<(I, S)>,
30 | }
31 |
32 | impl AutarkieBinaryMutatorStage {
33 | /// Create a `AutarkieBinaryMutatorStage`
34 | pub fn new(inner: M, stack: usize, mutation_ty: MutationMetadata) -> Self {
35 | Self {
36 | stack,
37 | inner,
38 | mutation_ty,
39 | phantom: PhantomData,
40 | }
41 | }
42 | }
43 |
44 | impl Stage for AutarkieBinaryMutatorStage
45 | where
46 | I: Node,
47 | E: Executor,
48 | Z: Evaluator,
49 | EM: EventFirer,
50 | S: HasMetadata + HasCurrentTestcase + HasRand,
51 | M: MutatorsTuple, S>,
52 | {
53 | fn perform(
54 | &mut self,
55 | fuzzer: &mut Z,
56 | executor: &mut E,
57 | state: &mut S,
58 | manager: &mut EM,
59 | ) -> Result<(), Error> {
60 | let mut metadata = state.metadata_mut::().expect("fxeZamEw____");
61 | let mut input = crate::serialize(&state.current_input_cloned().unwrap());
62 | let mut metadata = state.metadata_mut::().unwrap();
63 | metadata.generated_input();
64 | for _ in 0..self.stack {
65 | let mutation = state
66 | .rand_mut()
67 | .below(unsafe { NonZero::new(self.inner.len()).unwrap_unchecked() })
68 | .into();
69 | self.inner.get_and_mutate(mutation, state, &mut input);
70 | #[cfg(not(feature = "scale"))]
71 | let Some(deserialized) = crate::maybe_deserialize(&input) else {
72 | return Ok(());
73 | };
74 | #[cfg(feature = "scale")]
75 | let Some(deserialized) = crate::maybe_deserialize(&mut input.as_slice()) else {
76 | return Ok(());
77 | };
78 | let mut metadata = state.metadata_mut::().unwrap();
79 | metadata.add_mutation(self.mutation_ty.clone());
80 | fuzzer.evaluate_input(state, executor, manager, &deserialized)?;
81 | }
82 | let mut metadata = state.metadata_mut::().expect("fxeZamEw____");
83 | metadata.default_input();
84 | Ok(())
85 | }
86 | }
87 |
88 | impl Restartable for AutarkieBinaryMutatorStage {
89 | fn should_restart(&mut self, state: &mut S) -> Result {
90 | Ok(true)
91 | }
92 |
93 | fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> {
94 | Ok(())
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/stages/cmp.rs:
--------------------------------------------------------------------------------
1 | use crate::{MutationType, Node, Visitor};
2 | use libafl::{
3 | corpus::Corpus,
4 | events::EventFirer,
5 | executors::{Executor, HasObservers},
6 | observers::{AFLppCmpValuesMetadata, CmpValues, ObserversTuple},
7 | stages::{Restartable, Stage},
8 | state::HasCurrentTestcase,
9 | Evaluator, HasMetadata,
10 | };
11 | use libafl_bolts::{
12 | tuples::{Handle, MatchNameRef},
13 | AsSlice,
14 | };
15 | use libafl_targets::AFLppCmpLogObserver;
16 | use serde::Serialize;
17 | use std::{
18 | cell::RefCell,
19 | collections::{HashSet, VecDeque},
20 | marker::PhantomData,
21 | rc::Rc,
22 | };
23 |
24 | use crate::fuzzer::Context;
25 |
26 | #[derive(Debug)]
27 | pub struct CmpLogStage<'a, TE, I> {
28 | visitor: Rc>,
29 | tracer_executor: TE,
30 | cmplog_observer_handle: Handle>,
31 | phantom: PhantomData,
32 | }
33 |
34 | impl<'a, TE, I> CmpLogStage<'a, TE, I> {
35 | pub fn new(
36 | visitor: Rc>,
37 | tracer_executor: TE,
38 | cmplog_observer_handle: Handle>,
39 | ) -> Self {
40 | Self {
41 | cmplog_observer_handle,
42 | tracer_executor,
43 | visitor,
44 | phantom: PhantomData,
45 | }
46 | }
47 | }
48 |
49 | impl Stage for CmpLogStage<'_, TE, I>
50 | where
51 | I: Node + Serialize + Clone,
52 | S: HasCurrentTestcase + HasMetadata,
53 | E: Executor,
54 | EM: EventFirer,
55 | TE: Executor + HasObservers,
56 | TE::Observers: MatchNameRef + ObserversTuple,
57 | Z: Evaluator,
58 | {
59 | fn perform(
60 | &mut self,
61 | fuzzer: &mut Z,
62 | executor: &mut E,
63 | state: &mut S,
64 | manager: &mut EM,
65 | ) -> Result<(), libafl_bolts::Error> {
66 | if state.current_testcase().unwrap().scheduled_count() > 1 {
67 | return Ok(());
68 | }
69 |
70 | let unmutated_input = state.current_input_cloned()?;
71 |
72 | let mut obs = self.tracer_executor.observers_mut();
73 | let ob = obs.get_mut(&self.cmplog_observer_handle).unwrap();
74 | ob.set_original(true);
75 | self.tracer_executor
76 | .observers_mut()
77 | .pre_exec_all(state, &unmutated_input)?;
78 |
79 | let exit_kind =
80 | self.tracer_executor
81 | .run_target(fuzzer, state, manager, &unmutated_input)?;
82 | self.tracer_executor
83 | .observers_mut()
84 | .post_exec_all(state, &unmutated_input, &exit_kind)?;
85 |
86 | let mut reduced = HashSet::new();
87 | if let Ok(data) = state.metadata::() {
88 | for item in data.orig_cmpvals().values() {
89 | for i in item.iter() {
90 | match i {
91 | CmpValues::U16((left, right, is_const)) => {
92 | reduced.insert((*left as u64, *right as u64));
93 | reduced.insert((*right as u64, *left as u64));
94 | }
95 | CmpValues::U32((left, right, is_const)) => {
96 | reduced.insert((*left as u64, *right as u64));
97 | reduced.insert((*right as u64, *left as u64));
98 | }
99 | CmpValues::U64((left, right, is_const)) => {
100 | reduced.insert((*left, *right));
101 | reduced.insert((*right, *left));
102 | }
103 | CmpValues::Bytes((left, right)) => {
104 | // TODO
105 | }
106 | // ignore U8
107 | CmpValues::U8(_) => {}
108 | }
109 | }
110 | }
111 | }
112 |
113 | let metadata = state
114 | .metadata_mut::()
115 | .expect("we must have context!");
116 |
117 | for cmp in reduced {
118 | unmutated_input.__autarkie_cmps(&mut self.visitor.borrow_mut(), 0, cmp);
119 | let matches = self.visitor.borrow_mut().cmps();
120 | for path in matches {
121 | let cmp_path = path.0.iter().map(|(i, ty)| i.0).collect::>();
122 | let mut serialized_alternative = path.1.as_slice();
123 | let mut input = unmutated_input.clone();
124 | let before = crate::serialize(&input);
125 | #[cfg(debug_assertions)]
126 | println!("cmplog_splice | one | {:?}", path.0);
127 | input.__autarkie_mutate(
128 | &mut MutationType::Splice(&mut serialized_alternative),
129 | &mut self.visitor.borrow_mut(),
130 | cmp_path,
131 | );
132 | let res = fuzzer.evaluate_input(state, executor, manager, &input)?;
133 | }
134 | }
135 |
136 | // walk all fields in the input and capture the paths where reduced is present and store
137 | // those paths as potentially interesting.
138 | Ok(())
139 | }
140 | }
141 |
142 | impl<'a, TE, I, S> Restartable for CmpLogStage<'a, TE, I> {
143 | fn should_restart(&mut self, state: &mut S) -> Result {
144 | Ok(true)
145 | }
146 |
147 | fn clear_progress(&mut self, state: &mut S) -> Result<(), libafl::Error> {
148 | Ok(())
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/stages/generate.rs:
--------------------------------------------------------------------------------
1 | use crate::{fuzzer::context::Context, Node, Visitor};
2 | use libafl::{
3 | corpus::Corpus,
4 | events::EventFirer,
5 | executors::Executor,
6 | stages::{Restartable, Stage},
7 | state::{HasCorpus, HasCurrentTestcase},
8 | Evaluator, HasMetadata,
9 | };
10 | use serde::Serialize;
11 | use std::{cell::RefCell, marker::PhantomData, rc::Rc};
12 |
13 | #[derive(Debug)]
14 | pub struct GenerateStage {
15 | visitor: Rc>,
16 | phantom: PhantomData,
17 | }
18 |
19 | impl GenerateStage {
20 | pub fn new(visitor: Rc>) -> Self {
21 | Self {
22 | visitor,
23 | phantom: PhantomData,
24 | }
25 | }
26 | }
27 |
28 | impl Stage for GenerateStage
29 | where
30 | I: Node + Serialize,
31 | S: HasCurrentTestcase + HasCorpus + HasMetadata,
32 | E: Executor,
33 | EM: EventFirer,
34 | Z: Evaluator,
35 | {
36 | fn perform(
37 | &mut self,
38 | fuzzer: &mut Z,
39 | executor: &mut E,
40 | state: &mut S,
41 | manager: &mut EM,
42 | ) -> Result<(), libafl_bolts::Error> {
43 | let mut metadata = state.metadata_mut::()?;
44 | metadata.generated_input();
45 | let Some(generated) = generate(&mut self.visitor.borrow_mut()) else {
46 | metadata.default_input();
47 | return Ok(());
48 | };
49 | metadata.add_mutation(crate::fuzzer::context::MutationMetadata::Generate);
50 | fuzzer.evaluate_input(state, executor, manager, &generated)?;
51 | let mut metadata = state.metadata_mut::()?;
52 | metadata.default_input();
53 | Ok(())
54 | }
55 | }
56 |
57 | pub fn generate(visitor: &mut Visitor) -> Option
58 | where
59 | I: Node,
60 | {
61 | I::__autarkie_generate(visitor, &mut visitor.generate_depth(), &mut 0)
62 | }
63 |
64 | impl Restartable for GenerateStage {
65 | fn should_restart(&mut self, state: &mut S) -> Result {
66 | Ok(true)
67 | }
68 |
69 | fn clear_progress(&mut self, state: &mut S) -> Result<(), libafl::Error> {
70 | Ok(())
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/stages/libfuzzer_cmp.rs:
--------------------------------------------------------------------------------
1 | use crate::{MutationType, Node, Visitor};
2 | use libafl::{
3 | corpus::Corpus,
4 | events::EventFirer,
5 | executors::{Executor, HasObservers},
6 | observers::{AFLppCmpValuesMetadata, CmpValues, CmpValuesMetadata, ObserversTuple},
7 | stages::{Restartable, Stage},
8 | state::HasCurrentTestcase,
9 | Evaluator, HasMetadata,
10 | };
11 | use libafl_bolts::{
12 | tuples::{Handle, MatchNameRef},
13 | AsSlice,
14 | };
15 | use serde::Serialize;
16 | use std::{
17 | cell::RefCell,
18 | collections::{HashSet, VecDeque},
19 | marker::PhantomData,
20 | rc::Rc,
21 | };
22 |
23 | use crate::fuzzer::Context;
24 |
25 | #[derive(Debug)]
26 | pub struct LibfuzzerCmplogStage {
27 | visitor: Rc>,
28 | phantom: PhantomData,
29 | }
30 |
31 | impl LibfuzzerCmplogStage {
32 | pub fn new(visitor: Rc>) -> Self {
33 | Self {
34 | visitor,
35 | phantom: PhantomData,
36 | }
37 | }
38 | }
39 |
40 | impl Stage for LibfuzzerCmplogStage
41 | where
42 | I: Node + Serialize + Clone,
43 | S: HasCurrentTestcase + HasMetadata,
44 | E: Executor,
45 | EM: EventFirer,
46 | Z: Evaluator,
47 | {
48 | fn perform(
49 | &mut self,
50 | fuzzer: &mut Z,
51 | executor: &mut E,
52 | state: &mut S,
53 | manager: &mut EM,
54 | ) -> Result<(), libafl_bolts::Error> {
55 | if state.current_testcase().unwrap().scheduled_count() > 1 {
56 | return Ok(());
57 | }
58 | let unmutated_input = state.current_input_cloned()?;
59 | let mut reduced = HashSet::new();
60 | if let Ok(data) = state.metadata::() {
61 | for i in data.list.clone() {
62 | match i {
63 | CmpValues::U16((left, right, is_const)) => {
64 | reduced.insert((left as u64, right as u64));
65 | reduced.insert((right as u64, left as u64));
66 | }
67 | CmpValues::U32((left, right, is_const)) => {
68 | reduced.insert((left as u64, right as u64));
69 | reduced.insert((right as u64, left as u64));
70 | }
71 | CmpValues::U64((left, right, is_const)) => {
72 | reduced.insert((left, right));
73 | reduced.insert((right, left));
74 | }
75 | CmpValues::Bytes((left, right)) => {
76 | if left.as_slice()
77 | != [
78 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
79 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
80 | ]
81 | && right.as_slice() != left.as_slice()
82 | {
83 | // TODO
84 | }
85 | }
86 | // ignore U8
87 | CmpValues::U8(_) => {}
88 | }
89 | }
90 | }
91 |
92 | let metadata = state
93 | .metadata_mut::()
94 | .expect("we must have context!");
95 | metadata.generated_input();
96 | for cmp in reduced {
97 | unmutated_input.__autarkie_cmps(&mut self.visitor.borrow_mut(), 0, cmp);
98 | let matches = self.visitor.borrow_mut().cmps();
99 | for path in matches {
100 | let cmp_path = path.0.iter().map(|(i, ty)| i.0).collect::>();
101 | let mut serialized_alternative = path.1.as_slice();
102 | let mut input = unmutated_input.clone();
103 | let before = crate::serialize(&input);
104 | #[cfg(debug_assertions)]
105 | println!("cmplog_splice | one | {:?}", path.0);
106 | input.__autarkie_mutate(
107 | &mut MutationType::Splice(&mut serialized_alternative),
108 | &mut self.visitor.borrow_mut(),
109 | cmp_path,
110 | );
111 |
112 | let metadata = state
113 | .metadata_mut::()
114 | .expect("we must have context!");
115 | metadata.add_mutation(crate::fuzzer::context::MutationMetadata::Cmplog);
116 | let res = fuzzer.evaluate_input(state, executor, manager, &input)?;
117 | }
118 | }
119 | let metadata = state
120 | .metadata_mut::()
121 | .expect("we must have context!");
122 | metadata.default_input();
123 |
124 | // walk all fields in the input and capture the paths where reduced is present and store
125 | // those paths as potentially interesting.
126 | Ok(())
127 | }
128 | }
129 |
130 | impl Restartable for LibfuzzerCmplogStage {
131 | fn should_restart(&mut self, state: &mut S) -> Result {
132 | Ok(true)
133 | }
134 |
135 | fn clear_progress(&mut self, state: &mut S) -> Result<(), libafl::Error> {
136 | Ok(())
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/stages/minimization.rs:
--------------------------------------------------------------------------------
1 | use crate::{fuzzer::stages::stats::AutarkieStats, MutationType, Node, NodeType, Visitor};
2 | use libafl::{
3 | corpus::Corpus,
4 | events::EventFirer,
5 | executors::{Executor, HasObservers},
6 | feedbacks::{HasObserverHandle, MapIndexesMetadata, MapNoveltiesMetadata},
7 | observers::{CanTrack, MapObserver, ObserversTuple},
8 | stages::{Restartable, Stage},
9 | state::{HasCorpus, HasCurrentTestcase},
10 | Evaluator, HasMetadata,
11 | };
12 | use libafl_bolts::{tuples::Handle, AsIter, Named};
13 | use num_traits::Bounded;
14 | use serde::{Deserialize, Serialize};
15 | use std::{
16 | borrow::{Borrow, Cow},
17 | cell::RefCell,
18 | collections::{HashMap, HashSet, VecDeque},
19 | fmt::Debug,
20 | marker::PhantomData,
21 | rc::Rc,
22 | };
23 |
24 | use crate::fuzzer::Context;
25 |
26 | #[derive(Debug)]
27 | pub struct MinimizationStage {
28 | map_observer_handle: Handle,
29 | map_name: Cow<'static, str>,
30 | visitor: Rc>,
31 | phantom: PhantomData<(E, O, OT, S, I)>,
32 | }
33 |
34 | impl MinimizationStage
35 | where
36 | O: MapObserver,
37 | for<'it> O: AsIter<'it, Item = O::Entry>,
38 | C: AsRef + CanTrack,
39 | OT: ObserversTuple,
40 | {
41 | pub fn new(visitor: Rc>, map_feedback: &F) -> Self
42 | where
43 | F: HasObserverHandle + Named,
44 | {
45 | let map_name = map_feedback.name().clone();
46 | Self {
47 | map_observer_handle: map_feedback.observer_handle().clone(),
48 | map_name: map_name.clone(),
49 | visitor,
50 | phantom: PhantomData,
51 | }
52 | }
53 | }
54 |
55 | impl Stage for MinimizationStage
56 | where
57 | I: Node + Serialize + Clone,
58 | S: HasCurrentTestcase + HasCorpus + HasMetadata,
59 | E: Executor + HasObservers,
60 | EM: EventFirer,
61 | Z: Evaluator,
62 | O: MapObserver,
63 | C: AsRef + CanTrack,
64 | for<'de> ::Entry:
65 | Serialize + Deserialize<'de> + 'static + Default + Debug + Bounded,
66 | OT: ObserversTuple,
67 | {
68 | fn perform(
69 | &mut self,
70 | fuzzer: &mut Z,
71 | executor: &mut E,
72 | state: &mut S,
73 | manager: &mut EM,
74 | ) -> Result<(), libafl_bolts::Error> {
75 | if state.current_testcase()?.scheduled_count() > 0 {
76 | return Ok(());
77 | }
78 |
79 | let metadata = state.metadata::().unwrap();
80 | let novelties = state
81 | .current_testcase()
82 | .unwrap()
83 | .borrow()
84 | .metadata::()
85 | .unwrap()
86 | .list
87 | .clone();
88 | let mut current = state.current_input_cloned().unwrap();
89 | current.__autarkie_fields(&mut self.visitor.borrow_mut(), 0);
90 | let mut skip = 0;
91 | let mut fields = self.visitor.borrow_mut().fields();
92 | shuffle(&mut fields, &mut self.visitor.borrow_mut());
93 | let mut found = false;
94 | loop {
95 | let field = fields.pop();
96 | if field.is_none() {
97 | break;
98 | }
99 | let field = field.unwrap();
100 | let ((id, node_ty), ty) = field.last().unwrap();
101 | if let NodeType::Iterable(is_fixed_len, field_len, inner_ty) = node_ty {
102 | let path = VecDeque::from_iter(field.iter().map(|(i, ty)| i.0));
103 | // NOTE: -1 because we zero index
104 | let mut len = field_len.saturating_sub(1);
105 | let mut counter = 0;
106 | if *is_fixed_len {
107 | continue;
108 | }
109 | loop {
110 | if len == 0 || counter >= len {
111 | break;
112 | }
113 | let mut inner = current.clone();
114 | inner.__autarkie_mutate(
115 | &mut MutationType::IterablePop(counter),
116 | &mut self.visitor.borrow_mut(),
117 | path.clone(),
118 | );
119 | let run = fuzzer.evaluate_input(state, executor, manager, &inner)?;
120 | let map = &executor.observers()[&self.map_observer_handle]
121 | .as_ref()
122 | .how_many_set(&novelties);
123 | if *map == novelties.len() {
124 | found = true;
125 | current = inner;
126 | current.__autarkie_fields(&mut self.visitor.borrow_mut(), 0);
127 | fields = self.visitor.borrow_mut().fields();
128 | len = len.saturating_sub(1);
129 | }
130 | counter += 1;
131 | }
132 | }
133 | }
134 | state.current_testcase_mut()?.set_input(current.clone());
135 | if found {
136 | let metadata = state
137 | .metadata_mut::()
138 | .unwrap()
139 | .add_new_input_mutation(
140 | crate::fuzzer::context::MutationMetadata::IterableMinimization,
141 | );
142 | }
143 | Ok(())
144 | }
145 | }
146 | impl Restartable for MinimizationStage {
147 | fn should_restart(&mut self, state: &mut S) -> Result {
148 | Ok(true)
149 | }
150 |
151 | fn clear_progress(&mut self, state: &mut S) -> Result<(), libafl::Error> {
152 | Ok(())
153 | }
154 | }
155 |
156 | pub fn shuffle(data: &mut [T], v: &mut Visitor) {
157 | let n: usize = data.len();
158 | for i in (1..data.len()).rev() {
159 | let j = v.random_range(0, (i));
160 | data.swap(i, j);
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/stages/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod binary_mutator;
2 | #[cfg(feature = "afl")]
3 | pub mod cmp;
4 | pub mod generate;
5 | #[cfg(feature = "libfuzzer")]
6 | pub mod libfuzzer_cmp;
7 | pub mod minimization;
8 | pub mod mutating;
9 | pub mod mutational;
10 | pub mod novelty_minimization;
11 | pub mod recursive_minimization;
12 | pub mod stats;
13 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/stages/mutating.rs:
--------------------------------------------------------------------------------
1 | //! Stage that wraps mutating stages for stats and cleanup
2 | use crate::fuzzer::Context;
3 | use crate::Visitor;
4 | use core::{marker::PhantomData, time::Duration};
5 | use libafl_bolts::{current_time, Error};
6 | use std::cell::RefCell;
7 | use std::rc::Rc;
8 |
9 | use libafl::{
10 | stages::{Restartable, Stage},
11 | HasMetadata,
12 | };
13 | /// Track an inner Stage's execution time
14 | #[derive(Debug)]
15 | pub struct MutatingStageWrapper {
16 | inner: ST,
17 | visitor: Rc>,
18 | phantom: PhantomData,
19 | }
20 |
21 | impl MutatingStageWrapper {
22 | /// Create a `MutatingStageWrapper`
23 | pub fn new(inner: ST, visitor: Rc>) -> Self {
24 | Self {
25 | inner,
26 | visitor,
27 | phantom: PhantomData,
28 | }
29 | }
30 | }
31 |
32 | impl Stage for MutatingStageWrapper
33 | where
34 | S: HasMetadata,
35 | ST: Stage,
36 | {
37 | fn perform(
38 | &mut self,
39 | fuzzer: &mut Z,
40 | executor: &mut E,
41 | state: &mut S,
42 | manager: &mut M,
43 | ) -> Result<(), Error> {
44 | self.inner.perform(fuzzer, executor, state, manager)?;
45 | let _ = state.metadata_mut::().unwrap().clear_mutations();
46 | let _ = self.visitor.borrow_mut().serialized();
47 | Ok(())
48 | }
49 | }
50 |
51 | impl Restartable for MutatingStageWrapper
52 | where
53 | ST: Restartable,
54 | {
55 | fn should_restart(&mut self, state: &mut S) -> Result {
56 | self.inner.should_restart(state)
57 | }
58 |
59 | fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> {
60 | self.inner.clear_progress(state)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/stages/mutational.rs:
--------------------------------------------------------------------------------
1 | //! Stage that wraps mutating stages for stats and cleanup
2 | use crate::fuzzer::Context;
3 | use crate::Visitor;
4 | use core::{marker::PhantomData, time::Duration};
5 | use libafl_bolts::{current_time, Error};
6 | use std::cell::RefCell;
7 | use std::rc::Rc;
8 |
9 | use libafl::{
10 | events::EventFirer,
11 | executors::Executor,
12 | mutators::{MutationResult, Mutator},
13 | stages::{Restartable, Stage},
14 | state::HasCurrentTestcase,
15 | Evaluator, HasMetadata,
16 | };
17 |
18 | #[derive(Debug)]
19 | pub struct AutarkieMutationalStage {
20 | inner: M,
21 | stack: usize,
22 | phantom: PhantomData<(I, S)>,
23 | }
24 |
25 | impl AutarkieMutationalStage {
26 | /// Create a `AutarkieMutationalStage`
27 | pub fn new(inner: M, stack: usize) -> Self {
28 | Self {
29 | inner,
30 | stack,
31 | phantom: PhantomData,
32 | }
33 | }
34 | }
35 |
36 | impl Stage for AutarkieMutationalStage
37 | where
38 | E: Executor,
39 | Z: Evaluator,
40 | EM: EventFirer,
41 | S: HasMetadata + HasCurrentTestcase,
42 | M: Mutator,
43 | {
44 | fn perform(
45 | &mut self,
46 | fuzzer: &mut Z,
47 | executor: &mut E,
48 | state: &mut S,
49 | manager: &mut EM,
50 | ) -> Result<(), Error> {
51 | let mut current = state.current_input_cloned().unwrap();
52 | for i in 0..self.stack {
53 | if self.inner.mutate(state, &mut current)? == MutationResult::Mutated {
54 | fuzzer.evaluate_input(state, executor, manager, ¤t)?;
55 | }
56 | }
57 | Ok(())
58 | }
59 | }
60 |
61 | impl Restartable for AutarkieMutationalStage {
62 | fn should_restart(&mut self, state: &mut S) -> Result {
63 | Ok(true)
64 | }
65 |
66 | fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> {
67 | Ok(())
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/autarkie/src/fuzzer/stages/novelty_minimization.rs:
--------------------------------------------------------------------------------
1 | use crate::{fuzzer::stages::stats::AutarkieStats, MutationType, Node, NodeType, Visitor};
2 | use libafl::{
3 | corpus::Corpus,
4 | events::EventFirer,
5 | executors::{Executor, HasObservers},
6 | feedbacks::{HasObserverHandle, MapIndexesMetadata, MapNoveltiesMetadata},
7 | observers::{CanTrack, MapObserver, ObserversTuple},
8 | stages::{Restartable, Stage},
9 | state::{HasCorpus, HasCurrentTestcase},
10 | Evaluator, HasMetadata,
11 | };
12 | use libafl_bolts::{tuples::Handle, AsIter, Named};
13 | use num_traits::Bounded;
14 | use serde::{Deserialize, Serialize};
15 | use std::{
16 | borrow::{Borrow, Cow},
17 | cell::RefCell,
18 | collections::{HashMap, HashSet, VecDeque},
19 | fmt::Debug,
20 | marker::PhantomData,
21 | rc::Rc,
22 | };
23 |
24 | use crate::fuzzer::Context;
25 |
26 | #[derive(Debug)]
27 | pub struct NoveltyMinimizationStage {
28 | map_observer_handle: Handle,
29 | map_name: Cow<'static, str>,
30 | visitor: Rc>,
31 | phantom: PhantomData<(E, O, OT, S, I)>,
32 | }
33 |
34 | impl NoveltyMinimizationStage
35 | where
36 | O: MapObserver,
37 | for<'it> O: AsIter<'it, Item = O::Entry>,
38 | O::Entry: 'static + Default + Debug + serde::de::DeserializeOwned + serde::Serialize,
39 | C: AsRef + CanTrack,
40 | OT: ObserversTuple,
41 | {
42 | pub fn new(visitor: Rc>, map_feedback: &F) -> Self
43 | where
44 | F: HasObserverHandle + Named,
45 | {
46 | let map_name = map_feedback.name().clone();
47 | Self {
48 | map_observer_handle: map_feedback.observer_handle().clone(),
49 | map_name: map_name.clone(),
50 | visitor,
51 | phantom: PhantomData,
52 | }
53 | }
54 | }
55 |
56 | impl