[3] Pasteboard file-type blocklist bypassed by UTI aliasing
The blocklist compared strings while AppKit compared identities.
Medium. The blocklist and NSPasteboard disagreed about what counts as the same type: three families of alternate spelling reached the file-URL slot that exact string matching was meant to hold shut. Escalation past "attacker controls the pasteboard" needs a user to paste into an application that acts on file URLs.
Pasteboard writes are a capability handoff: a sandboxed renderer asks the more-trusted UI process to declare data under a named type on a system pasteboard, and some of those type names mean "this is a file on disk". canWritePasteboardType() — the filter in WebKit's macOS pasteboard layer that decides which type names a WebContent process may declare — consults isFilePasteboardType(), a three-entry blocklist. AppKit, on the other side of that filter, does not treat a pasteboard type as an opaque string: it canonicalizes names through the Uniform Type Identifier system, so several distinct spellings address one and the same pasteboard slot.
The angle: a compromised renderer can place a file URL of its choosing on the user's pasteboard under a name the blocklist does not recognize, so a later paste into a file-accepting application acts on an attacker-chosen path.
Patch Details
isFilePasteboardType() previously performed exact, case-sensitive isEqualToString: comparisons against NSFilenamesPboardType, NSFilesPromisePboardType and UTTypeFileURL.identifier (public.file-url). The patch replaces literal comparison with canonicalization on three axes and adds a conformance test.
First, the comparisons move to caseInsensitiveCompare:, covered by a new IsFilePasteboardTypeCaseVariants unit test. Second, the input is resolved both as a UTI via typeWithIdentifier: and as a com.apple.nspboard-type tag, and UTType.tags[@"com.apple.nspboard-type"] is checked for the two legacy names — this is what catches a synthesized dyn.* identifier standing in for a legacy NSPboardType with no declared UTI. Third, an OSType four-character-code wrapper of the form "CorePasteboardFlavorType 0x6675726C" is parsed: the eight hex digits are converted back into the 4-byte tag string (0x66 0x75 0x72 0x6C = 'furl') and resolved through the com.apple.ostype tag class. A new helper, typeConformsToFilePasteboardType(), widens the file-URL test from identifier equality to conformsToType:UTTypeFileURL, so any UTI that merely conforms to public.file-url is rejected rather than only the identifier itself.
The patch also deletes a block in setStringForType() that mirrored a write arriving under NSURLPboardType ("Apple URL pasteboard type", a type canWritePasteboardType() permits) into the UTTypeFileURL slot whenever the pasteboard already advertised public.file-url and the URL was a file: URL. A new SetStringForTypeRejectsFileURL API test asserts that writing file:///etc/passwd via "Apple URL pasteboard type" produces no public.file-url entry.
Input spelling Before After
────────────── ────── ─────
"public.file-url" blocked blocked
"PUBLIC.FILE-URL" ALLOWED (case-sensitive) blocked (caseInsensitiveCompare)
"dyn.ah62d4rv4gu8y6y4grf0..." ALLOWED (no literal hit) blocked (nspboard-type tag)
"CorePasteboardFlavorType
0x6675726C" ('furl') ALLOWED (no literal hit) blocked (com.apple.ostype tag)
UTI conforming to public.file-url ALLOWED (identity only) blocked (conformsToType:)
A blocklist comparing raw strings while its consumer compares canonicalized identities, so every alternate spelling of the forbidden type is an open path.
Background
Where this lives.
PlatformPasteboardMac.mm is the thin WebCore wrapper over NSPasteboard that the UI process's WebPasteboardProxy uses to service pasteboard IPC from WebContent. Because the renderer may be compromised, canWritePasteboardType() acts as the capability filter for which types that renderer may declare or write on a UI-process-owned pasteboard; getPathnamesForType() uses the same predicate as the guard on reading file paths back out.
Uniform Type Identifiers.
A UTI is a reverse-DNS string naming a data type, arranged in a conformance hierarchy — public.png conforms to public.image, which conforms to public.data. The hierarchy exists so consumers can ask "is this thing an image?" without enumerating every image format; the corollary for a blocklist is that testing identifier equality answers a much narrower question than testing conformance.
Legacy pasteboard types and tag classes.
Pasteboard type names predate UTIs. NSFilenamesPboardType and the OSType four-character codes are older naming schemes, and the UTI system bridges them through tag classes: a UTType can be looked up by, or asked for, its com.apple.nspboard-type or com.apple.ostype tag. When a legacy NSPboardType string has no declared UTI at all, the system synthesizes a dyn.* identifier encoding the original name. The practical consequence is that one pasteboard slot has several equally valid spellings, and which one you see depends on which API you asked.
File promises and file URLs.
NSFilenamesPboardType carries paths, NSFilesPromisePboardType carries a promise to produce files, and public.file-url carries a URL with a file: scheme. All three tell a receiving application "this refers to something on the filesystem", which is why all three are on the blocklist and why the interesting question is what else resolves to them.
Analysis
The root cause is an identity differential between a validator and its consumer — the classic canonicalization bypass. The exact spellings of all three forbidden names were already rejected before this patch; they appear in the new tests as regression coverage. What the literal comparisons did not match were three encoding families that AppKit nonetheless resolves onto the same pasteboard slot.
Case is the simplest of the three. All three pre-fix comparisons were case-sensitive, so PUBLIC.FILE-URL, Public.File-Url, nsfilenamespboardtype and APPLE FILES PROMISE PASTEBOARD TYPE fell straight through. UTI identifiers such as public.file-url compare case-insensitively, so the platform treats those spellings as one type. Whether AppKit's legacy com.apple.nspboard-type name lookup behaves the same way is a platform question the new IsFilePasteboardTypeCaseVariants test asserts rather than one WebKit's own code decides; the fix takes the conservative side and compares case-insensitively regardless.
The dynamic-UTI family is the one that most clearly defeats a string blocklist, because the attacker-supplied string shares no substring with the name being blocked. The new unit test labels dyn.ah62d4rv4gu8y6y4grf0gn5xbrzw1gydcr7u1e3cytf2gn as the dynamic UTI of NSFilenamesPboardType; that specific encoding is the test's own assertion and is relayed as such. The fix stops comparing strings and instead resolves whatever it is handed through typeWithIdentifier:, then asks the resulting UTType for its com.apple.nspboard-type tag — testing identity the way the platform defines it rather than the way the source file spells it.
The OSType wrapper works the same way one level down: "CorePasteboardFlavorType 0x6675726C" embeds the four-character code 'furl', and the new test's comment records that this com.apple.ostype tag maps to public.file-url. Parsing the hex, rebuilding the tag and resolving it through the com.apple.ostype class closes that encoding by the same mechanism as the previous one.
The secondary flaw in setStringForType() is a different bug in the same file and worth separating out. There, the capability check ran against the incoming type while the data landed under a different type: a write declared as NSURLPboardType — permitted — was mirrored into the UTTypeFileURL slot — forbidden — whenever the pasteboard already advertised public.file-url and the URL had a file: scheme. That is a confused deputy in the narrowest sense: the filter and the write disagree about which object is being operated on, so no amount of hardening in isFilePasteboardType() would have caught it.
Exploitability is bounded by what a pasteboard entry can do rather than by any memory-safety condition. The confirmed primitive is narrower than it first looks: getting a non-exact spelling of a forbidden file-bearing type past canWritePasteboardType(). That the declaration then occupies the public.file-url or NSFilenamesPboardType slot is AppKit's canonicalization — the behaviour the fix's own tag-class lookups encode and the new tests assert — and renderer-chosen content actually landing in that slot additionally requires a data write to follow the declaration under the same type. From there, a concrete compromise requires a user paste into an application that acts on file URLs, and the resulting capability is whatever that application does with an attacker-chosen path.
This vulnerability weakens the UI process's role as the filter on what a sandboxed renderer may declare on a system pasteboard — a boundary that exists precisely because pasteboard contents cross from the browser into arbitrary other applications.
Audit directions
- Validator/consumer identity differentials. Wherever a security decision compares a string while the component acting on that string canonicalizes it — UTIs, MIME types, scheme names, file extensions, header field names — the gap between the two notions of equality is the bug. Start with the remaining literal comparisons in
PlatformPasteboardMac.mmand the iOS pasteboard equivalents, then widen to any WebKit predicate that tests a platform-defined identifier withisEqualToString:. Code-review tell:isEqualToString:or==against a constant that names a platform type rather than a WebKit-internal one. - Check-one-name, write-another. A capability filter is only sound if the object it validates is the object that gets written. The removed
setStringForType()mirroring block is the canonical shape: validate type A, store under type B. Audit the pasteboard write paths for any place where the type used in the capability check differs from the type used in the store, and extend the same question to other UI-process resources a renderer can write into under a caller-supplied key. - Identity versus conformance in blocklists. A blocklist built on identifier equality misses every subtype; the
typeConformsToFilePasteboardType()change is the correction. Anywhere a hierarchy exists — UTI conformance, class hierarchies, scheme families — check whether a deny decision uses equality where it needs conformance, and note that the safe direction is the reverse for allow-lists.