fix(security): ignore rendezvous-provided relay hosts

Signed-off-by: Vlad (Kuzmin) Erium <libalias@gmail.com>
(cherry picked from commit a4b1c37f54554631c9f97200144afadc257eddee)
Signed-off-by: Vlad (Kuzmin) Erium <libalias@gmail.com>
This commit is contained in:
Vlad (Kuzmin) Erium 2026-04-14 22:51:55 +09:00
parent 458bee228e
commit 91b153aede
3 changed files with 78 additions and 10 deletions

View file

@ -515,7 +515,10 @@ impl Client {
peer_nat_type = ph.nat_type();
is_local = ph.is_local();
signed_id_pk = ph.pk.into();
relay_server = ph.relay_server;
relay_server = crate::common::resolve_trusted_relay_server(
&rendezvous_server,
&ph.relay_server,
);
peer_addr = AddrMangle::decode(&ph.socket_addr);
feedback = ph.feedback;
let s = udp.0.take();
@ -557,10 +560,14 @@ impl Client {
}
}
signed_id_pk = rr.pk().into();
let trusted_relay_server = crate::common::resolve_trusted_relay_server(
&rendezvous_server,
&rr.relay_server,
);
let fut = Self::create_relay(
&peer,
rr.uuid,
rr.relay_server,
trusted_relay_server,
&key,
conn_type,
my_addr.is_ipv4(),

View file

@ -934,6 +934,33 @@ pub fn increase_port<T: std::string::ToString>(host: T, offset: i32) -> String {
hbb_common::socket_client::increase_port(host, offset)
}
pub fn resolve_trusted_relay_server(
rendezvous_server: &str,
provided_by_rendezvous_server: &str,
) -> String {
let configured_relay_server = Config::get_option(keys::OPTION_RELAY_SERVER);
if !configured_relay_server.is_empty() {
return check_port(configured_relay_server, RELAY_PORT);
}
let derived_relay_server = if rendezvous_server.is_empty() {
String::new()
} else {
increase_port(check_port(rendezvous_server, RENDEZVOUS_PORT), 1)
};
if !provided_by_rendezvous_server.is_empty() {
let provided_relay_server = check_port(provided_by_rendezvous_server, RELAY_PORT);
if !derived_relay_server.is_empty() && provided_relay_server != derived_relay_server {
log::warn!(
"Ignoring rendezvous-provided relay server {} in favor of derived relay {}",
provided_relay_server,
derived_relay_server
);
}
}
derived_relay_server
}
pub const POSTFIX_SERVICE: &'static str = "_service";
#[inline]
@ -3860,6 +3887,47 @@ mod tests {
assert_eq!(get_tcp_proxy_addr(), format!("[1:2]:{RENDEZVOUS_PORT}"));
}
#[test]
fn test_resolve_trusted_relay_server_prefers_explicit_override() {
struct RestoreRelayServer(String);
impl Drop for RestoreRelayServer {
fn drop(&mut self) {
Config::set_option(keys::OPTION_RELAY_SERVER.to_string(), self.0.clone());
}
}
let _restore = RestoreRelayServer(Config::get_option(keys::OPTION_RELAY_SERVER));
Config::set_option(
keys::OPTION_RELAY_SERVER.to_string(),
"relay.override.example".to_string(),
);
assert_eq!(
resolve_trusted_relay_server("hbbs.example.com:21116", "attacker.example.com:29999"),
format!("relay.override.example:{RELAY_PORT}")
);
}
#[test]
fn test_resolve_trusted_relay_server_ignores_server_supplied_override() {
struct RestoreRelayServer(String);
impl Drop for RestoreRelayServer {
fn drop(&mut self) {
Config::set_option(keys::OPTION_RELAY_SERVER.to_string(), self.0.clone());
}
}
let _restore = RestoreRelayServer(Config::get_option(keys::OPTION_RELAY_SERVER));
Config::set_option(keys::OPTION_RELAY_SERVER.to_string(), "".to_string());
assert_eq!(
resolve_trusted_relay_server("hbbs.example.com:21116", "attacker.example.com:29999"),
"hbbs.example.com:21117"
);
}
#[tokio::test]
async fn test_http_request_via_tcp_proxy_rejects_invalid_header_json() {
let result = http_request_via_tcp_proxy("not a url", "get", None, "{").await;

View file

@ -752,14 +752,7 @@ impl RendezvousMediator {
}
fn get_relay_server(&self, provided_by_rendezvous_server: String) -> String {
let mut relay_server = Config::get_option("relay-server");
if relay_server.is_empty() {
relay_server = provided_by_rendezvous_server;
}
if relay_server.is_empty() {
relay_server = crate::increase_port(&self.host, 1);
}
relay_server
crate::common::resolve_trusted_relay_server(&self.host, &provided_by_rendezvous_server)
}
}