The issue is that the `url` crate can be fooled by long URLs into parsing way-too-long hosts inefficiently enough to allow DoS attacks by slowing it down. Some predicate was implemented to detect such URLs before passing them to `url`, but unfortunately a somewhat valid looking URL could be crafted to bypass that check and still run into the `url` parser, which seemed to have no problem with the format of the URL. Testing showed that this is a very special case, which is now handled as well. We hope that one day `url` will handle this all by itself.
Unified · Split
| @@ -85,7 +85,9 @@ pub(crate) fn find_scheme(input: &BStr) -> InputScheme { · 84 unmodified lines | ||
| 85 | 85 | pub(crate) fn url(input: &BStr, protocol_end: usize) -> Result<crate::Url, Error> { |
| 86 | 86 | const MAX_LEN: usize = 1024; |
| 87 | 87 | let bytes_to_path = input[protocol_end + "://".len()..] |
| 88 | − .find(b"/") | |
| 88 | + .iter() | |
| 89 | + .skip_while(|b| **b == b'/') | |
| 90 | + .position(|b| *b == b'/') | |
| 89 | 91 | .unwrap_or(input.len() - protocol_end); |
| 90 | 92 | if bytes_to_path > MAX_LEN { |
| 91 | 93 | return Err(Error::TooLong { |
Binary file not shown.
| @@ -139,15 +139,17 @@ mod unknown { · 138 unmodified lines | ||
| 139 | 139 | |
| 140 | 140 | #[test] |
| 141 | 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 | − ) | |
| 142 | + for name in ["very-long", "very-long2"] { | |
| 143 | + let base = Path::new("tests").join("fixtures").join("fuzzed"); | |
| 144 | + let location = base.join(Path::new(name).with_extension("url")); | |
| 145 | + let url = std::fs::read(&location).unwrap(); | |
| 146 | + let start = std::time::Instant::now(); | |
| 147 | + gix_url::parse(url.as_bstr()).ok(); | |
| 148 | + assert!( | |
| 149 | + start.elapsed() < Duration::from_millis(100), | |
| 150 | + "URL at '{}' parsed too slowly, took {:.00}s", | |
| 151 | + location.display(), | |
| 152 | + start.elapsed().as_secs_f32() | |
| 153 | + ) | |
| 154 | + } | |
| 153 | 155 | } |