Repository: servo/rust-url
Pull request #940 (2024-06-16 15:12:13)

fix panic on `xn--55555577`
This PR fixes a panic discovered by `clusterfuzz` via [`gitoxide` (PR)](https://github.com/Byron/gitoxide/pull/1402).

Details can be found in https://github.com/Byron/gitoxide/issues/1401 (but only once the bug is considered fixed by the fuzzer).

### Notes for the Reviewer

* The fix assumes that this is indeed an invalid punycode sequence, which is why it's made to error.
* The panic would only happen in debug mode, or if compiled with integer overflow checks specifically.
  However, this may indeed be the case for some applications which require this extra-levle of safety.
* *A new patch release would be appreciated* - the fuzzer will keep sending me emails otherwise as it seems to 'bounce' from fixed to not-fixed on this one.



codecov[bot]: ## [Codecov](https://app.codecov.io/gh/servo/rust-url/pull/940?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=servo) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Please [upload](https://docs.codecov.com/docs/codecov-uploader) report for BASE (`main@3d6dbbb`). [Learn more](https://docs.codecov.io/docs/error-reference?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=servo#section-missing-base-commit) about missing BASE report.


<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main     #940   +/-   ##
=======================================
  Coverage        ?   79.83%           
=======================================
  Files           ?       23           
  Lines           ?     4220           
  Branches        ?        0           
=======================================
  Hits            ?     3369           
  Misses          ?      851           
  Partials        ?        0           
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/servo/rust-url/pull/940?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=servo).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=servo).


Patch:
diff --git a/idna/src/punycode.rs b/idna/src/punycode.rs
index 436e40c..6a33088 100644
--- a/idna/src/punycode.rs
+++ b/idna/src/punycode.rs
@@ -17,7 +17,6 @@ use alloc::{string::String, vec::Vec};
 use core::char;
 use core::fmt::Write;
 use core::marker::PhantomData;
-use core::u32;
 
 // Bootstring parameters for Punycode
 const BASE: u32 = 36;
@@ -215,7 +214,7 @@ impl Decoder {
                 if C::EXTERNAL_CALLER && (digit > (u32::MAX - i) / weight) {
                     return Err(()); // Overflow
                 }
-                i += digit * weight;
+                i = i.checked_add(digit * weight).ok_or(())?;
                 let t = if k <= bias {
                     T_MIN
                 } else if k >= bias + T_MAX {
diff --git a/url/src/host.rs b/url/src/host.rs
index 62db3b4..7864b77 100644
--- a/url/src/host.rs
+++ b/url/src/host.rs
@@ -309,7 +309,7 @@ fn parse_ipv4addr(input: &str) -> ParseResult<Ipv4Addr> {
     }
     let mut ipv4 = numbers.pop().expect("a non-empty list of numbers");
     // Equivalent to: ipv4 >= 256 ** (4 − numbers.len())
-    if ipv4 > u32::max_value() >> (8 * numbers.len() as u32) {
+    if ipv4 > u32::MAX >> (8 * numbers.len() as u32) {
         return Err(ParseError::InvalidIpv4Address);
     }
     if numbers.iter().any(|x| *x > 255) {
diff --git a/url/src/parser.rs b/url/src/parser.rs
index 2e3a4f6..8dd054d 100644
--- a/url/src/parser.rs
+++ b/url/src/parser.rs
@@ -1109,7 +1109,7 @@ impl<'a> Parser<'a> {
         while let (Some(c), remaining) = input.split_first() {
             if let Some(digit) = c.to_digit(10) {
                 port = port * 10 + digit;
-                if port > ::std::u16::MAX as u32 {
+                if port > u16::MAX as u32 {
                     return Err(ParseError::InvalidPort);
                 }
                 has_any_digit = true;
@@ -1590,7 +1590,7 @@ pub fn ascii_alpha(ch: char) -> bool {
 
 #[inline]
 pub fn to_u32(i: usize) -> ParseResult<u32> {
-    if i <= ::std::u32::MAX as usize {
+    if i <= u32::MAX as usize {
         Ok(i as u32)
     } else {
         Err(ParseError::Overflow)


Test patch:
diff --git a/idna/tests/unit.rs b/idna/tests/unit.rs
index b73f3b7..d0b2412 100644
--- a/idna/tests/unit.rs
+++ b/idna/tests/unit.rs
@@ -40,6 +40,12 @@ fn test_punycode_prefix_without_length_check() {
     assert!(config.to_ascii("xn--.example.org").is_err());
 }
 
+#[test]
+fn test_punycode_invalid_encoding() {
+    let config = idna::Config::default();
+    assert!(config.to_ascii("xn--55555577").is_err());
+}
+
 // http://www.unicode.org/reports/tr46/#Table_Example_Processing
 #[test]
 fn test_examples() {