1
- #![ deny( clippy:: disallowed_macros, clippy:: expect_used, clippy:: unwrap_used) ]
2
-
3
1
use anyhow:: { Context , Result , bail, ensure} ;
4
2
use clap:: Parser ;
5
3
use std:: ffi:: OsStr ;
@@ -9,6 +7,7 @@ use std::process::{Command, ExitStatus, Stdio};
9
7
use super :: common;
10
8
11
9
const AFL_SRC_PATH : & str = "AFLplusplus" ;
10
+ const AFLPLUSPLUS_URL : & str = "https://github.com/AFLplusplus/AFLplusplus" ;
12
11
13
12
// https://github.com/rust-fuzz/afl.rs/issues/148
14
13
#[ cfg( target_os = "macos" ) ]
@@ -26,60 +25,153 @@ pub struct Args {
26
25
#[ clap( long, help = "Build AFL++ for the default toolchain" ) ]
27
26
pub build : bool ,
28
27
29
- #[ clap( long, help = "Rebuild AFL++ if it was already built" ) ]
28
+ #[ clap(
29
+ long,
30
+ help = "Rebuild AFL++ if it was already built. Note: AFL++ will be built without plugins \
31
+ if `--plugins` is not passed."
32
+ ) ]
30
33
pub force : bool ,
31
34
32
35
#[ clap( long, help = "Enable building of LLVM plugins" ) ]
33
36
pub plugins : bool ,
34
37
38
+ #[ clap(
39
+ long,
40
+ help = "Update to <TAG> instead of the latest stable version" ,
41
+ requires = "update"
42
+ ) ]
43
+ pub tag : Option < String > ,
44
+
45
+ #[ clap(
46
+ long,
47
+ help = "Update AFL++ to the latest stable version (preserving plugins, if applicable)"
48
+ ) ]
49
+ pub update : bool ,
50
+
35
51
#[ clap( long, help = "Show build output" ) ]
36
52
pub verbose : bool ,
37
53
}
38
54
39
55
pub fn config ( args : & Args ) -> Result < ( ) > {
40
56
let archive_file_path = common:: archive_file_path ( ) ?;
41
- if !args. force && archive_file_path. exists ( ) && args. plugins == common:: plugins_installed ( ) ? {
57
+
58
+ if !args. force
59
+ && !args. update
60
+ && archive_file_path. exists ( )
61
+ && args. plugins == common:: plugins_installed ( ) ?
62
+ {
42
63
let version = common:: afl_rustc_version ( ) ?;
43
64
bail ! (
44
65
"AFL LLVM runtime was already built for Rust {version}; run `cargo afl config --build \
45
66
--force` to rebuild it."
46
67
) ;
47
68
}
48
69
49
- let afl_src_dir = Path :: new ( env ! ( "CARGO_MANIFEST_DIR" ) ) . join ( AFL_SRC_PATH ) ;
50
- let afl_src_dir_str = & afl_src_dir. to_string_lossy ( ) ;
51
-
52
- let tempdir = tempfile:: tempdir ( ) . with_context ( || "could not create temporary directory" ) ?;
70
+ // smoelius: If updating and AFL++ was built with plugins before, build with plugins again.
71
+ let args = Args {
72
+ plugins : if args. update {
73
+ common:: plugins_installed ( ) . is_ok_and ( |is_true| is_true)
74
+ } else {
75
+ args. plugins
76
+ } ,
77
+ tag : args. tag . clone ( ) ,
78
+ ..* args
79
+ } ;
53
80
54
- if afl_src_dir. join ( ".git" ) . is_dir ( ) {
55
- let success = Command :: new ( "git" )
56
- . args ( [ "clone" , afl_src_dir_str, & * tempdir. path ( ) . to_string_lossy ( ) ] )
57
- . status ( )
58
- . as_ref ( )
59
- . is_ok_and ( ExitStatus :: success) ;
60
- ensure ! ( success, "could not run 'git'" ) ;
61
- } else {
62
- copy_aflplusplus_submodule ( & tempdir. path ( ) . join ( AFL_SRC_PATH ) ) ?;
81
+ let aflplusplus_dir =
82
+ common:: aflplusplus_dir ( ) . with_context ( || "could not determine AFLplusplus directory" ) ?;
83
+
84
+ // smoelius: The AFLplusplus directory could be in one of three possible states:
85
+ //
86
+ // 1. Nonexistent
87
+ // 2. Initialized with a copy of the AFLplusplus submodule from afl.rs's source tree
88
+ // 3. Cloned from `AFLPLUSPLUS_URL`
89
+ //
90
+ // If we are not updating and the AFLplusplus directory is nonexistent: initialize the directory
91
+ // with a copy of the AFLplusplus submodule from afl.rs's source tree (the `else` case in the
92
+ // next `if` statement).
93
+ //
94
+ // If we are updating and the AFLplusplus directory is a copy of the AFLplusplus submodule from
95
+ // afl.rs's source tree: remove it and create a new directory by cloning AFL++ (the `else` case
96
+ // in `update_to_stable_or_tag`).
97
+ //
98
+ // Finally, if we are updating: check out either `origin/stable` or the tag that was passed.
99
+ if args. update {
100
+ let rev_prev = if is_repo ( & aflplusplus_dir) ? {
101
+ rev ( & aflplusplus_dir) . map ( Some ) ?
102
+ } else {
103
+ None
104
+ } ;
105
+
106
+ update_to_stable_or_tag ( & aflplusplus_dir, args. tag . as_deref ( ) ) ?;
107
+
108
+ let rev_curr = rev ( & aflplusplus_dir) ?;
109
+
110
+ if rev_prev == Some ( rev_curr) && !args. force {
111
+ eprintln ! ( "Nothing to do. Pass `--force` to force rebuilding." ) ;
112
+ return Ok ( ( ) ) ;
113
+ }
114
+ } else if !aflplusplus_dir. join ( ".git" ) . try_exists ( ) ? {
115
+ copy_aflplusplus_submodule ( & aflplusplus_dir) ?;
63
116
}
64
117
65
- let work_dir = tempdir. path ( ) . join ( AFL_SRC_PATH ) ;
66
-
67
- build_afl ( args, & work_dir) ?;
68
- build_afl_llvm_runtime ( args, & work_dir) ?;
118
+ build_afl ( & args, & aflplusplus_dir) ?;
119
+ build_afl_llvm_runtime ( & args, & aflplusplus_dir) ?;
69
120
70
121
if args. plugins {
71
- copy_afl_llvm_plugins ( args, & work_dir ) ?;
122
+ copy_afl_llvm_plugins ( & args, & aflplusplus_dir ) ?;
72
123
}
73
124
74
125
let afl_dir = common:: afl_dir ( ) ?;
75
- let Some ( dir ) = afl_dir. parent ( ) . map ( Path :: to_path_buf ) else {
126
+ let Some ( afl_dir_parent ) = afl_dir. parent ( ) else {
76
127
bail ! ( "could not get afl dir parent" ) ;
77
128
} ;
78
- eprintln ! ( "Artifacts written to {}" , dir . display( ) ) ;
129
+ eprintln ! ( "Artifacts written to {}" , afl_dir_parent . display( ) ) ;
79
130
80
131
Ok ( ( ) )
81
132
}
82
133
134
+ fn update_to_stable_or_tag ( aflplusplus_dir : & Path , tag : Option < & str > ) -> Result < ( ) > {
135
+ if is_repo ( aflplusplus_dir) ? {
136
+ let success = Command :: new ( "git" )
137
+ . arg ( "fetch" )
138
+ . current_dir ( aflplusplus_dir)
139
+ . status ( )
140
+ . as_ref ( )
141
+ . is_ok_and ( ExitStatus :: success) ;
142
+ ensure ! ( success, "could not run 'git fetch'" ) ;
143
+ } else {
144
+ remove_aflplusplus_dir ( aflplusplus_dir) . unwrap_or_default ( ) ;
145
+ let success = Command :: new ( "git" )
146
+ . args ( [
147
+ "clone" ,
148
+ AFLPLUSPLUS_URL ,
149
+ & * aflplusplus_dir. to_string_lossy ( ) ,
150
+ ] )
151
+ . status ( )
152
+ . as_ref ( )
153
+ . is_ok_and ( ExitStatus :: success) ;
154
+ ensure ! ( success, "could not run 'git clone'" ) ;
155
+ }
156
+
157
+ let mut command = Command :: new ( "git" ) ;
158
+ command. arg ( "checkout" ) ;
159
+ if let Some ( tag) = tag {
160
+ command. arg ( tag) ;
161
+ } else {
162
+ command. arg ( "origin/stable" ) ;
163
+ }
164
+ command. current_dir ( aflplusplus_dir) ;
165
+ let success = command. status ( ) . as_ref ( ) . is_ok_and ( ExitStatus :: success) ;
166
+ ensure ! ( success, "could not run 'git checkout'" ) ;
167
+
168
+ Ok ( ( ) )
169
+ }
170
+
171
+ fn remove_aflplusplus_dir ( aflplusplus_dir : & Path ) -> Result < ( ) > {
172
+ std:: fs:: remove_dir_all ( aflplusplus_dir) . map_err ( Into :: into)
173
+ }
174
+
83
175
fn copy_aflplusplus_submodule ( aflplusplus_dir : & Path ) -> Result < ( ) > {
84
176
let afl_src_dir = Path :: new ( env ! ( "CARGO_MANIFEST_DIR" ) ) . join ( AFL_SRC_PATH ) ;
85
177
let afl_src_dir_str = & afl_src_dir. to_string_lossy ( ) ;
@@ -104,6 +196,28 @@ fn copy_aflplusplus_submodule(aflplusplus_dir: &Path) -> Result<()> {
104
196
Ok ( ( ) )
105
197
}
106
198
199
+ // smoelius: `dot_git` will refer to an ASCII text file if it was copied from the AFLplusplus
200
+ // submodule from afl.rs's source tree.
201
+ fn is_repo ( aflplusplus_dir : & Path ) -> Result < bool > {
202
+ let dot_git = aflplusplus_dir. join ( ".git" ) ;
203
+ if dot_git. try_exists ( ) ? {
204
+ Ok ( dot_git. is_dir ( ) )
205
+ } else {
206
+ Ok ( false )
207
+ }
208
+ }
209
+
210
+ fn rev ( dir : & Path ) -> Result < String > {
211
+ let mut command = Command :: new ( "git" ) ;
212
+ command. args ( [ "rev-parse" , "HEAD" ] ) ;
213
+ command. current_dir ( dir) ;
214
+ let output = command
215
+ . output ( )
216
+ . with_context ( || "could not run `git rev-parse`" ) ?;
217
+ ensure ! ( output. status. success( ) , "`git rev-parse` failed" ) ;
218
+ String :: from_utf8 ( output. stdout ) . map_err ( Into :: into)
219
+ }
220
+
107
221
fn build_afl ( args : & Args , work_dir : & Path ) -> Result < ( ) > {
108
222
// if you had already installed cargo-afl previously you **must** clean AFL++
109
223
let afl_dir = common:: afl_dir ( ) ?;
0 commit comments