Repository: ergoplatform/sigma-rust (language: rust)

Issue:
RUSTSEC-2024-0421: `idna` accepts Punycode labels that do not produce any non-ASCII when decoded

> `idna` accepts Punycode labels that do not produce any non-ASCII when decoded

| Details             |                                                |
| ------------------- | ---------------------------------------------- |
| Package             | `idna`                      |
| Version             | `0.2.3`                   |
| URL                 | [https://bugzilla.mozilla.org/show_bug.cgi?id=1887898](https://bugzilla.mozilla.org/show_bug.cgi?id=1887898) |
| Date                | 2024-12-09                         |
| Patched versions    | `>=1.0.0`                  |

`idna` 0.5.0 and earlier accepts Punycode labels that do not produce any non-ASCII output, which means that either ASCII labels or the empty root label can be masked such that they appear unequal without IDNA processing or when processed with a different implementation and equal when processed with `idna` 0.5.0 or earlier.

Concretely, `example.org` and `xn--example-.org` become equal after processing by `idna` 0.5.0 or earlier. Also, `example.org.xn--` and `example.org.` become equal after processing by `idna` 0.5.0 or earlier.

In applications using `idna` (but not in `idna` itself) this may be able to lead to privilege escalation when host name comparison is part of a privilege check and the behavior is combined with a client that resolves domains with such labels instead of treating them as errors that preclude DNS resolution / URL fetching and with the attacker managing to introduce a DNS entry (and TLS certificate) for an `xn--`-masked name that turns into the name of the target when processed by `idna` 0.5.0 or earlier.

## Remedy

Upgrade to `idna` 1.0.3 or later, if depending on `idna` directly, or to `url` 2.5.4 or later, if depending on `idna` via `url`. (This issue was fixed in `idna` 1.0.0, but versions earlier than 1.0.3 are not recommended for other reasons.)

When upgrading, please take a moment to read about [alternative Unicode back ends for `idna`](https://docs.rs/crate/idna_adapter/latest).

If you are using Rust earlier than 1.81 in combination with SQLx 0.8.2 or earlier, please also read an [issue](https://github.com/servo/rust-url/issues/992) about combining them with `url` 2.5.4 and `idna` 1.0.3.

## Additional information

This issue resulted from `idna` 0.5.0 and earlier implementing the UTS 46 specification literally on this point and the specification having this bug. The specification bug has been fixed in [revision 33 of UTS 46](https://www.unicode.org/reports/tr46/tr46-33.html#Modifications).

## Acknowledgements

Thanks to kageshiron for recognizing the security implications of this behavior.

See [advisory page](https://rustsec.org/advisories/RUSTSEC-2024-0421.html) for additional details.



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 918ba2d32..049e9e324 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -27,8 +27,9 @@ jobs:
       - name: Build ergo-lib
         uses: actions-rs/cargo@v1
         with:
+          use-cross: true
           command: rustc
-          args: --no-default-features --manifest-path ergo-lib/Cargo.toml --crate-type rlib
+          args: --target thumbv7m-none-eabi --no-default-features --manifest-path ergo-lib/Cargo.toml --crate-type rlib
 
   test:
     name: Tests on ${{ matrix.os }}
@@ -224,7 +225,7 @@ jobs:
           - 9053:9053
 
     steps:
-      - uses: fwal/setup-swift@v1
+      - uses: fwal/setup-swift@v2
       - name: checkout
         uses: actions/checkout@v2
 
@@ -253,10 +254,15 @@ jobs:
 
   android_tests:
     name: Test JNI(Android) bindings
-    runs-on: macos-12
+    runs-on: ubuntu-latest
     steps:
       - name: checkout
         uses: actions/checkout@v2
+      - name: Enable KVM
+        run: |
+          echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
+          sudo udevadm control --reload-rules
+          sudo udevadm trigger --name-match=kvm
 
       - uses: actions-rs/toolchain@v1
         with:
diff --git a/Cargo.toml b/Cargo.toml
index 7c20d043f..4731738d0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -83,7 +83,7 @@ tokio = { version = "1.15.0", features = ["full"] }
 tokio-stream = { version = "0.1.8", features = ["sync", "time"] }
 tokio-util = { version = "0.6.9", features = ["codec"] }
 bounded-integer = { version = "^0.5", features = ["types"] }
-url = "~2.2"
+url = "2.5.4"
 getrandom = { version = "0.2.7" }
 itertools = { version = "0.10.3", default-features = false }
 miette = { version = "5", features = ["fancy"] }
diff --git a/bindings/ergo-lib-jni/build.gradle b/bindings/ergo-lib-jni/build.gradle
index 20619bab4..af52d9622 100644
--- a/bindings/ergo-lib-jni/build.gradle
+++ b/bindings/ergo-lib-jni/build.gradle
@@ -1,6 +1,6 @@
 buildscript {
     ext {
-        kotlin_version = '1.4.10'
+        kotlin_version = '1.9.0'
     }
     ext.buildConfig = [
         'compileSdkVersion': 30,
@@ -23,17 +23,16 @@ buildscript {
         }
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:4.0.0'
+        classpath 'com.android.tools.build:gradle:8.0.0'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
         classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
-        classpath 'gradle.plugin.org.mozilla.rust-android-gradle:plugin:0.8.3'
+        classpath 'org.mozilla.rust-android-gradle:plugin:0.9.2'
     }
 }
 
 apply plugin: 'maven-publish'
 apply plugin: 'com.android.library'
 apply plugin: 'kotlin-android'
-apply plugin: 'kotlin-android-extensions'
 apply plugin: 'kotlin-kapt'
 apply plugin: 'kotlin-allopen'
 apply plugin: 'org.mozilla.rust-android-gradle.rust-android'
@@ -53,6 +52,9 @@ android {
     useLibrary 'android.test.runner'
 
     ndkVersion "26.2.11394342"
+    android {
+        namespace 'com.ergoplatform'
+    }
 
     defaultConfig {
         minSdkVersion buildConfig.minSdkVersion
@@ -80,8 +82,11 @@ android {
     }
 
     compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_8
-        targetCompatibility JavaVersion.VERSION_1_8
+        sourceCompatibility JavaVersion.VERSION_18
+        targetCompatibility JavaVersion.VERSION_18
+    }
+    kotlinOptions {
+        jvmTarget = "18"
     }
 
     testOptions {
@@ -139,5 +144,3 @@ dependencies {
     androidTestImplementation 'androidx.test.ext:junit:1.1.1'
     androidTestImplementation 'androidx.test:runner:1.2.0'
 }
-
-
diff --git a/bindings/ergo-lib-jni/gradle/wrapper/gradle-wrapper.properties b/bindings/ergo-lib-jni/gradle/wrapper/gradle-wrapper.properties
index 386f5e48a..54d114a6b 100644
--- a/bindings/ergo-lib-jni/gradle/wrapper/gradle-wrapper.properties
+++ b/bindings/ergo-lib-jni/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip
diff --git a/bindings/ergo-lib-jni/src/main/AndroidManifest.xml b/bindings/ergo-lib-jni/src/main/AndroidManifest.xml
index 3319c5813..c534434ee 100644
--- a/bindings/ergo-lib-jni/src/main/AndroidManifest.xml
+++ b/bindings/ergo-lib-jni/src/main/AndroidManifest.xml
@@ -1,5 +1,4 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="org.ergoplatform.wallet.jni">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
     <!-- <uses-permission android:name="android.permission.INTERNET" /> -->
     <!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
 </manifest>
diff --git a/ergo-chain-types/src/digest32.rs b/ergo-chain-types/src/digest32.rs
index 39eaa93eb..f3352633e 100644
--- a/ergo-chain-types/src/digest32.rs
+++ b/ergo-chain-types/src/digest32.rs
@@ -42,7 +42,7 @@ impl<const N: usize> Digest<N> {
         Digest([0u8; N])
     }
 
-    /// Parse Digest<N> from base64 encoded string
+    /// Parse `Digest<N>` from base64 encoded string
     pub fn from_base64(s: &str) -> Result<Digest<N>, DigestNError> {
         let bytes = base64::decode(s)?;
         let arr: [u8; N] = bytes.as_slice().try_into()?;
diff --git a/ergo-chain-types/src/peer_addr.rs b/ergo-chain-types/src/peer_addr.rs
index 934602496..d8d35805c 100644
--- a/ergo-chain-types/src/peer_addr.rs
+++ b/ergo-chain-types/src/peer_addr.rs
@@ -32,6 +32,29 @@ impl PeerAddr {
     }
 }
 
+impl TryFrom<&Url> for PeerAddr {
+    type Error = std::io::Error;
+    fn try_from(url: &Url) -> Result<Self, Self::Error> {
+        // Based on url.socket_addrs, this will not work on wasm32-unknown-unknown if the url is a domain since that lacks DNS operations
+        use std::net::ToSocketAddrs;
+        fn io_result<T>(opt: Option<T>, message: &str) -> std::io::Result<T> {
+            opt.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, message))
+        }
+
+        let host = io_result(url.host(), "No host name in the URL")?;
+        let port = url.port_or_known_default().unwrap_or(9053);
+
+        Ok(match host {
+            url::Host::Domain(domain) => PeerAddr(io_result(
+                (domain, port).to_socket_addrs()?.nth(0),
+                "Could not resolve domain",
+            )?),
+            url::Host::Ipv4(ip) => PeerAddr((ip, port).into()),
+            url::Host::Ipv6(ip) => PeerAddr((ip, port).into()),
+        })
+    }
+}
+
 impl ScorexSerializable for PeerAddr {
     fn scorex_serialize<W: sigma_ser::vlq_encode::WriteSigmaVlqExt>(
         &self,
diff --git a/ergo-rest/src/api/peer_discovery_internals/chrome.rs b/ergo-rest/src/api/peer_discovery_internals/chrome.rs
index 6afd24d96..1ca521af1 100644
--- a/ergo-rest/src/api/peer_discovery_internals/chrome.rs
+++ b/ergo-rest/src/api/peer_discovery_internals/chrome.rs
@@ -395,7 +395,7 @@ fn spawn_http_request_task_chrome(
                     url.set_port(Some(9053)).unwrap();
                     #[allow(clippy::unwrap_used)]
                     let node_conf = NodeConf {
-                        addr: PeerAddr(url.socket_addrs(|| Some(9053)).unwrap()[0]),
+                        addr: PeerAddr::try_from(&url).unwrap(),
                         api_key: None,
                         timeout: Some(request_timeout_duration),
                     };
diff --git a/ergo-rest/src/api/peer_discovery_internals/non_chrome.rs b/ergo-rest/src/api/peer_discovery_internals/non_chrome.rs
index 3a6bfdfe3..281fe23d2 100644
--- a/ergo-rest/src/api/peer_discovery_internals/non_chrome.rs
+++ b/ergo-rest/src/api/peer_discovery_internals/non_chrome.rs
@@ -15,7 +15,7 @@
 //!  \----------------------/                   \----------------------/
 //!              ^                                                  |
 //!              |__________________________________________________|
-//!                <active node| non-active node| list of peers>   
+//!                <active node| non-active node| list of peers>
 //! ```
 use super::PeerDiscoverySettings;
 use crate::api::peer_discovery_internals::get_peers_all;
@@ -292,7 +292,7 @@ fn spawn_http_request_task<
                     url.set_port(Some(9053)).unwrap();
                     #[allow(clippy::unwrap_used)]
                     let node_conf = NodeConf {
-                        addr: PeerAddr(url.socket_addrs(|| Some(9053)).unwrap()[0]),
+                        addr: PeerAddr::try_from(&url).unwrap(),
                         api_key: None,
                         timeout: Some(request_timeout_duration),
                     };
diff --git a/ergotree-ir/src/chain/ergo_box/register/value.rs b/ergotree-ir/src/chain/ergo_box/register/value.rs
index 7dbf4a445..764b23ea3 100644
--- a/ergotree-ir/src/chain/ergo_box/register/value.rs
+++ b/ergotree-ir/src/chain/ergo_box/register/value.rs
@@ -16,7 +16,7 @@ pub enum RegisterValue {
     /// Constant value
     Parsed(Constant),
     /// Parsed evaluated Tuple expression
-    /// see https://github.com/ergoplatform/sigma-rust/issues/700
+    /// see <https://github.com/ergoplatform/sigma-rust/issues/700>
     ParsedTupleExpr(EvaluatedTuple),
     /// Unparseable bytes
     Invalid {
@@ -28,7 +28,7 @@ pub enum RegisterValue {
 }
 
 /// Ensures that tuple only contains Constant values
-/// see https://github.com/ergoplatform/sigma-rust/issues/700
+/// see <https://github.com/ergoplatform/sigma-rust/issues/700>
 #[derive(PartialEq, Eq, Debug, Clone, From)]
 pub struct EvaluatedTuple {
     tuple: Tuple,
diff --git a/gf2_192/src/gf2_192poly.rs b/gf2_192/src/gf2_192poly.rs
index 19bdedd37..214f9ece4 100644
--- a/gf2_192/src/gf2_192poly.rs
+++ b/gf2_192/src/gf2_192poly.rs
@@ -126,7 +126,7 @@ impl Gf2_192Poly {
         res
     }
 
-    /// Returns Vec<u8> consisting of the concatenation of all the coefficients of the polynomial
+    /// Returns `Vec<u8>` consisting of the concatenation of all the coefficients of the polynomial
     /// NOT including the degree-zero coefficient. Each coefficient takes 24 bytes for a total of
     /// `self.degree * 24` bytes
     pub fn to_bytes(&self) -> Vec<u8> {
diff --git a/sigma-util/src/vec_ext.rs b/sigma-util/src/vec_ext.rs
index a222521e1..5ba78c472 100644
--- a/sigma-util/src/vec_ext.rs
+++ b/sigma-util/src/vec_ext.rs
@@ -4,9 +4,9 @@ use alloc::sync::Arc;
 
 use alloc::vec::Vec;
 
-/// Vec<i8> to Vec<u8> conversion
+/// `Vec<i8>` to `Vec<u8>` conversion
 pub trait FromVecI8 {
-    /// Convert Vec<i8> to Vec<u8>
+    /// Convert `Vec<i8>` to `Vec<u8>`
     fn from_vec_i8(bs: Vec<i8>) -> Self;
 }
 
@@ -21,9 +21,9 @@ impl FromVecI8 for Arc<[u8]> {
     }
 }
 
-/// Convert Vec<i8> to Vec<u8>
+/// Convert `Vec<i8>` to `Vec<u8>`
 pub trait AsVecU8 {
-    /// Returns Vec<u8>
+    /// Returns `Vec<u8>`
     fn as_vec_u8(&self) -> Vec<u8>;
 }
 
@@ -39,9 +39,9 @@ impl AsVecU8 for Arc<[i8]> {
         Vec::<u8>::from_vec_i8(self.iter().copied().collect())
     }
 }
-/// Convert Vec<u8> to Vec<i8>
+/// Convert `Vec<u8>` to `Vec<i8>`
 pub trait AsVecI8 {
-    /// Returns Vec<i8>
+    /// Returns `Vec<i8>`
     fn as_vec_i8(&self) -> Vec<i8>;
 }
 


Test patch: