From 7d7e0b26545dedf41353938f9d23e0005c3e0f2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=AD=A3=E8=B6=85?= Date: Wed, 11 Mar 2026 15:16:03 +0800 Subject: [PATCH] fix(utils): harden panic-prone paths (#2113) Signed-off-by: houseme Co-authored-by: houseme Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: heihutu <30542132+heihutu@users.noreply.github.com> --- crates/utils/src/certs.rs | 24 +++++++++++++++-- crates/utils/src/net.rs | 47 ++++++++++++++++++++++++++++------ crates/utils/src/notify/net.rs | 4 +-- 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/crates/utils/src/certs.rs b/crates/utils/src/certs.rs index a9dd6945..ff81431f 100644 --- a/crates/utils/src/certs.rs +++ b/crates/utils/src/certs.rs @@ -93,7 +93,11 @@ pub fn build_webpki_client_verifier(tls_path: &str) -> io::Result path, + None => { + warn!("skip domain certificate load, invalid UTF-8 path: {:?}", cert_path); + continue; + } + }; + + let key_path = match key_path.to_str() { + Some(path) => path, + None => { + warn!("skip domain key load, invalid UTF-8 path: {:?}", key_path); + continue; + } + }; + + match load_cert_key_pair(cert_path, key_path) { Ok((certs, key)) => { cert_key_pairs.insert(domain_name.to_string(), (certs, key)); } diff --git a/crates/utils/src/net.rs b/crates/utils/src/net.rs index 7db5bc5b..fa13b79e 100644 --- a/crates/utils/src/net.rs +++ b/crates/utils/src/net.rs @@ -18,7 +18,7 @@ use std::{ collections::{HashMap, HashSet}, fmt::Display, io::Error, - net::{IpAddr, Ipv6Addr, SocketAddr, TcpListener, ToSocketAddrs}, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, TcpListener, ToSocketAddrs}, sync::{Arc, LazyLock, Mutex, RwLock}, time::{Duration, Instant}, }; @@ -26,7 +26,7 @@ use tracing::{error, info}; use transform_stream::AsyncTryStream; use url::{Host, Url}; -static LOCAL_IPS: LazyLock> = LazyLock::new(|| must_get_local_ips().unwrap()); +static LOCAL_IPS: LazyLock> = LazyLock::new(get_local_ips_with_fallback); #[derive(Debug, Clone)] struct DnsCacheEntry { @@ -53,7 +53,7 @@ type DynDnsResolver = dyn Fn(&str) -> std::io::Result> + Send + static CUSTOM_DNS_RESOLVER: LazyLock>>> = LazyLock::new(|| RwLock::new(None)); fn resolve_domain(domain: &str) -> std::io::Result> { - if let Some(resolver) = CUSTOM_DNS_RESOLVER.read().unwrap().clone() { + if let Some(resolver) = get_custom_dns_resolver() { return resolver(domain); } @@ -164,6 +164,28 @@ pub fn is_local_host(host: Host<&str>, port: u16, local_port: u16) -> std::io::R Ok(is_local_host) } +fn get_custom_dns_resolver() -> Option> { + match CUSTOM_DNS_RESOLVER.read() { + Ok(guard) => guard.clone(), + Err(poisoned) => { + error!("CUSTOM_DNS_RESOLVER RwLock is poisoned; using resolver value despite poisoning"); + let guard = poisoned.into_inner(); + guard.clone() + } + } +} + +fn has_custom_dns_resolver() -> bool { + get_custom_dns_resolver().is_some() +} + +fn get_local_ips_with_fallback() -> Vec { + match must_get_local_ips() { + Ok(ips) if !ips.is_empty() => ips, + _ => vec![IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), IpAddr::V6(Ipv6Addr::LOCALHOST)], + } +} + /// returns IP address of given host using layered DNS resolution. /// /// This is the async version of `get_host_ip()` that provides enhanced DNS resolution @@ -172,7 +194,7 @@ pub async fn get_host_ip(host: Host<&str>) -> std::io::Result> { match host { Host::Domain(domain) => { // Check cache first - if CUSTOM_DNS_RESOLVER.read().unwrap().is_none() + if !has_custom_dns_resolver() && let Ok(mut cache) = DNS_CACHE.lock() && let Some(entry) = cache.get(domain) { @@ -188,7 +210,7 @@ pub async fn get_host_ip(host: Host<&str>) -> std::io::Result> { // Fallback to standard resolution when DNS resolver is not available match resolve_domain(domain) { Ok(ips) => { - if CUSTOM_DNS_RESOLVER.read().unwrap().is_none() { + if !has_custom_dns_resolver() { // Cache the result if let Ok(mut cache) = DNS_CACHE.lock() { cache.insert(domain.to_string(), DnsCacheEntry::new(ips.clone())); @@ -213,7 +235,16 @@ pub async fn get_host_ip(host: Host<&str>) -> std::io::Result> { } pub fn get_available_port() -> u16 { - TcpListener::bind("0.0.0.0:0").unwrap().local_addr().unwrap().port() + try_get_available_port().unwrap_or_default() +} + +fn try_get_available_port() -> std::io::Result { + let listener = + TcpListener::bind("0.0.0.0:0").map_err(|err| Error::other(format!("Failed to bind for ephemeral port: {err}")))?; + listener + .local_addr() + .map(|addr| addr.port()) + .map_err(|err| Error::other(format!("Failed to read ephemeral port: {err}"))) } /// returns IPs of local interface @@ -297,7 +328,7 @@ pub fn parse_and_resolve_address(addr_str: &str) -> std::io::Result .parse() .map_err(|e| Error::other(format!("Invalid port format: {addr_str}, err:{e:?}")))?; let final_port = if port == 0 { - get_available_port() // assume get_available_port is available here + try_get_available_port()? // assume get_available_port is available here } else { port }; @@ -305,7 +336,7 @@ pub fn parse_and_resolve_address(addr_str: &str) -> std::io::Result } else { let mut addr = check_local_server_addr(addr_str)?; // assume check_local_server_addr is available here if addr.port() == 0 { - addr.set_port(get_available_port()); + addr.set_port(try_get_available_port()?); } addr }; diff --git a/crates/utils/src/notify/net.rs b/crates/utils/src/notify/net.rs index 62ca5ed2..391a6781 100644 --- a/crates/utils/src/notify/net.rs +++ b/crates/utils/src/notify/net.rs @@ -200,8 +200,8 @@ impl std::fmt::Display for ParsedURL { && let Some(port) = url.port() && ((url.scheme() == "http" && port == 80) || (url.scheme() == "https" && port == 443)) { - url.set_host(Some(&host)).unwrap(); - url.set_port(None).unwrap(); + let _ = url.set_host(Some(&host)); + let _ = url.set_port(None); } let mut s = url.to_string();