fix(apple): Don't crash if tunnel manager session is unexpectedly nil (#7594)

In certain weird edge cases such as:

- The user removes the VPN profile while Firezone is signed in, causing
the `NETunnelProviderSession` to go invalid immediately
- The user approves the system extension to load before the VPN profile
access is granted

then the TunnelManager will not be able to obtain a valid reference to a
NETunnelProviderSession object.

In these cases, for now, it makes more sense to fail silently than to
crash, effectively making these operations a no-op until the user
remedies the VPN profile. Currently the user is prompted to re-grant VPN
profile whenever its status goes to `invalid`, so these cases don't
technically fail without prompting the user.

Draft because it's stacked on #7593 


Fixes #7579 
Fixes #7591

---------

Signed-off-by: Jamil <jamilbk@users.noreply.github.com>
Co-authored-by: Thomas Eizinger <thomas@eizinger.io>
This commit is contained in:
Jamil
2024-12-30 12:14:56 -08:00
committed by GitHub
parent abc61c1480
commit 1ddc2f5de6
2 changed files with 19 additions and 18 deletions

View File

@@ -248,7 +248,7 @@ public class TunnelManager {
}
do {
try session().startTunnel(options: options)
try session()?.startTunnel(options: options)
} catch {
Log.app.error("Error starting tunnel: \(error)")
}
@@ -257,21 +257,21 @@ public class TunnelManager {
func stop(clearToken: Bool = false) {
if clearToken {
do {
try session().sendProviderMessage(encoder.encode(TunnelMessage.signOut)) { _ in
self.session().stopTunnel()
try session()?.sendProviderMessage(encoder.encode(TunnelMessage.signOut)) { _ in
self.session()?.stopTunnel()
}
} catch {
Log.app.error("\(#function): \(error)")
}
} else {
session().stopTunnel()
session()?.stopTunnel()
}
}
func updateInternetResourceState() {
guard session().status == .connected else { return }
guard session()?.status == .connected else { return }
try? session().sendProviderMessage(encoder.encode(TunnelMessage.internetResourceEnabled(internetResourceEnabled))) { _ in }
try? session()?.sendProviderMessage(encoder.encode(TunnelMessage.internetResourceEnabled(internetResourceEnabled))) { _ in }
}
func toggleInternetResource(enabled: Bool) {
@@ -280,10 +280,10 @@ public class TunnelManager {
}
func fetchResources(callback: @escaping (ResourceList) -> Void) {
guard session().status == .connected else { return }
guard session()?.status == .connected else { return }
do {
try session().sendProviderMessage(encoder.encode(TunnelMessage.getResourceList(resourceListHash))) { data in
try session()?.sendProviderMessage(encoder.encode(TunnelMessage.getResourceList(resourceListHash))) { data in
if let data = data {
self.resourceListHash = Data(SHA256.hash(data: data))
let decoder = JSONDecoder()
@@ -301,7 +301,7 @@ public class TunnelManager {
func clearLogs() async throws {
return try await withCheckedThrowingContinuation { continuation in
do {
try session().sendProviderMessage(
try session()?.sendProviderMessage(
encoder.encode(TunnelMessage.clearLogs)
) { _ in continuation.resume() }
} catch {
@@ -313,7 +313,7 @@ public class TunnelManager {
func getLogFolderSize() async throws -> Int64 {
return try await withCheckedThrowingContinuation { continuation in
do {
try session().sendProviderMessage(
try session()?.sendProviderMessage(
encoder.encode(TunnelMessage.getLogFolderSize)
) { data in
@@ -345,7 +345,7 @@ public class TunnelManager {
func loop() {
do {
try session().sendProviderMessage(
try session()?.sendProviderMessage(
encoder.encode(TunnelMessage.exportLogs)
) { data in
guard let data = data
@@ -385,7 +385,7 @@ public class TunnelManager {
func consumeStopReason() async throws -> String {
return try await withCheckedThrowingContinuation { continuation in
do {
try session().sendProviderMessage(
try session()?.sendProviderMessage(
encoder.encode(TunnelMessage.consumeStopReason)
) { data in
@@ -413,12 +413,8 @@ public class TunnelManager {
}
}
private func session() -> NETunnelProviderSession {
guard let manager = manager,
let session = manager.connection as? NETunnelProviderSession
else { fatalError("Could not cast tunnel connection to NETunnelProviderSession!") }
return session
private func session() -> NETunnelProviderSession? {
return manager?.connection as? NETunnelProviderSession
}
// Subscribe to system notifications about our VPN status changing

View File

@@ -12,6 +12,11 @@ export default function Apple() {
>
{/* When you cut a release, remove any solved issues from the "known issues" lists over in `client-apps`. This must not be done when the issue's PR merges. */}
<Unreleased>
<ChangeItem pull="7594">
Fixes a race condition that could cause the app to crash in rare
circumstances if the VPN profile is removed from system settings while
the app is running.
</ChangeItem>
<ChangeItem pull="7593">
Fixes a bug where the VPN status would not properly update upon the
first launch of the app.