Skip to content

Commit 60126d7

Browse files
committed
fix: denial of service attack by passing a URL with a very long host.
We now check for certain size limits and prevent passing long URLs to the `url` crate.
1 parent a12e4a8 commit 60126d7

File tree

4 files changed

+33
-2
lines changed

4 files changed

+33
-2
lines changed

gix-url/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ pub fn parse(input: &BStr) -> Result<Url, parse::Error> {
3535
InputScheme::Url { protocol_end } if input[..protocol_end].eq_ignore_ascii_case(b"file") => {
3636
parse::file_url(input, protocol_end)
3737
}
38-
InputScheme::Url { .. } => parse::url(input),
38+
InputScheme::Url { protocol_end } => parse::url(input, protocol_end),
3939
InputScheme::Scp { colon } => parse::scp(input, colon),
4040
}
4141
}

gix-url/src/parse.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ pub enum Error {
1919
kind: UrlKind,
2020
source: url::ParseError,
2121
},
22+
23+
#[error("The host portion of the following URL is too long ({} bytes, {len} bytes total): {truncated_url:?}", truncated_url.len())]
24+
TooLong { truncated_url: BString, len: usize },
2225
#[error("{} \"{url}\" does not specify a path to a repository", kind.as_str())]
2326
MissingRepositoryPath { url: BString, kind: UrlKind },
2427
#[error("URL {url:?} is relative which is not allowed in this context")]
@@ -79,7 +82,17 @@ pub(crate) fn find_scheme(input: &BStr) -> InputScheme {
7982
InputScheme::Local
8083
}
8184

82-
pub(crate) fn url(input: &BStr) -> Result<crate::Url, Error> {
85+
pub(crate) fn url(input: &BStr, protocol_end: usize) -> Result<crate::Url, Error> {
86+
const MAX_LEN: usize = 1024;
87+
let bytes_to_path = input[protocol_end + "://".len()..]
88+
.find(b"/")
89+
.unwrap_or(input.len() - protocol_end);
90+
if bytes_to_path > MAX_LEN {
91+
return Err(Error::TooLong {
92+
truncated_url: input[..(protocol_end + "://".len() + MAX_LEN).min(input.len())].into(),
93+
len: input.len(),
94+
});
95+
}
8396
let (input, url) = input_to_utf8_and_url(input, UrlKind::Url)?;
8497
let scheme = url.scheme().into();
8598

gix-url/tests/fixtures/fuzzed/very-long.url

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

gix-url/tests/parse/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use bstr::{BStr, ByteSlice};
22
use gix_url::{testing::TestUrlExtension, Scheme};
3+
use std::path::Path;
4+
use std::time::Duration;
35

46
fn assert_url(url: &str, expected: gix_url::Url) -> Result<gix_url::Url, crate::Error> {
57
let actual = gix_url::parse(url.into())?;
@@ -134,3 +136,18 @@ mod unknown {
134136
)
135137
}
136138
}
139+
140+
#[test]
141+
fn fuzzed() {
142+
let base = Path::new("tests").join("fixtures").join("fuzzed");
143+
let location = base.join(Path::new("very-long").with_extension("url"));
144+
let url = std::fs::read(&location).unwrap();
145+
let start = std::time::Instant::now();
146+
gix_url::parse(url.as_bstr()).ok();
147+
assert!(
148+
start.elapsed() < Duration::from_millis(100),
149+
"URL at '{}' parsed too slowly, took {:.00}s",
150+
location.display(),
151+
start.elapsed().as_secs_f32()
152+
)
153+
}

0 commit comments

Comments
 (0)