Skip to content

Commit eeb65e0

Browse files
committed
Add url fuzz target
1 parent 08ab906 commit eeb65e0

4 files changed

Lines changed: 85 additions & 2 deletions

File tree

fuzz/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,9 @@ name = "uri_deserialize_pjuri"
2828
path = "fuzz_targets/uri/deserialize_pjuri.rs"
2929
doc = false
3030
bench = false
31+
32+
[[bin]]
33+
name = "url_decode_url"
34+
path = "fuzz_targets/url/decode_url.rs"
35+
doc = false
36+
bench = false

fuzz/cycle.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
# Continuously cycle over fuzz targets running each for 1 hour.
44
# It uses chrt SCHED_IDLE so that other process takes priority.
5+
# A number of concurrent forks can be applied for parallelization. Be sure to leave one or two available CPUs open for the OS.
56
#
67
# For cargo-fuzz usage see https://github.com/rust-fuzz/cargo-fuzz?tab=readme-ov-file#usage
78

89
set -euo pipefail
910

11+
FORKS=${1:-1}
1012
REPO_DIR=$(git rev-parse --show-toplevel)
1113
# can't find the file because of the ENV var
1214
# shellcheck source=/dev/null
@@ -17,7 +19,7 @@ while :; do
1719
targetName=$(targetFileToName "$targetFile")
1820
echo "Fuzzing target $targetName ($targetFile)"
1921
# fuzz for one hour
20-
cargo +nightly fuzz run "$targetName" -- -max_total_time=3600
22+
cargo +nightly fuzz run "$targetName" -- -max_total_time=3600 -fork="$FORKS"
2123
# minimize the corpus
2224
cargo +nightly fuzz cmin "$targetName"
2325
done

fuzz/fuzz.sh

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
#!/usr/bin/env bash
22

33
# This script is used to briefly fuzz every target when no target is provided. Otherwise, it will briefly fuzz the provided target
4+
# When fuzzing with a specific target a number of concurrent forks can be applied. Be sure to leave one or two available CPUs open for the OS.
45

56
set -euo pipefail
67

78
TARGET=""
9+
FORKS=1
810

911
if [[ $# -gt 0 ]]; then
1012
TARGET="$1"
1113
shift
1214
fi
1315

16+
if [[ $# -gt 0 ]]; then
17+
FORKS="$1"
18+
shift
19+
fi
20+
1421
REPO_DIR=$(git rev-parse --show-toplevel)
1522

1623
# can't find the file because of the ENV var
@@ -26,5 +33,5 @@ fi
2633
for targetFile in $targetFiles; do
2734
targetName=$(targetFileToName "$targetFile")
2835
echo "Fuzzing target $targetName ($targetFile)"
29-
cargo fuzz run "$targetName" -- -max_total_time=30
36+
cargo fuzz run "$targetName" -- -max_total_time=30 -fork="$FORKS"
3037
done
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#![no_main]
2+
3+
use std::str;
4+
5+
use libfuzzer_sys::fuzz_target;
6+
// Adjust this path to wherever your Url module lives in your crate.
7+
use payjoin::Url;
8+
9+
fn do_test(data: &[u8]) {
10+
let Ok(s) = str::from_utf8(data) else { return };
11+
12+
let Ok(mut url) = Url::parse(s) else { return };
13+
14+
let _ = url.scheme();
15+
let _ = url.domain();
16+
let _ = url.port();
17+
let _ = url.path();
18+
let _ = url.query();
19+
let _ = url.fragment();
20+
let _ = url.as_str();
21+
let _ = url.to_string();
22+
if let Some(segs) = url.path_segments() {
23+
let _ = segs.collect::<Vec<_>>();
24+
}
25+
26+
// Cross-check IPv4/IPv6 parsing against std::net
27+
let host_str = url.host_str();
28+
if let Ok(std_addr) = host_str.parse::<std::net::Ipv4Addr>() {
29+
assert!(url.domain().is_none(), "domain() must be None for IPv4 host");
30+
let _ = std_addr.octets();
31+
}
32+
let bracketed = host_str.trim_start_matches('[').trim_end_matches(']');
33+
if let Ok(std_addr) = bracketed.parse::<std::net::Ipv6Addr>() {
34+
assert!(url.domain().is_none(), "domain() must be None for IPv6 host");
35+
let _ = std_addr.segments();
36+
}
37+
38+
let raw = url.as_str().to_owned();
39+
if let Ok(reparsed) = Url::parse(&raw) {
40+
assert_eq!(
41+
reparsed.as_str(),
42+
raw,
43+
"round-trip mismatch: first={raw:?} second={:?}",
44+
reparsed.as_str()
45+
);
46+
}
47+
48+
url.set_port(Some(8080));
49+
url.set_port(None);
50+
url.set_fragment(Some("fuzz"));
51+
url.set_fragment(None);
52+
url.query_pairs_mut().append_pair("k", "v");
53+
url.clear_query();
54+
url.query_pairs_mut().append_pair("fuzz_key", "fuzz_val");
55+
56+
if let Some(mut segs) = url.path_segments_mut() {
57+
segs.push("fuzz_segment");
58+
}
59+
60+
let _ = url.join("relative/path");
61+
let _ = url.join("/absolute/path");
62+
let _ = url.join("../dotdot");
63+
let _ = url.join("https://other.example.com/new");
64+
}
65+
66+
fuzz_target!(|data| {
67+
do_test(data);
68+
});

0 commit comments

Comments
 (0)