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 env_vars = HashMap::new();
78        if with_exit_code {
79            env_vars.insert("TRIDENT_WITH_EXIT_CODE", "1".to_string());
80        }
81        let mut child = self.spawn_fuzzer(target, env_vars, seed)?;
82        Self::handle_child(&mut child, with_exit_code).await?;
83    }
84
85    #[throws]
86    pub async fn run_with_coverage(
87        &self,
88        target: &str,
89        config: &TridentConfig,
90        coverage_config: CoverageConfig,
91        seed: Option<String>,
92        with_exit_code: bool,
93    ) {
94        if let Err(err) = coverage_config.validate() {
95            throw!(Error::Anyhow(anyhow::anyhow!(err)));
96        }
97
98        let coverage = Coverage::new(
99            &self.get_target_dir()?,
100            target,
101            coverage_config.get_attach_extension(),
102            coverage_config.get_format(),
103            coverage_config.get_loopcount(),
104            config.coverage_server_port(),
105        );
106
107        if coverage.check_llvm_tools_installed().await.is_err() {
108            coverage.prompt_and_install_llvm_tools().await?;
109        }
110
111        coverage.clean().await?;
112
113        let mut env_vars = self.setup_coverage_env_vars(&coverage, config).await?;
114        if with_exit_code {
115            env_vars.insert("TRIDENT_WITH_EXIT_CODE", "1".to_string());
116        }
117        let mut child = self.spawn_fuzzer(target, env_vars, seed)?;
118
119        coverage.notify_extension(NotificationType::Setup).await?;
120        Self::handle_child(&mut child, with_exit_code).await?;
121
122        coverage.generate_report().await?;
123    }
124
125    #[throws]
126    async fn setup_coverage_env_vars(
127        &self,
128        coverage: &Coverage,
129        config: &TridentConfig,
130    ) -> HashMap<&str, String> {
131        let mut rustflags = std::env::var("RUSTFLAGS").unwrap_or_default();
132        rustflags.push_str(&coverage.get_rustflags());
133
134        let mut env_vars: HashMap<&str, String> = HashMap::new();
135        env_vars.insert("RUSTFLAGS", rustflags);
136        env_vars.insert("LLVM_PROFILE_FILE", coverage.get_profraw_file());
137        env_vars.insert("CARGO_LLVM_COV_TARGET_DIR", coverage.get_target_dir());
138        env_vars.insert("FUZZER_LOOPCOUNT", coverage.get_loopcount().to_string());
139        env_vars.insert(
140            "COVERAGE_SERVER_PORT",
141            config.coverage_server_port().to_string(),
142        );
143        // We need this to know whether to generate code for profraw file generation
144        env_vars.insert("COLLECT_COVERAGE", "1".to_string());
145
146        env_vars
147    }
148
149    #[throws]
150    fn spawn_fuzzer(
151        &self,
152        target: &str,
153        mut env_vars: HashMap<&str, String>,
154        seed: Option<String>,
155    ) -> tokio::process::Child {
156        if let Some(seed) = seed {
157            // this is just to make sure it will be possible to decode the seed
158            // if it is not a valid hex string, it will panic
159            let _decoded_seed = hex::decode(&seed)
160                .unwrap_or_else(|_| panic!("The seed is not a valid hex string: {}", seed));
161
162            env_vars.insert("TRIDENT_FUZZ_SEED", seed);
163        }
164
165        Command::new("cargo")
166            .envs(env_vars)
167            .arg("run")
168            .arg("--bin")
169            .arg(target)
170            .args(["--profile", "release"])
171            .spawn()?
172    }
173
174    #[throws]
175    pub async fn run_debug(&self, target: String, seed: String) {
176        let config = TridentConfig::new();
177
178        if config.get_metrics() {
179            if config.get_metrics_json() {
180                let json_path = generate_unique_fuzz_filename("fuzzing_metrics", &target, "json")
181                    .await
182                    .map_err(|e| {
183                        Error::Anyhow(anyhow::anyhow!(
184                            "Failed to generate fuzzing metrics path: {:?}",
185                            e
186                        ))
187                    })?;
188                std::env::set_var("FUZZING_JSON", json_path.to_string_lossy().to_string());
189            }
190
191            if config.get_metrics_dashboard() {
192                let dashboard_path =
193                    generate_unique_fuzz_filename("fuzzing_dashboard", &target, "html")
194                        .await
195                        .map_err(|e| {
196                            Error::Anyhow(anyhow::anyhow!(
197                                "Failed to generate dashboard path: {:?}",
198                                e
199                            ))
200                        })?;
201                std::env::set_var(
202                    "FUZZING_DASHBOARD",
203                    dashboard_path.to_string_lossy().to_string(),
204                );
205            }
206        }
207
208        if config.get_regression() {
209            let regression_path = generate_unique_fuzz_filename("regression", &target, "json")
210                .await
211                .map_err(|e| {
212                    Error::Anyhow(anyhow::anyhow!(
213                        "Failed to generate regression path: {:?}",
214                        e
215                    ))
216                })?;
217            std::env::set_var(
218                "FUZZING_REGRESSION",
219                regression_path.to_string_lossy().to_string(),
220            );
221
222            println!("FUZZING_REGRESSION: {}", regression_path.to_string_lossy());
223        }
224
225        let debug_path = generate_unique_fuzz_filename("trident_logs", &seed, "log")
226            .await
227            .map_err(|e| {
228                Error::Anyhow(anyhow::anyhow!(
229                    "Failed to generate debug fuzzing path: {:?}",
230                    e
231                ))
232            })?;
233
234        std::env::set_var(
235            "TRIDENT_FUZZ_DEBUG_PATH",
236            debug_path.to_string_lossy().to_string(),
237        );
238
239        std::env::set_var("TRIDENT_FUZZ_DEBUG", seed);
240
241        let mut child = Command::new("cargo")
242            .arg("run")
243            .arg("--bin")
244            .arg(target)
245            .args(["--profile", "release"])
246            .spawn()?;
247
248        Self::handle_child(&mut child, false).await?;
249    }
250}