iOS Security Stack 2/5: Concurrency Locks (#33241)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: b99ad804fb
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
This commit is contained in:
Mariano
2026-03-03 16:28:27 +00:00
committed by GitHub
parent 3ee8528b17
commit 6df57d9633
3 changed files with 46 additions and 19 deletions

View File

@@ -1,6 +1,7 @@
import AVFoundation
import OpenClawKit
import Foundation
import os
actor CameraController {
struct CameraDeviceInfo: Codable, Sendable {
@@ -260,7 +261,7 @@ actor CameraController {
private final class PhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate {
private let continuation: CheckedContinuation<Data, Error>
private var didResume = false
private let resumed = OSAllocatedUnfairLock(initialState: false)
init(_ continuation: CheckedContinuation<Data, Error>) {
self.continuation = continuation
@@ -271,8 +272,12 @@ private final class PhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegat
didFinishProcessingPhoto photo: AVCapturePhoto,
error: Error?
) {
guard !self.didResume else { return }
self.didResume = true
let alreadyResumed = self.resumed.withLock { old in
let was = old
old = true
return was
}
guard !alreadyResumed else { return }
if let error {
self.continuation.resume(throwing: error)
@@ -301,15 +306,19 @@ private final class PhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegat
error: Error?
) {
guard let error else { return }
guard !self.didResume else { return }
self.didResume = true
let alreadyResumed = self.resumed.withLock { old in
let was = old
old = true
return was
}
guard !alreadyResumed else { return }
self.continuation.resume(throwing: error)
}
}
private final class MovieFileDelegate: NSObject, AVCaptureFileOutputRecordingDelegate {
private let continuation: CheckedContinuation<URL, Error>
private var didResume = false
private let resumed = OSAllocatedUnfairLock(initialState: false)
init(_ continuation: CheckedContinuation<URL, Error>) {
self.continuation = continuation
@@ -321,8 +330,12 @@ private final class MovieFileDelegate: NSObject, AVCaptureFileOutputRecordingDel
from connections: [AVCaptureConnection],
error: Error?)
{
guard !self.didResume else { return }
self.didResume = true
let alreadyResumed = self.resumed.withLock { old in
let was = old
old = true
return was
}
guard !alreadyResumed else { return }
if let error {
let ns = error as NSError

View File

@@ -9,6 +9,7 @@ import Darwin
import OpenClawKit
import Network
import Observation
import os
import Photos
import ReplayKit
import Security
@@ -990,12 +991,16 @@ extension GatewayConnectionController {
#endif
private final class GatewayTLSFingerprintProbe: NSObject, URLSessionDelegate, @unchecked Sendable {
private struct ProbeState {
var didFinish = false
var session: URLSession?
var task: URLSessionWebSocketTask?
}
private let url: URL
private let timeoutSeconds: Double
private let onComplete: (String?) -> Void
private var didFinish = false
private var session: URLSession?
private var task: URLSessionWebSocketTask?
private let state = OSAllocatedUnfairLock(initialState: ProbeState())
init(url: URL, timeoutSeconds: Double, onComplete: @escaping (String?) -> Void) {
self.url = url
@@ -1008,9 +1013,11 @@ private final class GatewayTLSFingerprintProbe: NSObject, URLSessionDelegate, @u
config.timeoutIntervalForRequest = self.timeoutSeconds
config.timeoutIntervalForResource = self.timeoutSeconds
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
self.session = session
let task = session.webSocketTask(with: self.url)
self.task = task
self.state.withLock { s in
s.session = session
s.task = task
}
task.resume()
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + self.timeoutSeconds) { [weak self] in
@@ -1036,12 +1043,18 @@ private final class GatewayTLSFingerprintProbe: NSObject, URLSessionDelegate, @u
}
private func finish(_ fingerprint: String?) {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
guard !self.didFinish else { return }
self.didFinish = true
self.task?.cancel(with: .goingAway, reason: nil)
self.session?.invalidateAndCancel()
let (shouldComplete, taskToCancel, sessionToInvalidate) = self.state.withLock { s -> (Bool, URLSessionWebSocketTask?, URLSession?) in
guard !s.didFinish else { return (false, nil, nil) }
s.didFinish = true
let task = s.task
let session = s.session
s.task = nil
s.session = nil
return (true, task, session)
}
guard shouldComplete else { return }
taskToCancel?.cancel(with: .goingAway, reason: nil)
sessionToInvalidate?.invalidateAndCancel()
self.onComplete(fingerprint)
}