Skip to content

Commit 65cf226

Browse files
Do not panic when reading public key of relay
Avoid panic when reading public key of relay due to unwrapping an unexpected relay type. The unwrap happened if an OpenVPN relay was selected on Android, which should not happen in the first place.
1 parent 1dc1bde commit 65cf226

File tree

6 files changed

+103
-52
lines changed

6 files changed

+103
-52
lines changed

Diff for: mullvad-daemon/src/tunnel.rs

+6-11
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,8 @@ pub enum Error {
4444
#[error("Not logged in on a valid device")]
4545
NoAuthDetails,
4646

47-
#[error("No relay available")]
48-
NoRelayAvailable,
49-
50-
#[error("No bridge available")]
51-
NoBridgeAvailable,
47+
#[error("Failed to select a matching relay")]
48+
SelectRelay(#[from] mullvad_relay_selector::Error),
5249

5350
#[error("Failed to resolve hostname for custom relay")]
5451
ResolveCustomHostname,
@@ -148,11 +145,7 @@ impl InnerParametersGenerator {
148145
let data = self.device().await?;
149146
let selected_relay = self
150147
.relay_selector
151-
.get_relay(retry_attempt as usize, RuntimeParameters { ipv6 })
152-
.map_err(|err| match err {
153-
mullvad_relay_selector::Error::NoBridge => Error::NoBridgeAvailable,
154-
_ => Error::NoRelayAvailable,
155-
})?;
148+
.get_relay(retry_attempt as usize, RuntimeParameters { ipv6 })?;
156149

157150
match selected_relay {
158151
#[cfg(not(target_os = "android"))]
@@ -287,7 +280,9 @@ impl TunnelParametersGenerator for ParametersGenerator {
287280
.generate(retry_attempt, ipv6)
288281
.await
289282
.map_err(|error| match error {
290-
Error::NoBridgeAvailable => ParameterGenerationError::NoMatchingBridgeRelay,
283+
Error::SelectRelay(mullvad_relay_selector::Error::NoBridge) => {
284+
ParameterGenerationError::NoMatchingBridgeRelay
285+
}
291286
Error::ResolveCustomHostname => {
292287
ParameterGenerationError::CustomTunnelHostResultionError
293288
}

Diff for: mullvad-relay-selector/src/relay_selector/detailer.rs

+20-9
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@ use mullvad_types::{
1414
constraints::Constraint,
1515
endpoint::MullvadWireguardEndpoint,
1616
relay_constraints::TransportPort,
17-
relay_list::{OpenVpnEndpoint, OpenVpnEndpointData, Relay, WireguardEndpointData},
17+
relay_list::{
18+
OpenVpnEndpoint, OpenVpnEndpointData, Relay, RelayEndpointData, WireguardEndpointData,
19+
},
1820
};
1921
use talpid_types::net::{
20-
all_of_the_internet, wireguard::PeerConfig, Endpoint, IpVersion, TransportProtocol,
22+
all_of_the_internet,
23+
wireguard::{PeerConfig, PublicKey},
24+
Endpoint, IpVersion, TransportProtocol,
2125
};
2226

2327
use super::{
@@ -31,6 +35,8 @@ pub enum Error {
3135
NoOpenVpnEndpoint,
3236
#[error("No bridge endpoint could be derived")]
3337
NoBridgeEndpoint,
38+
#[error("OpenVPN relays and bridges does not have a public key. Expected a Wireguard relay")]
39+
MissingPublicKey,
3440
#[error("The selected relay does not support IPv6")]
3541
NoIPv6(Box<Relay>),
3642
#[error("Invalid port argument: port {0} is not in any valid Wireguard port range")]
@@ -70,7 +76,7 @@ fn wireguard_singlehop_endpoint(
7076
SocketAddr::new(host, port)
7177
};
7278
let peer_config = PeerConfig {
73-
public_key: exit.endpoint_data.unwrap_wireguard_ref().public_key.clone(),
79+
public_key: get_public_key(exit)?.clone(),
7480
endpoint,
7581
allowed_ips: all_of_the_internet(),
7682
// This will be filled in later, not the relay selector's problem
@@ -106,7 +112,7 @@ fn wireguard_multihop_endpoint(
106112
SocketAddr::from((ip, port))
107113
};
108114
let exit = PeerConfig {
109-
public_key: exit.endpoint_data.unwrap_wireguard_ref().public_key.clone(),
115+
public_key: get_public_key(exit)?.clone(),
110116
endpoint: exit_endpoint,
111117
// The exit peer should be able to route incoming VPN traffic to the rest of
112118
// the internet.
@@ -121,11 +127,7 @@ fn wireguard_multihop_endpoint(
121127
SocketAddr::from((host, port))
122128
};
123129
let entry = PeerConfig {
124-
public_key: entry
125-
.endpoint_data
126-
.unwrap_wireguard_ref()
127-
.public_key
128-
.clone(),
130+
public_key: get_public_key(entry)?.clone(),
129131
endpoint: entry_endpoint,
130132
// The entry peer should only be able to route incoming VPN traffic to the
131133
// exit peer.
@@ -177,6 +179,15 @@ fn get_port_for_wireguard_relay(
177179
}
178180
}
179181

182+
/// Read the [`PublicKey`] of a relay. This will only succeed if [relay][`Relay`] is a
183+
/// [Wireguard][`RelayEndpointData::Wireguard`] relay.
184+
fn get_public_key(relay: &Relay) -> Result<&PublicKey, Error> {
185+
match &relay.endpoint_data {
186+
RelayEndpointData::Wireguard(endpoint) => Ok(&endpoint.public_key),
187+
RelayEndpointData::Openvpn | RelayEndpointData::Bridge => Err(Error::MissingPublicKey),
188+
}
189+
}
190+
180191
/// Selects a random port number from a list of provided port ranges.
181192
///
182193
/// This function iterates over a list of port ranges, each represented as a tuple (u16, u16)

Diff for: mullvad-relay-selector/src/relay_selector/matcher.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,24 @@ pub const fn filter_openvpn(relay: &Relay) -> bool {
114114
}
115115

116116
/// Returns whether the relay matches the tunnel constraint `filter`
117+
#[cfg(not(target_os = "android"))]
117118
pub const fn filter_tunnel_type(filter: &Constraint<TunnelType>, relay: &Relay) -> bool {
118119
match filter {
119120
Constraint::Any => true,
120121
Constraint::Only(typ) => match typ {
121-
// Do not keep OpenVPN relays on Android
122-
TunnelType::OpenVpn if cfg!(target_os = "android") => false,
123122
TunnelType::OpenVpn => filter_openvpn(relay),
124123
TunnelType::Wireguard => filter_wireguard(relay),
125124
},
126125
}
127126
}
128127

128+
/// Returns whether the relay matches the tunnel constraint `filter`
129+
#[cfg(target_os = "android")]
130+
pub const fn filter_tunnel_type(_: &Constraint<TunnelType>, relay: &Relay) -> bool {
131+
// Only keep Wireguard relays on Android (i.e. filter out OpenVPN relays)
132+
filter_wireguard(relay)
133+
}
134+
129135
/// Returns whether the relay is a Wireguard relay.
130136
pub const fn filter_wireguard(relay: &Relay) -> bool {
131137
matches!(relay.endpoint_data, RelayEndpointData::Wireguard(_))

Diff for: mullvad-relay-selector/src/relay_selector/mod.rs

+68-20
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use mullvad_types::{
2525
OpenVpnConstraints, RelayConstraints, RelayOverride, RelaySettings, ResolvedBridgeSettings,
2626
SelectedObfuscation, WireguardConstraints,
2727
},
28-
relay_list::{Relay, RelayList},
28+
relay_list::{Relay, RelayEndpointData, RelayList},
2929
settings::Settings,
3030
CustomTunnelEndpoint,
3131
};
@@ -201,10 +201,39 @@ pub enum GetRelay {
201201
/// will actually be routed to the broader internet.
202202
#[derive(Clone, Debug)]
203203
pub enum WireguardConfig {
204+
/// Strongly prefer to instantiate this variant using [`WireguardConfig::singlehop`] as that
205+
/// will assert that the relay is of the expected type.
204206
Singlehop { exit: Relay },
207+
/// Strongly prefer to instantiate this variant using [`WireguardConfig::multihop`] as that
208+
/// will assert that the entry & exit relays are of the expected type.
205209
Multihop { exit: Relay, entry: Relay },
206210
}
207211

212+
impl WireguardConfig {
213+
fn singlehop(exit: Relay) -> Self {
214+
// FIXME: This assert would be better to encode at the type level.
215+
assert!(matches!(
216+
exit.endpoint_data,
217+
RelayEndpointData::Wireguard(_)
218+
));
219+
Self::Singlehop { exit }
220+
}
221+
222+
fn multihop(exit: Relay, entry: Relay) -> Self {
223+
// FIXME: This assert would be better to encode at the type level.
224+
assert!(matches!(
225+
exit.endpoint_data,
226+
RelayEndpointData::Wireguard(_)
227+
));
228+
// FIXME: This assert would be better to encode at the type level.
229+
assert!(matches!(
230+
entry.endpoint_data,
231+
RelayEndpointData::Wireguard(_)
232+
));
233+
Self::Multihop { exit, entry }
234+
}
235+
}
236+
208237
#[derive(Clone, Debug)]
209238
pub enum SelectedBridge {
210239
Normal { settings: CustomProxy, relay: Relay },
@@ -454,7 +483,7 @@ impl RelaySelector {
454483
}
455484
SpecializedSelectorConfig::Normal(pure_config) => {
456485
let parsed_relays = &self.parsed_relays.lock().unwrap();
457-
Self::get_relay_inner(&query, parsed_relays, &pure_config)
486+
Self::get_relay_inner(query, parsed_relays, &pure_config)
458487
}
459488
}
460489
}
@@ -522,7 +551,7 @@ impl RelaySelector {
522551
runtime_params,
523552
user_preferences,
524553
);
525-
Self::get_relay_inner(&query, parsed_relays, &normal_config)
554+
Self::get_relay_inner(query, parsed_relays, &normal_config)
526555
}
527556
}
528557
}
@@ -568,7 +597,7 @@ impl RelaySelector {
568597
/// * An `Err` if no suitable bridge is found
569598
#[cfg(not(target_os = "android"))]
570599
fn get_relay_inner(
571-
query: &RelayQuery,
600+
query: RelayQuery,
572601
parsed_relays: &ParsedRelays,
573602
config: &NormalSelectorConfig<'_>,
574603
) -> Result<GetRelay, Error> {
@@ -585,7 +614,7 @@ impl RelaySelector {
585614
let mut new_query = query.clone();
586615
new_query.tunnel_protocol = Constraint::Only(tunnel_type);
587616
// If a suitable relay is found, short-circuit and return it
588-
if let Ok(relay) = Self::get_relay_inner(&new_query, parsed_relays, config) {
617+
if let Ok(relay) = Self::get_relay_inner(new_query, parsed_relays, config) {
589618
return Ok(relay);
590619
}
591620
}
@@ -596,15 +625,24 @@ impl RelaySelector {
596625

597626
#[cfg(target_os = "android")]
598627
fn get_relay_inner(
599-
query: &RelayQuery,
628+
mut query: RelayQuery,
600629
parsed_relays: &ParsedRelays,
601630
config: &NormalSelectorConfig<'_>,
602631
) -> Result<GetRelay, Error> {
632+
// FIXME: A bit of defensive programming - calling `get_wiregurad_relay` with a query that doesn't
633+
// specify Wireguard as the desired tunnel type is not valid and will lead to unwanted
634+
// behavior. This should be seen as a workaround, and it would be nicer to lift this
635+
// invariant to be checked by the type system instead.
636+
query.tunnel_protocol = Constraint::Only(TunnelType::Wireguard);
603637
Self::get_wireguard_relay(query, config, parsed_relays)
604638
}
605639

606640
/// Derive a valid Wireguard relay configuration from `query`.
607641
///
642+
/// # Note
643+
/// This function should *only* be called with a Wireguard query.
644+
/// It will panic if the tunnel type is not specified as Wireguard.
645+
///
608646
/// # Returns
609647
/// * An `Err` if no exit relay can be chosen
610648
/// * An `Err` if no entry relay can be chosen (if multihop is enabled on `query`)
@@ -613,18 +651,22 @@ impl RelaySelector {
613651
///
614652
/// [`MullvadEndpoint`]: mullvad_types::endpoint::MullvadEndpoint
615653
fn get_wireguard_relay(
616-
query: &RelayQuery,
654+
query: RelayQuery,
617655
config: &NormalSelectorConfig<'_>,
618656
parsed_relays: &ParsedRelays,
619657
) -> Result<GetRelay, Error> {
658+
assert_eq!(
659+
query.tunnel_protocol,
660+
Constraint::Only(TunnelType::Wireguard)
661+
);
620662
let inner = if !query.wireguard_constraints.multihop() {
621-
Self::get_wireguard_singlehop_config(query, config, parsed_relays)?
663+
Self::get_wireguard_singlehop_config(&query, config, parsed_relays)?
622664
} else {
623-
Self::get_wireguard_multihop_config(query, config, parsed_relays)?
665+
Self::get_wireguard_multihop_config(&query, config, parsed_relays)?
624666
};
625-
let endpoint = Self::get_wireguard_endpoint(query, parsed_relays, &inner)?;
667+
let endpoint = Self::get_wireguard_endpoint(&query, parsed_relays, &inner)?;
626668
let obfuscator =
627-
Self::get_wireguard_obfuscator(query, inner.clone(), &endpoint, parsed_relays)?;
669+
Self::get_wireguard_obfuscator(&query, inner.clone(), &endpoint, parsed_relays)?;
628670

629671
Ok(GetRelay::Wireguard {
630672
endpoint,
@@ -646,7 +688,8 @@ impl RelaySelector {
646688
let candidates =
647689
filter_matching_relay_list(query, parsed_relays.relays(), config.custom_lists);
648690
helpers::pick_random_relay(&candidates)
649-
.map(|exit| WireguardConfig::Singlehop { exit: exit.clone() })
691+
.cloned()
692+
.map(WireguardConfig::singlehop)
650693
.ok_or(Error::NoRelay)
651694
}
652695

@@ -700,10 +743,7 @@ impl RelaySelector {
700743
}
701744
.ok_or(Error::NoRelay)?;
702745

703-
Ok(WireguardConfig::Multihop {
704-
exit: exit.clone(),
705-
entry: entry.clone(),
706-
})
746+
Ok(WireguardConfig::multihop(exit.clone(), entry.clone()))
707747
}
708748

709749
/// Constructs a [`MullvadEndpoint`] with details for how to connect to `relay`.
@@ -754,6 +794,10 @@ impl RelaySelector {
754794

755795
/// Derive a valid OpenVPN relay configuration from `query`.
756796
///
797+
/// # Note
798+
/// This function should *only* be called with an OpenVPN query.
799+
/// It will panic if the tunnel type is not specified as OpenVPN.
800+
///
757801
/// # Returns
758802
/// * An `Err` if no exit relay can be chosen
759803
/// * An `Err` if no entry bridge can be chosen (if bridge mode is enabled on `query`)
@@ -763,16 +807,19 @@ impl RelaySelector {
763807
/// [`MullvadEndpoint`]: mullvad_types::endpoint::MullvadEndpoint
764808
#[cfg(not(target_os = "android"))]
765809
fn get_openvpn_relay(
766-
query: &RelayQuery,
810+
query: RelayQuery,
767811
config: &NormalSelectorConfig<'_>,
768812
parsed_relays: &ParsedRelays,
769813
) -> Result<GetRelay, Error> {
814+
assert_eq!(query.tunnel_protocol, Constraint::Only(TunnelType::OpenVpn));
770815
let exit =
771-
Self::choose_openvpn_relay(query, config, parsed_relays).ok_or(Error::NoRelay)?;
772-
let endpoint = Self::get_openvpn_endpoint(query, &exit, parsed_relays)?;
816+
Self::choose_openvpn_relay(&query, config, parsed_relays).ok_or(Error::NoRelay)?;
817+
let endpoint = Self::get_openvpn_endpoint(&query, &exit, parsed_relays)?;
773818
let bridge =
774-
Self::get_openvpn_bridge(query, &exit, &endpoint.protocol, parsed_relays, config)?;
819+
Self::get_openvpn_bridge(&query, &exit, &endpoint.protocol, parsed_relays, config)?;
775820

821+
// FIXME: This assert would be better to encode at the type level.
822+
assert!(matches!(exit.endpoint_data, RelayEndpointData::Openvpn));
776823
Ok(GetRelay::OpenVpn {
777824
endpoint,
778825
exit,
@@ -964,6 +1011,7 @@ impl RelaySelector {
9641011

9651012
/// # Returns
9661013
/// A randomly selected relay that meets the specified constraints, or `None` if no suitable relay is found.
1014+
#[cfg(not(target_os = "android"))]
9671015
fn choose_openvpn_relay(
9681016
query: &RelayQuery,
9691017
config: &NormalSelectorConfig<'_>,

Diff for: mullvad-types/src/relay_constraints.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ impl GeographicLocationConstraint {
265265
GeographicLocationConstraint::Hostname(country.into(), city.into(), hostname.into())
266266
}
267267

268-
// TODO(markus): Document
268+
/// Check if `self` is _just_ a country. See [`GeographicLocationConstraint`] for more details.
269269
pub fn is_country(&self) -> bool {
270270
matches!(self, GeographicLocationConstraint::Country(_))
271271
}

Diff for: mullvad-types/src/relay_list.rs

-9
Original file line numberDiff line numberDiff line change
@@ -168,15 +168,6 @@ pub enum RelayEndpointData {
168168
Wireguard(WireguardRelayEndpointData),
169169
}
170170

171-
impl RelayEndpointData {
172-
pub fn unwrap_wireguard_ref(&self) -> &WireguardRelayEndpointData {
173-
if let RelayEndpointData::Wireguard(wg) = &self {
174-
return wg;
175-
}
176-
panic!("not a wireguard endpoint");
177-
}
178-
}
179-
180171
/// Data needed to connect to OpenVPN endpoints.
181172
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
182173
pub struct OpenVpnEndpointData {

0 commit comments

Comments
 (0)