Skip to content

Commit

Permalink
Validate VPN errors before re-throwing them (#1054)
Browse files Browse the repository at this point in the history
**Required**:

Task/Issue URL: https://app.asana.com/0/414709148257752/1208225499545869/f
iOS PR: duckduckgo/iOS#3513
macOS PR: duckduckgo/macos-browser#3490
What kind of version bump will this require?: Major

**Description**:

This PR attempts to catch malformed errors before they get thrown to the OS. It also informs us when this happens.
  • Loading branch information
samsymons authored Nov 3, 2024
1 parent 80894bf commit 45261df
Showing 1 changed file with 66 additions and 2 deletions.
68 changes: 66 additions & 2 deletions Sources/NetworkProtection/PacketTunnelProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ open class PacketTunnelProvider: NEPacketTunnelProvider {
case rekeyAttempt(_ step: RekeyAttemptStep)
case failureRecoveryAttempt(_ step: FailureRecoveryStep)
case serverMigrationAttempt(_ step: ServerMigrationAttemptStep)
case malformedErrorDetected(_ error: Error)
}

public enum AttemptStep: CustomDebugStringConvertible {
Expand Down Expand Up @@ -710,7 +711,13 @@ open class PacketTunnelProvider: NEPacketTunnelProvider {
Logger.networkProtection.log("🔴 Stopping VPN due to no auth token")
await attemptShutdownDueToRevokedAccess()

throw error
// Check that the error is valid and able to be re-thrown to the OS before shutting the tunnel down
if let wrappedError = wrapped(error: error) {
providerEvents.fire(.malformedErrorDetected(error))
throw wrappedError
} else {
throw error
}
}

do {
Expand All @@ -737,7 +744,14 @@ open class PacketTunnelProvider: NEPacketTunnelProvider {
self.knownFailureStore.lastKnownFailure = KnownFailure(error)

providerEvents.fire(.tunnelStartAttempt(.failure(error)))
throw error

// Check that the error is valid and able to be re-thrown to the OS before shutting the tunnel down
if let wrappedError = wrapped(error: error) {
providerEvents.fire(.malformedErrorDetected(error))
throw wrappedError
} else {
throw error
}
}
}

Expand Down Expand Up @@ -1815,6 +1829,56 @@ open class PacketTunnelProvider: NEPacketTunnelProvider {
snoozeTimingStore.reset()
}

// MARK: - Error Validation

enum InvalidDiagnosticError: Error, CustomNSError {
case errorWithInvalidUnderlyingError(Error)

var errorCode: Int {
switch self {
case .errorWithInvalidUnderlyingError(let error):
return (error as NSError).code
}
}

var localizedDescription: String {
switch self {
case .errorWithInvalidUnderlyingError(let error):
return "Error '\(type(of: error))', message: \(error.localizedDescription)"
}
}

var errorUserInfo: [String: Any] {
switch self {
case .errorWithInvalidUnderlyingError(let error):
let newError = NSError(domain: (error as NSError).domain, code: (error as NSError).code)
return [NSUnderlyingErrorKey: newError]
}
}
}

/// Wraps an error instance in a new error type in cases where it is malformed; i.e., doesn't use an `NSError` instance for its underlying error, etc.
private func wrapped(error: Error) -> Error? {
if containsValidUnderlyingError(error) {
return nil
} else {
return InvalidDiagnosticError.errorWithInvalidUnderlyingError(error)
}
}

private func containsValidUnderlyingError(_ error: Error) -> Bool {
let nsError = error as NSError

if let underlyingError = nsError.userInfo[NSUnderlyingErrorKey] as? Error {
return containsValidUnderlyingError(underlyingError)
} else if nsError.userInfo[NSUnderlyingErrorKey] != nil {
// If `NSUnderlyingErrorKey` exists but is not an `Error`, return false
return false
}

return true
}

}

extension WireGuardAdapterError: LocalizedError, CustomDebugStringConvertible {
Expand Down

0 comments on commit 45261df

Please sign in to comment.