Skip to content

Commit d2c04d0

Browse files
authored
Merge pull request #25 from epi052/add-request-to-response-trait
responses now have a `.request()` method to access the request that generated the response
2 parents cc2fe4d + 62f60b0 commit d2c04d0

File tree

19 files changed

+232
-231
lines changed

19 files changed

+232
-231
lines changed

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "feroxfuzz"
3-
version = "1.0.0-rc.10"
3+
version = "1.0.0-rc.11"
44
edition = "2021"
55
authors = ["Ben 'epi' Risher (@epi052)"]
66
license = "Apache-2.0"
@@ -47,7 +47,7 @@ tokio = { version = "1.20", optional = true, features = [
4747
num = { version = "0.4" }
4848
cfg-if = { version = "1.0" }
4949
dyn-clone = { version = "1.0.9" }
50-
libafl = { version = "0.9.0", default-features = false, features = ["std"] }
50+
libafl = { version = "0.10.1", default-features = false, features = ["std"] }
5151
url = { version = "2.2", features = ["serde"] }
5252
## optional
5353
serde = { version = "1.0", optional = true, features = ["derive", "rc"] }
@@ -63,9 +63,9 @@ regex = { version = "1.6" }
6363
serde_regex = { version = "1.1.0" }
6464
lazy_static = { version = "1.4" }
6565
futures = { version = "0.3", optional = true }
66-
base64 = { version = "0.13.1", optional = true }
66+
base64 = { version = "0.21.2", optional = true }
6767
hex = { version = "0.4.3", optional = true }
68-
flume = { version = "0.10.14" }
68+
flume = { version = "0.11.0" }
6969

7070
[dev-dependencies]
7171
http = { version = "0.2" }

Makefile.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
dependencies = ["upgrade-deps", "update"]
88

99
[tasks.check]
10-
dependencies = ["clippy", "fmt", "test-lib", "test-doc", "doc"]
10+
dependencies = ["clippy", "fmt", "test-lib", "test-doc", "doc", "semver"]
1111

1212
[tasks.test]
1313
clear = true
@@ -56,3 +56,9 @@ clear = true
5656
script = """
5757
cargo nextest run --all-features --retries 10 --lib "${@}"
5858
"""
59+
60+
[tasks.semver]
61+
clear = true
62+
script = """
63+
cargo semver-checks check-release
64+
"""

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ The easiest way to get started is to include FeroxFuzz in your project's `Cargo.
7171

7272
```toml
7373
[dependencies]
74-
feroxfuzz = { version = "1.0.0-rc.10" }
74+
feroxfuzz = { version = "1.0.0-rc.11" }
7575
```
7676

7777
## Docs

src/client/async_client.rs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,7 @@ impl AsyncRequests for AsyncClient {
109109
let parsed_version = parse_version(request.version())?;
110110

111111
// build out the reqwest::Request from our mutated feroxfuzz::Request
112-
113-
let request_id = request.id;
114-
let request_method = request.method().as_str()?.to_string();
115-
116-
let reqwest_request = self.build_request(parsed_version, request)?;
112+
let reqwest_request = self.build_request(parsed_version, request.clone())?;
117113

118114
// start timer for the request
119115
let now = Instant::now();
@@ -126,13 +122,9 @@ impl AsyncRequests for AsyncClient {
126122
.map_err(reqwest_to_ferox_error)?;
127123

128124
// build the AsyncResponse, the await is for reqwest's asynchronous read of the response body
129-
let response = AsyncResponse::try_from_reqwest_response(
130-
request_id,
131-
request_method,
132-
reqwest_response,
133-
now.elapsed(),
134-
)
135-
.await?;
125+
let response =
126+
AsyncResponse::try_from_reqwest_response(request, reqwest_response, now.elapsed())
127+
.await?;
136128

137129
Ok(response)
138130
}

src/client/blocking_client.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,8 @@ impl BlockingRequests for BlockingClient {
7878
// enum as its input type for that part of the request
7979
let parsed_version = parse_version(request.version())?;
8080

81-
let request_id = request.id;
82-
let request_method = request.method().as_str()?.to_string();
83-
8481
// build out the reqwest Request from our mutated Request
85-
let reqwest_request = self.build_request(parsed_version, request)?;
82+
let reqwest_request = self.build_request(parsed_version, request.clone())?;
8683

8784
// start timer for the request
8885
let now = Instant::now();
@@ -94,12 +91,8 @@ impl BlockingRequests for BlockingClient {
9491
.map_err(reqwest_to_ferox_error)?;
9592

9693
// build the AsyncResponse, the await is for reqwest's asynchronous read of the response body
97-
let response = BlockingResponse::try_from_reqwest_response(
98-
request_id,
99-
request_method,
100-
reqwest_response,
101-
now.elapsed(),
102-
)?;
94+
let response =
95+
BlockingResponse::try_from_reqwest_response(request, reqwest_response, now.elapsed())?;
10396

10497
Ok(response)
10598
}

src/deciders/length.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use serde::{Deserialize, Serialize};
2424
/// ```
2525
/// # use http::response;
2626
/// # use feroxfuzz::responses::{Response, AsyncResponse};
27-
/// # use feroxfuzz::requests::RequestId;
27+
/// # use feroxfuzz::requests::Request;
2828
/// # use feroxfuzz::error::FeroxFuzzError;
2929
/// # use tokio_test;
3030
/// # use std::time::Duration;
@@ -43,9 +43,8 @@ use serde::{Deserialize, Serialize};
4343
/// # tokio_test::block_on(async {
4444
/// // for testing; normally a Response comes as a result of a sent request
4545
/// let reqwest_response = http::response::Builder::new().body("0123456789").unwrap();
46-
/// let id = RequestId::new(0);
4746
/// let elapsed = Duration::from_secs(1);
48-
/// let response = AsyncResponse::try_from_reqwest_response(id, String::from("GET"), reqwest_response.into(), elapsed).await?;
47+
/// let response = AsyncResponse::try_from_reqwest_response(Request::default(), reqwest_response.into(), elapsed).await?;
4948
///
5049
/// // also not relevant to the current example, but needed to make the call to .post_send_hook
5150
/// let corpus = Wordlist::with_words(["a", "b", "c"]).name("chars").build();

src/deciders/regex.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,13 @@ where
141141
/// # use feroxfuzz::deciders::{ResponseRegexDecider, DeciderHooks, LogicOperation, Deciders};
142142
/// # use feroxfuzz::observers::ResponseObserver;
143143
/// # use feroxfuzz::responses::BlockingResponse;
144-
/// # use feroxfuzz::requests::{Request, RequestId};
144+
/// # use feroxfuzz::requests::Request;
145145
/// # use regex::bytes::Regex;
146146
/// # fn main() -> Result<(), FeroxFuzzError> {
147147
/// // for testing; normally a Response comes as a result of a sent request
148148
/// let reqwest_response = http::response::Builder::new().status(200).body("XyZDeRpZyX").unwrap();
149-
/// let id = RequestId::new(0);
150149
/// let elapsed = Duration::from_secs(1);
151-
/// let response = BlockingResponse::try_from_reqwest_response(id, String::from("GET"), reqwest_response.into(), elapsed)?;
150+
/// let response = BlockingResponse::try_from_reqwest_response(Request::default(), reqwest_response.into(), elapsed)?;
152151
///
153152
/// // not relevant to the current example, but needed to make the call to .post_send_hook
154153
/// let mut state = SharedState::with_corpus(RangeCorpus::with_stop(10).name("corpus").build()?);

src/deciders/status_code.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use serde::{Deserialize, Serialize};
2424
/// ```
2525
/// # use http::response;
2626
/// # use feroxfuzz::responses::{Response, AsyncResponse};
27-
/// # use feroxfuzz::requests::RequestId;
27+
/// # use feroxfuzz::requests::Request;
2828
/// # use feroxfuzz::error::FeroxFuzzError;
2929
/// # use tokio_test;
3030
/// # use std::time::Duration;
@@ -43,9 +43,8 @@ use serde::{Deserialize, Serialize};
4343
/// # tokio_test::block_on(async {
4444
/// // for testing; normally a Response comes as a result of a sent request
4545
/// let reqwest_response = http::response::Builder::new().status(200).body("").unwrap();
46-
/// let id = RequestId::new(0);
4746
/// let elapsed = Duration::from_secs(1);
48-
/// let response = AsyncResponse::try_from_reqwest_response(id, String::from("GET"), reqwest_response.into(), elapsed).await?;
47+
/// let response = AsyncResponse::try_from_reqwest_response(Request::default(), reqwest_response.into(), elapsed).await?;
4948
///
5049
/// // also not relevant to the current example, but needed to make the call to .post_send_hook
5150
/// let corpus = Wordlist::with_words(["a", "b", "c"]).name("chars").build();

src/fuzzers/async_fuzzer.rs

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::events::{
2222
StopFuzzing,
2323
};
2424
use crate::mutators::Mutators;
25-
use crate::observers::Observers;
25+
use crate::observers::{Observers, ResponseObserver};
2626
use crate::processors::Processors;
2727
use crate::requests::Request;
2828
use crate::responses::{AsyncResponse, Response};
@@ -51,14 +51,6 @@ const NUM_POST_PROCESSORS: usize = 6;
5151
/// initial `Result` that is returned from the `tokio::spawn` call
5252
static mut STOP_FUZZING_FLAG: bool = false;
5353

54-
/// internal type used to pass a single object from the `tokio::spawn`
55-
/// call to the `FuturesUnordered` stream
56-
#[derive(Debug, Clone)]
57-
struct RequestFuture {
58-
request: Request,
59-
response: AsyncResponse,
60-
}
61-
6254
/// A fuzzer that sends requests asynchronously
6355
#[derive(Clone, Debug, Default)]
6456
pub struct AsyncFuzzer<A, D, M, O, P, S>
@@ -425,8 +417,6 @@ where
425417
// spawn a new task to send the request, and when received, send the response
426418
// across the mpsc channel to the second/post-send loop
427419
let sent = tx.send(tokio::spawn(async move {
428-
let cloned_request = mutated_request.clone();
429-
430420
// send the request, and await the response
431421
let result = cloned_client.send(mutated_request).await;
432422

@@ -436,17 +426,7 @@ where
436426
// for reference: the permit is acquired at the top of the loop
437427
drop(permit);
438428

439-
match result {
440-
// if the request was successful, return the response
441-
// and the request that generated it
442-
Ok(response) => Ok(RequestFuture {
443-
response,
444-
request: cloned_request,
445-
}),
446-
// otherwise, allow the error to bubble up to the processing
447-
// loop
448-
Err(err) => Err(err),
449-
}
429+
result
450430
}));
451431

452432
// UnboundedChannel::send can only error if the receiver has called close()
@@ -529,7 +509,7 @@ async fn process_responses<D, O, P>(
529509
mut observers: O,
530510
mut processors: P,
531511
post_send_logic: LogicOperation,
532-
receiver: flume::Receiver<JoinHandle<Result<RequestFuture, FeroxFuzzError>>>,
512+
receiver: flume::Receiver<JoinHandle<Result<AsyncResponse, FeroxFuzzError>>>,
533513
) -> Result<(), FeroxFuzzError>
534514
where
535515
D: Deciders<O, AsyncResponse> + Send + Clone,
@@ -539,7 +519,7 @@ where
539519
// second loop handles responses
540520
//
541521
// outer loop awaits the actual response, which is a double-nested Result
542-
// Result<Result<RequestFuture, FeroxFuzzError>, tokio::task::JoinError>
522+
// Result<Result<AsyncResponse, FeroxFuzzError>, tokio::task::JoinError>
543523
tracing::debug!("entering the response processing loop...");
544524

545525
while let Ok(handle) = receiver.recv_async().await {
@@ -563,7 +543,7 @@ where
563543
continue;
564544
};
565545

566-
let Ok(request_future) = task_result else {
546+
let Ok(response) = task_result else {
567547
let error = task_result.err().unwrap();
568548

569549
// feroxfuzz::client::Client::send returned an error, which is a client error
@@ -574,13 +554,25 @@ where
574554
continue;
575555
};
576556

577-
// unpack the request_future into its request and response parts for convenience
578-
let (request, response) = (request_future.request, request_future.response);
579-
580-
let request_id = response.id(); // grab the id; only used for logging
581-
582557
observers.call_post_send_hooks(response);
583558

559+
// at this point, we still need a reference to the request
560+
//
561+
// the response observer takes ownership of the response, so we can grab a
562+
// reference to the response observer and then extract out the request
563+
// from the observer's reference to the response. this is a rather
564+
// convoluted way of avoiding an unnecessary clone while not having to
565+
// rework a bunch of internal implementation details
566+
let response_observer = observers
567+
.match_name::<ResponseObserver<AsyncResponse>>("ResponseObserver")
568+
// reasonable to assume one exists, if not, we can figure it out then
569+
// if someone comes along with a use-case for not using one, we can
570+
// figure it out then
571+
.unwrap();
572+
573+
let request = response_observer.request();
574+
let request_id = request.id();
575+
584576
let decision = deciders.call_post_send_hooks(&state, &observers, None, post_send_logic);
585577

586578
if state.update(&observers, decision.as_ref()).is_err() {
@@ -597,7 +589,7 @@ where
597589
Some(Action::AddToCorpus(name, corpus_item_type, flow_control)) => {
598590
match corpus_item_type {
599591
CorpusItemType::Request => {
600-
if let Err(err) = state.add_request_fields_to_corpus(&name, &request) {
592+
if let Err(err) = state.add_request_fields_to_corpus(&name, request) {
601593
warn!("Could not add {:?} to corpus[{name}]: {:?}", &request, err);
602594
}
603595
}

src/mutators/afl.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ use crate::{atomic_load, AsBytes};
1919

2020
use libafl::bolts::rands::Rand;
2121
use libafl::inputs::HasBytesVec;
22-
use libafl::mutators::mutations::{buffer_copy, buffer_self_copy};
2322
use libafl::state::{HasMaxSize, HasRand};
2423
#[cfg(feature = "serde")]
2524
use serde::{
@@ -36,6 +35,40 @@ pub use libafl::mutators::mutations::{
3635
DwordInterestingMutator, QwordAddMutator, WordAddMutator, WordInterestingMutator,
3736
};
3837

38+
// note: the following two functions are from libafl, they were a part of the public api
39+
// and then were later made private. there wasn't a public replacement, so in the interest
40+
// of time, they're copied here
41+
42+
/// Mem move in the own vec
43+
#[inline]
44+
pub fn buffer_self_copy<T>(data: &mut [T], from: usize, to: usize, len: usize) {
45+
debug_assert!(!data.is_empty());
46+
debug_assert!(from + len <= data.len());
47+
debug_assert!(to + len <= data.len());
48+
if len != 0 && from != to {
49+
let ptr = data.as_mut_ptr();
50+
unsafe {
51+
core::ptr::copy(ptr.add(from), ptr.add(to), len);
52+
}
53+
}
54+
}
55+
56+
/// Mem move between vecs
57+
#[inline]
58+
pub fn buffer_copy<T>(dst: &mut [T], src: &[T], from: usize, to: usize, len: usize) {
59+
debug_assert!(!dst.is_empty());
60+
debug_assert!(!src.is_empty());
61+
debug_assert!(from + len <= src.len());
62+
debug_assert!(to + len <= dst.len());
63+
let dst_ptr = dst.as_mut_ptr();
64+
let src_ptr = src.as_ptr();
65+
if len != 0 {
66+
unsafe {
67+
core::ptr::copy(src_ptr.add(from), dst_ptr.add(to), len);
68+
}
69+
}
70+
}
71+
3972
/// An enum wrapper for libafl mutators that facilitates static dispatch
4073
#[derive(Debug)]
4174
#[non_exhaustive]
@@ -601,6 +634,7 @@ impl Mutator for CrossoverInsertMutator {
601634

602635
// perform the actual mutation
603636
input.bytes_mut().resize(size + len, 0);
637+
604638
buffer_self_copy(input.bytes_mut(), to, to + len, size - to);
605639
buffer_copy(input.bytes_mut(), other_entry.as_bytes(), from, to, len);
606640
}

0 commit comments

Comments
 (0)