← All issues

[23] Pass an extra ApplicationBundleIdentifier through PCM

Severity: Low | Component: WebKit Networking / PCM | f8277ab

Rated Low because the diff is a feature addition rather than a vulnerability fix, but it relocates a privacy-proxy fail-closed flag from an unconditional set in the loader to a conditional set in a centralised helper and broadens the adattributiond daemon's sandbox/entitlements to permit direct network egress — two structural changes that narrow a privacy invariant and widen the daemon's attack surface.

Adds a "secondary" identifier (com.apple.webkit.adattributiond) plumbed through PrivateClickMeasurementManager and NetworkLoader::start, sets it on the NSURLSessionConfiguration for adattributiond's URLSession, and grants the daemon network-client/socket-delegate/networkserviceproxy entitlements plus mach-lookup for com.apple.networkserviceproxy. The _privacyProxyFailClosed flag moves from PrivateClickMeasurementNetworkLoaderCocoa.mm (unconditional) to NetworkDataTaskCocoa.mm::setPCMDataCarriedOnRequest next to _needsNetworkTrackingPrevention; per the commit message it now only applies outside debug mode.

Source/WebKit/NetworkProcess/PrivateClickMeasurement/cocoa/PrivateClickMeasurementNetworkLoaderCocoa.mm

WTF::switchOn(applicationBundleIdentifier,
- [&] (const String& bundleIdentifier) {
- configuration.get()._sourceApplicationBundleIdentifier = bundleIdentifier.createNSString().get();
+ [&] (const std::pair<String, String>& bundleIdentifiers) {
+ configuration.get()._sourceApplicationBundleIdentifier = bundleIdentifiers.first.createNSString().get();
+ configuration.get()._sourceApplicationSecondaryIdentifier = bundleIdentifiers.second.createNSString().get();
}, ...);
- [request _setPrivacyProxyFailClosed:YES];
+ [request setAttribution:NSURLRequestAttributionUser];

Source/WebKit/NetworkProcess/cocoa/NetworkDataTaskCocoa.mm

request._needsNetworkTrackingPrevention = YES;
+ request._privacyProxyFailClosed = YES;

Source/WebKit/Scripts/process-entitlements.sh

+ plistbuddy Add :com.apple.private.network.socket-delegate bool YES
+ plistbuddy Add :com.apple.security.network.client bool YES
+ plistbuddy Add :com.apple.private.networkserviceproxy bool YES
+ plistbuddy Add :com.apple.security.exception.mach-lookup.global-name:0 string com.apple.networkserviceproxy

Privacy-proxy fail-closed guard relocated and made conditional on debug mode, paired with a sandbox/entitlement relaxation that hands the attribution daemon a network egress path.

ApplicationBundleIdentifierOrAuditToken becomes ApplicationBundleIdentifiersOrAuditToken (Variant<std::pair<String, String>, Vector<uint8_t>>) and is threaded through PrivateClickMeasurementManager::create/ctor, NetworkLoader::start, managerOrProxy, and initializePCMStorageInDirectory. The Cocoa loader sets both _sourceApplicationBundleIdentifier and _sourceApplicationSecondaryIdentifier. PCMDaemonEntryPoint.mm hard-codes the secondary identifier to com.apple.webkit.adattributiond. setAttribution:NSURLRequestAttributionUser is now set on PCM report requests. The adattributiond sandbox profile is relaxed to read com.apple.networkextension.uuidcache.plist.

PCM is WebKit's privacy-preserving ad-click attribution mechanism — a click on a source site stores metadata, and on later conversion an attribution report is sent to the source's endpoint. On iOS, PCM runs in an XPC daemon (adattributiond) using NSURLSession to post reports. _sourceApplicationBundleIdentifier/_sourceApplicationSecondaryIdentifier are NSURLSessionConfiguration SPIs that tag outgoing traffic with the originating application's identity. _privacyProxyFailClosed is an NSMutableURLRequest SPI that tells the networking stack to fail the request rather than fall back to a direct connection when the privacy relay is unavailable; without it, a relay outage degrades to a direct connection that exposes the client IP. networkserviceproxy is the system service that brokers network connections for sandboxed daemons.

The refactor itself is mechanical and ownership-preserving — the Variant still holds either a value-pair of Strings or a Vector<uint8_t>, all by-value, so no lifetime change. The two behavioural deltas matter. The relocation of _privacyProxyFailClosed narrows the invariant from "set on this request object before it leaves the loader" to "set somewhere downstream conditional on PCM data being carried"; reviewers should be wary of any future code path that constructs a PCM-bound request but bypasses setPCMDataCarriedOnRequest, since the fail-closed guarantee would silently degrade.

The adattributiond daemon gains a network-egress capability it did not previously have. New reachable state: the daemon can open client sockets and talk to networkserviceproxy. Invariants assumed but not enforced: that PCM report URLs are only the attestation/attribution endpoints the daemon constructs internally (no attacker-supplied URL), that the secondary identifier is the only value ever used, and that the relocated _privacyProxyFailClosed is applied on every PCM-carried request in production builds. Class of bugs newly possible if those assumptions break: SSRF or arbitrary-host egress from the daemon if URL construction is attacker-influenced upstream; IP leak to attribution endpoints if _privacyProxyFailClosed is missed on any code path.

This weakens the network-egress confinement model around PCM. The invariant "PCM requests never bypass the privacy proxy" narrows to "in non-debug builds, assuming the conditional path is taken". No direct exploitation primitive is introduced by this commit alone.