[23] Pass an extra ApplicationBundleIdentifier through PCM
이번 commit은 vulnerability fix가 아닌 기능 추가에 해당하므로 Severity를 Low로 평가합니다. 다만 두 가지 구조적 변경이 포함되어 있어 주목이 필요합니다. privacy-proxy fail-closed flag가 loader의 무조건적 설정에서 중앙화된 helper의 조건부 설정으로 이동했고, adattributiond daemon의 sandbox와 entitlement가 직접 네트워크 egress를 허용하는 방향으로 확장되었습니다. 두 변경 모두 privacy invariant를 약화시키고 daemon의 attack surface를 넓히는 결과로 이어집니다.
"secondary" identifier(com.apple.webkit.adattributiond)가 PrivateClickMeasurementManager와 NetworkLoader::start를 거쳐 전달되도록 추가되었습니다. adattributiond의 URLSession에는 해당 식별자가 NSURLSessionConfiguration을 통해 설정됩니다. 아울러 daemon에는 network-client/socket-delegate/networkserviceproxy entitlement와 com.apple.networkserviceproxy에 대한 mach-lookup 권한이 새로 부여되었습니다. _privacyProxyFailClosed flag는 PrivateClickMeasurementNetworkLoaderCocoa.mm의 무조건적 설정에서 NetworkDataTaskCocoa.mm::setPCMDataCarriedOnRequest 내부의 _needsNetworkTrackingPrevention 옆으로 이동했습니다. commit message에 따르면 이 flag는 이제 debug mode 외부에서만 적용됩니다.
Source/WebKit/NetworkProcess/PrivateClickMeasurement/cocoa/PrivateClickMeasurementNetworkLoaderCocoa.mm
Source/WebKit/NetworkProcess/cocoa/NetworkDataTaskCocoa.mm
Source/WebKit/Scripts/process-entitlements.sh
Privacy-proxy fail-closed guard가 debug mode 조건부로 재배치되었으며, attribution daemon에 network egress 경로를 부여하는 sandbox/entitlement 완화와 함께 적용되었습니다.
Patch Details
ApplicationBundleIdentifierOrAuditToken이 ApplicationBundleIdentifiersOrAuditToken(Variant<std::pair<String, String>, Vector<uint8_t>>)으로 변경되었습니다. 이 타입은 PrivateClickMeasurementManager::create/ctor, NetworkLoader::start, managerOrProxy, initializePCMStorageInDirectory를 거쳐 전달됩니다. Cocoa loader는 _sourceApplicationBundleIdentifier와 _sourceApplicationSecondaryIdentifier 양쪽을 모두 설정합니다. PCMDaemonEntryPoint.mm에서는 secondary identifier가 com.apple.webkit.adattributiond로 하드코딩되었습니다. PCM report 요청에는 setAttribution:NSURLRequestAttributionUser가 설정되도록 변경되었으며, adattributiond sandbox profile은 com.apple.networkextension.uuidcache.plist를 읽을 수 있도록 완화되었습니다.
Background
PCM은 WebKit의 privacy-preserving ad-click attribution 메커니즘입니다. source 사이트에서의 클릭 정보를 메타데이터로 저장하고, 이후 conversion이 발생하면 해당 source의 endpoint로 attribution report를 전송합니다. iOS에서는 PCM이 XPC daemon(adattributiond)으로 실행되며, NSURLSession을 통해 report를 전송합니다. _sourceApplicationBundleIdentifier와 _sourceApplicationSecondaryIdentifier는 발신 트래픽에 원본 애플리케이션의 identity를 태그하는 NSURLSessionConfiguration SPI입니다. _privacyProxyFailClosed는 NSMutableURLRequest SPI로, privacy relay를 사용할 수 없는 경우 직접 연결로 fallback하는 대신 요청 자체를 실패시키도록 networking stack에 지시합니다. 이 설정이 없으면 relay 장애 시 직접 연결로 저하되어 클라이언트 IP가 노출될 수 있습니다. networkserviceproxy는 sandboxed daemon의 네트워크 연결을 중개하는 시스템 서비스입니다.
Analysis
이번 refactoring 자체는 기계적이며 ownership을 유지하는 변경입니다. Variant는 여전히 String 쌍 또는 Vector<uint8_t>를 값으로 보유하므로 lifetime 변화는 없습니다. 주목해야 할 것은 동작상의 두 가지 변화입니다. _privacyProxyFailClosed의 이동으로 인해 invariant가 "loader를 벗어나기 전 해당 request 객체에 설정"에서 "PCM 데이터 전달 여부를 조건으로 downstream 어딘가에서 설정"으로 범위가 좁아졌습니다. 향후 PCM-bound request를 생성하면서 setPCMDataCarriedOnRequest를 우회하는 code path가 추가될 경우, fail-closed 보장이 조용히 약화될 수 있으므로 주의가 필요합니다.
adattributiond daemon은 기존에 없던 network egress 권한을 새로 확보했습니다. 새로 열린 도달 가능한 상태는 다음과 같습니다. daemon이 client socket을 열고 networkserviceproxy와 통신할 수 있게 되었습니다. 가정되지만 강제되지 않는 invariant도 존재합니다. PCM report URL이 daemon이 내부적으로 구성하는 attestation/attribution endpoint에만 해당한다는 점(공격자가 제공한 URL 없음), secondary identifier만 사용된다는 점, 이동된 _privacyProxyFailClosed가 production build에서 모든 PCM-carried request에 적용된다는 점이 이에 해당합니다. 이러한 가정이 깨질 경우 새로 가능해지는 버그 유형은 다음과 같습니다. URL 구성이 upstream에서 공격자의 영향을 받는 경우 daemon에서 SSRF 또는 임의 호스트로의 egress가 발생할 가능성이 있습니다. 또한 어떤 code path에서든 _privacyProxyFailClosed가 누락되면 attribution endpoint에 IP 노출로 이어질 수 있습니다.
이번 변경은 PCM 주변의 network-egress confinement 모델을 약화시킵니다. "PCM 요청은 privacy proxy를 절대 우회하지 않는다"는 invariant가 "non-debug build에서, 조건부 경로가 실행된다는 가정 하에"로 범위가 좁아졌습니다. 다만 이번 commit 단독으로는 직접적인 exploitation primitive가 도입되지는 않습니다.
Audit directions
- Privacy/security flag가 request 생성 지점에서 중앙화된 helper로 이동했으며, 해당 helper는 upstream 파라미터에 따라 조건부로 실행됩니다.
setPCMDataCarriedOnRequest와 PCM-carrying request를 생성하는 모든 호출 지점을 점검하여pcmDataCarried경로가 모든 production 경로에서_privacyProxyFailClosed = YES에 도달하는지 확인합니다.Source/WebKit/NetworkProcess전체에서_privacyProxyFailClosed를 검색합니다. - 새로운 capability 클래스를 도입하는 daemon sandbox/entitlement 완화.
process-entitlements.sh와SandboxProfiles/ios/*.sb.in에서com.apple.security.network.client또는com.apple.private.networkserviceproxy를 획득하는 daemon을 점검합니다. 각 daemon에 대해 daemon 코드 내의 URL 구성을 추적하여 공격자가 영향을 미칠 수 있는 URL이나 호스트가 새로운 egress 경로에 도달하지 못하는지 확인합니다. adattributiond의 경우PrivateClickMeasurementManager.cpp에서tokenPublicKeyURL,tokenSignatureURL, 그리고 report URL 구성 방식을 검토합니다. - 단일 값 대안을 tuple 대안으로 확장하는
Variant구조 변경.ApplicationBundleIdentifiersOrAuditToken의 모든switchOn/WTF::visit처리 지점을 점검하여 모든 지점에서first와second양쪽이 업데이트되는지, 그리고 빈 문자열 기본값이 기존에 설정된 필드를 null로 만들지 않는지 확인합니다. - downstream network policy의 trust-tag로 사용되는 하드코딩된 daemon bundle identifier.
com.apple.webkit.adattributiond가 시스템 networking stack에서 인식되는 값인지, 그리고 primary identifier보다 더 허용적인 per-app policy를 상속받지 않는지 확인합니다.