Sfoglia il codice sorgente

p2pnet: validate hosts ips before storing

aggstam 3 anni fa
parent
commit
d8b440b831
5 ha cambiato i file con 156 aggiunte e 17 eliminazioni
  1. 7 0
      Cargo.lock
  2. 2 0
      Cargo.toml
  3. 56 0
      src/net/constants.rs
  4. 88 17
      src/net/hosts.rs
  5. 3 0
      src/net/mod.rs

+ 7 - 0
Cargo.lock

@@ -332,6 +332,12 @@ dependencies = [
  "rustc-demangle",
 ]
 
+[[package]]
+name = "base32"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
+
 [[package]]
 name = "base64"
 version = "0.13.0"
@@ -1180,6 +1186,7 @@ dependencies = [
  "async-std",
  "async-trait",
  "async-tungstenite",
+ "base32",
  "bincode",
  "blake2b_simd",
  "blake3",

+ 2 - 0
Cargo.toml

@@ -67,6 +67,7 @@ rustls-pemfile = {version = "1.0.1", optional = true}
 
 # Encoding
 bincode = {version = "2.0.0-rc.1", features = ["serde"], optional = true}
+base32 = {version = "0.4.0", optional = true}
 bs58 = {version = "0.4.0", optional = true}
 hex = {version = "0.4.3", optional = true}
 serde_json = {version = "1.0.85", optional = true}
@@ -205,6 +206,7 @@ dht = [
 ]
 
 net = [
+    "base32",
     "fxhash",
     "ed25519-compact",
     "fast-socks5",

+ 56 - 0
src/net/constants.rs

@@ -0,0 +1,56 @@
+/// Localnet addresses
+pub const LOCALNET: [&str; 5] = ["localhost", "0.0.0.0", "[::]", "127.0.0.1", "[::1]"];
+
+/// Illegal IPv6 addresses
+pub const IP6_PRIV_RANGES: [&str; 2] = ["fc00::/7", "fec0::/10"];
+
+/// Illegal IPv4 addresses
+pub const IP4_PRIV_RANGES: [&str; 47] = [
+    "0.0.0.0/8",
+    "10.0.0.0/8",
+    "127.0.0.0/8",
+    "224.0.0.0/8",
+    "225.0.0.0/8",
+    "226.0.0.0/8",
+    "227.0.0.0/8",
+    "228.0.0.0/8",
+    "229.0.0.0/8",
+    "230.0.0.0/8",
+    "231.0.0.0/8",
+    "232.0.0.0/8",
+    "233.0.0.0/8",
+    "234.0.0.0/8",
+    "235.0.0.0/8",
+    "236.0.0.0/8",
+    "237.0.0.0/8",
+    "238.0.0.0/8",
+    "239.0.0.0/8",
+    "240.0.0.0/8",
+    "241.0.0.0/8",
+    "242.0.0.0/8",
+    "243.0.0.0/8",
+    "244.0.0.0/8",
+    "245.0.0.0/8",
+    "246.0.0.0/8",
+    "247.0.0.0/8",
+    "248.0.0.0/8",
+    "249.0.0.0/8",
+    "250.0.0.0/8",
+    "251.0.0.0/8",
+    "252.0.0.0/8",
+    "253.0.0.0/8",
+    "254.0.0.0/8",
+    "255.0.0.0/8",
+    "100.64.0.0/10",
+    "169.254.0.0/16",
+    "172.16.0.0/12",
+    "192.0.0.0/24",
+    "192.0.2.0/24",
+    "192.88.99.0/24",
+    "192.168.0.0/16",
+    "198.18.0.0/15",
+    "198.51.100.0/24",
+    "203.0.113.0/24",
+    "233.252.0.0/24",
+    "255.255.255.255/32",
+];

+ 88 - 17
src/net/hosts.rs

@@ -1,9 +1,10 @@
 use async_std::sync::{Arc, Mutex};
+use std::net::IpAddr;
 
 use fxhash::FxHashSet;
 use url::Url;
 
-const LOCALNET: [&str; 5] = ["localhost", "0.0.0.0", "[::]", "127.0.0.1", "[::1]"];
+use super::constants::{IP4_PRIV_RANGES, IP6_PRIV_RANGES, LOCALNET};
 
 /// Pointer to hosts class.
 pub type HostsPtr = Arc<Hosts>;
@@ -20,32 +21,21 @@ impl Hosts {
         Arc::new(Self { addrs: Mutex::new(FxHashSet::default()), localnet })
     }
 
-    /// Add a new host to the host list, after filtering localnet hosts,
-    /// if configured to do so.
+    /// Add a new host to the host list, after filtering.
     pub async fn store(&self, input_addrs: Vec<Url>) {
         let addrs = if !self.localnet {
-            let mut filtered = vec![];
-            for addr in &input_addrs {
-                match addr.host_str() {
-                    Some(host_str) => {
-                        if LOCALNET.contains(&host_str) {
-                            continue
-                        }
-                    }
-                    None => continue,
-                }
-                filtered.push(addr.clone());
-            }
-            filtered
+            let filtered = filter_localnet(input_addrs);
+            filter_invalid(filtered)
         } else {
             input_addrs
         };
-
         for addr in addrs {
             self.addrs.lock().await.insert(addr);
         }
     }
 
+    // TODO: add single host store, which also checks that resolved ips are the same as the connection
+
     /// Return the list of hosts.
     pub async fn load_all(&self) -> Vec<Url> {
         self.addrs.lock().await.iter().cloned().collect()
@@ -61,3 +51,84 @@ impl Hosts {
         self.addrs.lock().await.is_empty()
     }
 }
+
+/// Auxiliary function to filter localnet hosts.
+fn filter_localnet(input_addrs: Vec<Url>) -> Vec<Url> {
+    let mut filtered = vec![];
+    for addr in &input_addrs {
+        match addr.host_str() {
+            Some(host_str) => {
+                if LOCALNET.contains(&host_str) {
+                    continue
+                }
+            }
+            None => continue,
+        }
+        filtered.push(addr.clone());
+    }
+    filtered
+}
+
+/// Auxiliary function to filter invalid(unresolvable) hosts.
+fn filter_invalid(input_addrs: Vec<Url>) -> Vec<Url> {
+    let mut filtered = vec![];
+    for addr in &input_addrs {
+        // Discard domainless Urls
+        let domain = match addr.domain() {
+            Some(d) => d,
+            None => continue,
+        };
+
+        // Validate onion domain
+        if domain.ends_with(".onion") && is_valid_onion(domain) {
+            filtered.push(addr.clone());
+            continue
+        }
+
+        // Validate normal domain
+        if let Ok(socket_addrs) = addr.socket_addrs(|| None) {
+            // Check if domain resolved to anything
+            if socket_addrs.is_empty() {
+                continue
+            }
+            // Checking resolved IP validity
+            let mut valid = true;
+            for i in socket_addrs {
+                match i.ip() {
+                    IpAddr::V4(a) => {
+                        if IP4_PRIV_RANGES.contains(&a.to_string().as_str()) {
+                            valid = false;
+                            break
+                        }
+                    }
+                    IpAddr::V6(a) => {
+                        if IP6_PRIV_RANGES.contains(&a.to_string().as_str()) {
+                            valid = false;
+                            break
+                        }
+                    }
+                }
+            }
+            if valid {
+                filtered.push(addr.clone());
+            }
+        }
+    }
+    filtered
+}
+
+/// Auxiliary function to validate an onion.
+fn is_valid_onion(onion: &str) -> bool {
+    let onion = match onion.strip_suffix(".onion") {
+        Some(s) => s,
+        None => onion,
+    };
+
+    if onion.len() != 56 {
+        return false
+    }
+
+    let alphabet = base32::Alphabet::RFC4648 { padding: false };
+
+    !base32::decode(alphabet, onion).is_none()
+}

+ 3 - 0
src/net/mod.rs

@@ -88,6 +88,9 @@ pub mod settings;
 /// Network transport implementations.
 pub mod transport;
 
+/// Network constants for various validations.
+pub mod constants;
+
 pub use acceptor::{Acceptor, AcceptorPtr};
 pub use channel::{Channel, ChannelPtr};
 pub use connector::Connector;