CVE-2024-39697: Insufficient Information (NVD-CWE-noinfo)
value: phonenumber is a library for parsing, formatting and validating international phone numbers. Since 0.3.4, the phonenumber parsing code may panic due to a panic-guarded out-of-bounds access on the phonenumber string. In a typical deployment of rust-phonenumber, this may get triggered by feeding a maliciously crafted phonenumber, e.g. over the network, specifically strings of the form `+dwPAA;phone-context=AA`, where the "number" part potentially parses as a number larger than 2^56. This vulnerability is fixed in 0.3.6.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-07-09T15:15Z
Repository: https://github.com/whisperfish/rust-phonenumber
Language: Rust
Files changed: src/national_number.rs, src/parser/mod.rs
Commit date: 2023-12-14 15:01:29 +0100
Commit message: Fit NationalNumber in 64 bits

Diff: diff --git a/src/national_number.rs b/src/national_number.rs
index 4739094..97e72c3 100644
--- a/src/national_number.rs
+++ b/src/national_number.rs
@@ -17,11 +17,29 @@ use std::fmt;
 
 /// The national number part of a phone number.
 #[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize, Hash, Debug)]
 pub struct NationalNumber {
     pub(crate) value: u64,
+}
+
+impl NationalNumber {
+    pub fn new(value: u64, zeros: u8) -> Self {
+        // E.164 specifies a maximum of 15 decimals, which corresponds to slightly over 48.9 bits.
+        // 56 bits ought to cut it here.
+        assert!(value < (1 << 56), "number too long");
+        Self {
+            value: ((zeros as u64) << 56) | value,
+        }
+    }
 
+    /// The number without any leading zeroes.
+    pub fn value(&self) -> u64 {
+        self.value & 0x00ffffffffffffff
+    }
+
+    /// The number of leading zeroes.
+    ///
     /// In some countries, the national (significant) number starts with one or
     /// more "0"s without this being a national prefix or trunk code of some
     /// kind.  For example, the leading zero in the national (significant) number
     /// of an Italian phone number indicates the number is a fixed-line number.
     /// There have been plans to migrate fixed-line numbers to start with the
@@ -34,22 +52,12 @@ pub struct NationalNumber {
     /// when dialling internationally, set this flag to true, and also set the
     /// number of leading zeros.
     ///
     /// Clients who use the parsing or conversion functionality of the i18n phone
     /// number libraries will have these fields set if necessary automatically.
-    pub(crate) zeros: u8,
-}
-
-impl NationalNumber {
-    /// The number without any leading zeroes.
-    pub fn value(&self) -> u64 {
-        self.value
-    }
-
-    /// The number of leading zeroes.
     pub fn zeros(&self) -> u8 {
-        self.zeros
+        (self.value >> 56) as u8
     }
 }
 
 impl From<NationalNumber> for u64 {
     fn from(n: NationalNumber) -> u64 {
@@ -57,12 +65,12 @@ impl From<NationalNumber> for u64 {
     }
 }
 
 impl fmt::Display for NationalNumber {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        for _ in 0..self.zeros {
+        for _ in 0..self.zeros() {
             write!(f, "0")?;
         }
 
-        write!(f, "{}", self.value)
+        write!(f, "{}", self.value())
     }
 }
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 658d89d..26a3fc2 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -82,14 +82,14 @@ pub fn parse_with<S: AsRef<str>>(
         code: country::Code {
             value: number.prefix.map(|p| p.parse()).unwrap_or(Ok(0))?,
             source: number.country,
         },
 
-        national: NationalNumber {
-            value: number.national.parse()?,
-            zeros: number.national.chars().take_while(|&c| c == '0').count() as u8,
-        },
+        national: NationalNumber::new(
+            number.national.parse()?,
+            number.national.chars().take_while(|&c| c == '0').count() as u8,
+        ),
 
         extension: number.extension.map(|s| Extension(s.into_owned())),
         carrier: number.carrier.map(|s| Carrier(s.into_owned())),
     })
 }
@@ -107,14 +107,11 @@ mod test {
             code: country::Code {
                 value: 64,
                 source: country::Source::Default,
             },
 
-            national: NationalNumber {
-                value: 33316005,
-                zeros: 0,
-            },
+            national: NationalNumber::new(33316005, 0),
 
             extension: None,
             carrier: None,
         };
 
@@ -198,14 +195,11 @@ mod test {
             code: country::Code {
                 value: 64,
                 source: country::Source::Number,
             },
 
-            national: NationalNumber {
-                value: 64123456,
-                zeros: 0,
-            },
+            national: NationalNumber::new(64123456, 0),
 
             extension: None,
             carrier: None,
         };
 
@@ -219,14 +213,11 @@ mod test {
                 code: country::Code {
                     value: 49,
                     source: country::Source::Default,
                 },
 
-                national: NationalNumber {
-                    value: 30123456,
-                    zeros: 0,
-                },
+                national: NationalNumber::new(30123456, 0),
 
                 extension: None,
                 carrier: None,
             },
             parser::parse(Some(country::DE), "301/23456").unwrap()
@@ -237,14 +228,11 @@ mod test {
                 code: country::Code {
                     value: 81,
                     source: country::Source::Plus,
                 },
 
-                national: NationalNumber {
-                    value: 2345,
-                    zeros: 0,
-                },
+                national: NationalNumber::new(2345, 0,),
 
                 extension: None,
                 carrier: None,
             },
             parser::parse(Some(country::JP), "+81 *2345").unwrap()
@@ -255,14 +243,11 @@ mod test {
                 code: country::Code {
                     value: 64,
                     source: country::Source::Default,
                 },
 
-                national: NationalNumber {
-                    value: 12,
-                    zeros: 0,
-                },
+                national: NationalNumber::new(12, 0,),
 
                 extension: None,
                 carrier: None,
             },
             parser::parse(Some(country::NZ), "12").unwrap()
@@ -273,14 +258,11 @@ mod test {
                 code: country::Code {
                     value: 55,
                     source: country::Source::Default,
                 },
 
-                national: NationalNumber {
-                    value: 3121286979,
-                    zeros: 0,
-                },
+                national: NationalNumber::new(3121286979, 0),
 
                 extension: None,
                 carrier: Some("12".into()),
             },
             parser::parse(Some(country::BR), "012 3121286979").unwrap()

Vulnerable code:     pub(crate) zeros: u8,
}

impl NationalNumber {
    /// The number without any leading zeroes.
    pub fn value(&self) -> u64 {
        self.value
    }

    /// The number of leading zeroes.
        self.zeros
        for _ in 0..self.zeros {
        write!(f, "{}", self.value)
        national: NationalNumber {
            value: number.national.parse()?,
            zeros: number.national.chars().take_while(|&c| c == '0').count() as u8,
        },
            national: NationalNumber {
                value: 33316005,
                zeros: 0,
            },
            national: NationalNumber {
                value: 64123456,
                zeros: 0,
            },
                national: NationalNumber {
                    value: 30123456,
                    zeros: 0,
                },
                national: NationalNumber {
                    value: 2345,
                    zeros: 0,
                },
                national: NationalNumber {
                    value: 12,
                    zeros: 0,
                },
                national: NationalNumber {
                    value: 3121286979,
                    zeros: 0,
                },
Fixed code: }

impl NationalNumber {
    pub fn new(value: u64, zeros: u8) -> Self {
        // E.164 specifies a maximum of 15 decimals, which corresponds to slightly over 48.9 bits.
        // 56 bits ought to cut it here.
        assert!(value < (1 << 56), "number too long");
        Self {
            value: ((zeros as u64) << 56) | value,
        }
    }
    /// The number without any leading zeroes.
    pub fn value(&self) -> u64 {
        self.value & 0x00ffffffffffffff
    }

    /// The number of leading zeroes.
    ///
        (self.value >> 56) as u8
        for _ in 0..self.zeros() {
        write!(f, "{}", self.value())
        national: NationalNumber::new(
            number.national.parse()?,
            number.national.chars().take_while(|&c| c == '0').count() as u8,
        ),
            national: NationalNumber::new(33316005, 0),
            national: NationalNumber::new(64123456, 0),
                national: NationalNumber::new(30123456, 0),
                national: NationalNumber::new(2345, 0,),
                national: NationalNumber::new(12, 0,),
                national: NationalNumber::new(3121286979, 0),