Skip to content

Commit 8475022

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 d867bd8 commit 8475022

File tree

6 files changed

+108
-54
lines changed

6 files changed

+108
-54
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

+73-22
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,8 +201,40 @@ pub enum GetRelay {
201201
/// will actually be routed to the broader internet.
202202
#[derive(Clone, Debug)]
203203
pub enum WireguardConfig {
204-
Singlehop { exit: Relay },
205-
Multihop { exit: Relay, entry: Relay },
204+
/// Strongly prefer to instantiate this variant using [`WireguardConfig::singlehop`] as that
205+
/// will assert that the relay is of the expected type.
206+
Singlehop {
207+
exit: Relay,
208+
},
209+
Multihop {
210+
exit: Relay,
211+
entry: Relay,
212+
},
213+
}
214+
215+
impl WireguardConfig {
216+
fn singlehop(exit: Relay) -> Self {
217+
// FIXME: This assert would be better to encode at the type level.
218+
assert!(matches!(
219+
exit.endpoint_data,
220+
RelayEndpointData::Wireguard(_)
221+
));
222+
Self::Singlehop { exit }
223+
}
224+
225+
fn multihop(exit: Relay, entry: Relay) -> Self {
226+
// FIXME: This assert would be better to encode at the type level.
227+
assert!(matches!(
228+
exit.endpoint_data,
229+
RelayEndpointData::Wireguard(_)
230+
));
231+
// FIXME: This assert would be better to encode at the type level.
232+
assert!(matches!(
233+
entry.endpoint_data,
234+
RelayEndpointData::Wireguard(_)
235+
));
236+
Self::Multihop { exit, entry }
237+
}
206238
}
207239

208240
#[derive(Clone, Debug)]
@@ -454,7 +486,7 @@ impl RelaySelector {
454486
}
455487
SpecializedSelectorConfig::Normal(pure_config) => {
456488
let parsed_relays = &self.parsed_relays.lock().unwrap();
457-
Self::get_relay_inner(&query, parsed_relays, &pure_config)
489+
Self::get_relay_inner(query, parsed_relays, &pure_config)
458490
}
459491
}
460492
}
@@ -522,7 +554,7 @@ impl RelaySelector {
522554
runtime_params,
523555
user_preferences,
524556
);
525-
Self::get_relay_inner(&query, parsed_relays, &normal_config)
557+
Self::get_relay_inner(query, parsed_relays, &normal_config)
526558
}
527559
}
528560
}
@@ -568,7 +600,7 @@ impl RelaySelector {
568600
/// * An `Err` if no suitable bridge is found
569601
#[cfg(not(target_os = "android"))]
570602
fn get_relay_inner(
571-
query: &RelayQuery,
603+
query: RelayQuery,
572604
parsed_relays: &ParsedRelays,
573605
config: &NormalSelectorConfig<'_>,
574606
) -> Result<GetRelay, Error> {
@@ -585,7 +617,7 @@ impl RelaySelector {
585617
let mut new_query = query.clone();
586618
new_query.tunnel_protocol = Constraint::Only(tunnel_type);
587619
// 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) {
620+
if let Ok(relay) = Self::get_relay_inner(new_query, parsed_relays, config) {
589621
return Ok(relay);
590622
}
591623
}
@@ -596,15 +628,24 @@ impl RelaySelector {
596628

597629
#[cfg(target_os = "android")]
598630
fn get_relay_inner(
599-
query: &RelayQuery,
631+
mut query: RelayQuery,
600632
parsed_relays: &ParsedRelays,
601633
config: &NormalSelectorConfig<'_>,
602634
) -> Result<GetRelay, Error> {
635+
// FIXME: A bit of defensive programming - calling `get_wiregurad_relay` with a query that doesn't
636+
// specify Wireguard as the desired tunnel type is not valid and will lead to unwanted
637+
// behavior. This should be seen as a workaround, and it would be nicer to lift this
638+
// invariant to be checked by the type system instead.
639+
query.tunnel_protocol = Constraint::Only(TunnelType::Wireguard);
603640
Self::get_wireguard_relay(query, config, parsed_relays)
604641
}
605642

606643
/// Derive a valid Wireguard relay configuration from `query`.
607644
///
645+
/// # Note
646+
/// This function should *only* be called with a Wireguard query.
647+
/// It will panic if the tunnel type is not specified as Wireguard.
648+
///
608649
/// # Returns
609650
/// * An `Err` if no exit relay can be chosen
610651
/// * An `Err` if no entry relay can be chosen (if multihop is enabled on `query`)
@@ -613,18 +654,22 @@ impl RelaySelector {
613654
///
614655
/// [`MullvadEndpoint`]: mullvad_types::endpoint::MullvadEndpoint
615656
fn get_wireguard_relay(
616-
query: &RelayQuery,
657+
query: RelayQuery,
617658
config: &NormalSelectorConfig<'_>,
618659
parsed_relays: &ParsedRelays,
619660
) -> Result<GetRelay, Error> {
661+
assert_eq!(
662+
query.tunnel_protocol,
663+
Constraint::Only(TunnelType::Wireguard)
664+
);
620665
let inner = if !query.wireguard_constraints.multihop() {
621-
Self::get_wireguard_singlehop_config(query, config, parsed_relays)?
666+
Self::get_wireguard_singlehop_config(&query, config, parsed_relays)?
622667
} else {
623-
Self::get_wireguard_multihop_config(query, config, parsed_relays)?
668+
Self::get_wireguard_multihop_config(&query, config, parsed_relays)?
624669
};
625-
let endpoint = Self::get_wireguard_endpoint(query, parsed_relays, &inner)?;
670+
let endpoint = Self::get_wireguard_endpoint(&query, parsed_relays, &inner)?;
626671
let obfuscator =
627-
Self::get_wireguard_obfuscator(query, inner.clone(), &endpoint, parsed_relays)?;
672+
Self::get_wireguard_obfuscator(&query, inner.clone(), &endpoint, parsed_relays)?;
628673

629674
Ok(GetRelay::Wireguard {
630675
endpoint,
@@ -646,7 +691,8 @@ impl RelaySelector {
646691
let candidates =
647692
filter_matching_relay_list(query, parsed_relays.relays(), config.custom_lists);
648693
helpers::pick_random_relay(&candidates)
649-
.map(|exit| WireguardConfig::Singlehop { exit: exit.clone() })
694+
.cloned()
695+
.map(WireguardConfig::singlehop)
650696
.ok_or(Error::NoRelay)
651697
}
652698

@@ -700,10 +746,7 @@ impl RelaySelector {
700746
}
701747
.ok_or(Error::NoRelay)?;
702748

703-
Ok(WireguardConfig::Multihop {
704-
exit: exit.clone(),
705-
entry: entry.clone(),
706-
})
749+
Ok(WireguardConfig::multihop(exit.clone(), entry.clone()))
707750
}
708751

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

755798
/// Derive a valid OpenVPN relay configuration from `query`.
756799
///
800+
/// # Note
801+
/// This function should *only* be called with an OpenVPN query.
802+
/// It will panic if the tunnel type is not specified as OpenVPN.
803+
///
757804
/// # Returns
758805
/// * An `Err` if no exit relay can be chosen
759806
/// * An `Err` if no entry bridge can be chosen (if bridge mode is enabled on `query`)
@@ -763,16 +810,19 @@ impl RelaySelector {
763810
/// [`MullvadEndpoint`]: mullvad_types::endpoint::MullvadEndpoint
764811
#[cfg(not(target_os = "android"))]
765812
fn get_openvpn_relay(
766-
query: &RelayQuery,
813+
query: RelayQuery,
767814
config: &NormalSelectorConfig<'_>,
768815
parsed_relays: &ParsedRelays,
769816
) -> Result<GetRelay, Error> {
817+
assert_eq!(query.tunnel_protocol, Constraint::Only(TunnelType::OpenVpn));
770818
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)?;
819+
Self::choose_openvpn_relay(&query, config, parsed_relays).ok_or(Error::NoRelay)?;
820+
let endpoint = Self::get_openvpn_endpoint(&query, &exit, parsed_relays)?;
773821
let bridge =
774-
Self::get_openvpn_bridge(query, &exit, &endpoint.protocol, parsed_relays, config)?;
822+
Self::get_openvpn_bridge(&query, &exit, &endpoint.protocol, parsed_relays, config)?;
775823

824+
// FIXME: This assert would be better to encode at the type level.
825+
assert!(matches!(exit.endpoint_data, RelayEndpointData::Openvpn));
776826
Ok(GetRelay::OpenVpn {
777827
endpoint,
778828
exit,
@@ -964,6 +1014,7 @@ impl RelaySelector {
9641014

9651015
/// # Returns
9661016
/// A randomly selected relay that meets the specified constraints, or `None` if no suitable relay is found.
1017+
#[cfg(not(target_os = "android"))]
9671018
fn choose_openvpn_relay(
9681019
query: &RelayQuery,
9691020
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)