trident_client/commander/
fuzz.rs

1use crate::coverage::Coverage;
2use crate::coverage::NotificationType;
3use crate::utils::generate_unique_fuzz_filename;
4use fehler::throw;
5use fehler::throws;
6use std::collections::HashMap;
7use tokio::process::Command;
8use trident_config::coverage::Coverage as CoverageConfig;
9use trident_config::TridentConfig;
10
11use super::Commander;
12use super::Error;
13
14impl Commander {
15    #[throws]
16    pub async fn run(&self, target: String, with_exit_code: bool, seed: Option<String>) {
17        let config = TridentConfig::new();
18
19        if config.get_metrics() {
20            std::env::set_var("FUZZING_METRICS", "true");
21
22            if config.get_metrics_json() {
23                let json_path = generate_unique_fuzz_filename("fuzzing_metrics", &target, "json")
24                    .await
25                    .map_err(|e| {
26                        Error::Anyhow(anyhow::anyhow!(
27                            "Failed to generate fuzzing metrics path: {:?}",
28                            e
29                        ))
30                    })?;
31                std::env::set_var("FUZZING_JSON", json_path.to_string_lossy().to_string());
32            }
33
34            if config.get_metrics_dashboard() {
35                let dashboard_path =
36                    generate_unique_fuzz_filename("fuzzing_dashboard", &target, "html")
37                        .await
38                        .map_err(|e| {
39                            Error::Anyhow(anyhow::anyhow!(
40                                "Failed to generate dashboard path: {:?}",
41                                e
42                            ))
43                        })?;
44                std::env::set_var(
45                    "FUZZING_DASHBOARD",
46                    dashboard_path.to_string_lossy().to_string(),
47                );
48            }
49        }
50
51        if config.get_regression() {
52            let regression_path = generate_unique_fuzz_filename("regression", &target, "json")
53                .await
54                .map_err(|e| {
55                    Error::Anyhow(anyhow::anyhow!(
56                        "Failed to generate regression path: {:?}",
57                        e
58                    ))
59                })?;
60            std::env::set_var(
61                "FUZZING_REGRESSION",
62                regression_path.to_string_lossy().to_string(),
63            );
64        }
65
66        let coverage_config = config.get_coverage();
67        if coverage_config.get_enable() {
68            self.run_with_coverage(&target, &config, coverage_config, seed, with_exit_code)
69                .await?;
70        } else {
71            self.run_default(&target, seed, with_exit_code).await?;
72        }
73    }
74
75    #[throws]
76    pub async fn run_default(&self, target: &str, seed: Option<String>, with_exit_code: bool) {
77        let mut child = self.spawn_fuzzer(target, HashMap::new(), seed)?;
78        Self::handle_child(&mut child, with_exit_code).await?;
79    }
80
81    #[throws]
82    pub async fn run_with_coverage(
83        &self,
84        target: &str,
85        config: &TridentConfig,
86        coverage_config: CoverageConfig,
87        seed: Option<String>,
88        with_exit_code: bool,
89    ) {
90        if let Err(err) = coverage_config.validate() {
91            throw!(Error::Anyhow(anyhow::anyhow!(err)));
92        }
93
94        let coverage = Coverage::new(
95            &self.get_target_dir()?,
96            target,
97            coverage_config.get_attach_extension(),
98            coverage_config.get_format(),
99            coverage_config.get_loopcount(),
100            config.coverage_server_port(),
101        );
102
103        if coverage.check_llvm_tools_installed().await.is_err() {
104            coverage.prompt_and_install_llvm_tools().await?;
105        }
106
107        coverage.clean().await?;
108
109        let env_vars = self.setup_coverage_env_vars(&coverage, config).await?;
110        let mut child = self.spawn_fuzzer(target, env_vars, seed)?;
111
112        coverage.notify_extension(NotificationType::Setup).await?;
113        Self::handle_child(&mut child, with_exit_code).await?;
114
115        coverage.generate_report().await?;
116    }
117
118    #[throws]
119    async fn setup_coverage_env_vars(
120        &self,
121        coverage: &Coverage,
122        config: &TridentConfig,
123    ) -> HashMap<&str, String> {
124        let mut rustflags = std::env::var("RUSTFLAGS").unwrap_or_default();
125        rustflags.push_str(&coverage.get_rustflags());
126
127        let mut env_vars: HashMap<&str, String> = HashMap::new();
128        env_vars.insert("RUSTFLAGS", rustflags);
129        env_vars.insert("LLVM_PROFILE_FILE", coverage.get_profraw_file());
130        env_vars.insert("CARGO_LLVM_COV_TARGET_DIR", coverage.get_target_dir());
131        env_vars.insert("FUZZER_LOOPCOUNT", coverage.get_loopcount().to_string());
132        env_vars.insert(
133            "COVERAGE_SERVER_PORT",
134            config.coverage_server_port().to_string(),
135        );
136        // We need this to know whether to generate code for profraw file generation
137        env_vars.insert("COLLECT_COVERAGE", "1".to_string());
138
139        env_vars
140    }
141
142    #[throws]
143    fn spawn_fuzzer(
144        &self,
145        target: &str,
146        mut env_vars: HashMap<&str, String>,
147        seed: Option<String>,
148    ) -> tokio::process::Child {
149        if let Some(seed) = seed {
150            // this is just to make sure it will be possible to decode the seed
151            // if it is not a valid hex string, it will panic
152            let _decoded_seed = hex::decode(&seed)
153                .unwrap_or_else(|_| panic!("The seed is not a valid hex string: {}", seed));
154
155            env_vars.insert("TRIDENT_FUZZ_SEED", seed);
156        }
157
158        Command::new("cargo")
159            .envs(env_vars)
160            .arg("run")
161            .arg("--bin")
162            .arg(target)
163            .args(["--profile", "release"])
164            .spawn()?
165    }
166
167    #[throws]
168    pub async fn run_debug(&self, target: String, seed: String) {
169        let config = TridentConfig::new();
170
171        if config.get_metrics() {
172            if config.get_metrics_json() {
173                let json_path = generate_unique_fuzz_filename("fuzzing_metrics", &target, "json")
174                    .await
175                    .map_err(|e| {
176                        Error::Anyhow(anyhow::anyhow!(
177                            "Failed to generate fuzzing metrics path: {:?}",
178                            e
179                        ))
180                    })?;
181                std::env::set_var("FUZZING_JSON", json_path.to_string_lossy().to_string());
182            }
183
184            if config.get_metrics_dashboard() {
185                let dashboard_path =
186                    generate_unique_fuzz_filename("fuzzing_dashboard", &target, "html")
187                        .await
188                        .map_err(|e| {
189                            Error::Anyhow(anyhow::anyhow!(
190                                "Failed to generate dashboard path: {:?}",
191                                e
192                            ))
193                        })?;
194                std::env::set_var(
195                    "FUZZING_DASHBOARD",
196                    dashboard_path.to_string_lossy().to_string(),
197                );
198            }
199        }
200
201        if config.get_regression() {
202            let regression_path = generate_unique_fuzz_filename("regression", &target, "json")
203                .await
204                .map_err(|e| {
205                    Error::Anyhow(anyhow::anyhow!(
206                        "Failed to generate regression path: {:?}",
207                        e
208                    ))
209                })?;
210            std::env::set_var(
211                "FUZZING_REGRESSION",
212                regression_path.to_string_lossy().to_string(),
213            );
214
215            println!("FUZZING_REGRESSION: {}", regression_path.to_string_lossy());
216        }
217
218        let debug_path = generate_unique_fuzz_filename("trident_logs", &seed, "log")
219            .await
220            .map_err(|e| {
221                Error::Anyhow(anyhow::anyhow!(
222                    "Failed to generate debug fuzzing path: {:?}",
223                    e
224                ))
225            })?;
226
227        std::env::set_var(
228            "TRIDENT_FUZZ_DEBUG_PATH",
229            debug_path.to_string_lossy().to_string(),
230        );
231
232        std::env::set_var("TRIDENT_FUZZ_DEBUG", seed);
233
234        let mut child = Command::new("cargo")
235            .arg("run")
236            .arg("--bin")
237            .arg(target)
238            .args(["--profile", "release"])
239            .spawn()?;
240
241        Self::handle_child(&mut child, false).await?;
242    }
243}