c6cd4e07e9 Missing validation for incoming file paths from web content process when attachment elements are enabled
Triage note: Adds UI-process allowlisting of file paths supplied by a (possibly compromised) web content process, closing a file-access/sandbox gap.
Contents
The bug at a glance
This closes a UI-process file-access gap reachable from a compromised WebContent process: the renderer could name an arbitrary local path (e.g. /etc/passwd) in RegisterAttachmentIdentifierFromFilePath and have the trusted UI process read that file into an attachment, exfiltrating file contents outside the sandbox. That is a sandbox-boundary confidentiality escape, so high is appropriate; it is not memory corruption and requires prior renderer control, which keeps it below critical.
The angle is a confused-deputy: the UI process legitimately reads files the user chose via pasteboard or drag-and-drop, but it did not distinguish those user-blessed paths from arbitrary paths the renderer invented. The fix records exactly which paths were legitimately handed to the web process and rejects any attachment registration for a path not on that allowlist.
Root cause
OBSERVED: WebPageProxy::registerAttachmentIdentifierFromFilePath runs in the UI process and, before the patch, validated only that attachments were enabled and that the identifier was a valid key. It then proceeded to register (and the UI process to read) the file at the web-process-supplied filePath. There was no check that the renderer was entitled to that path.
INFERRED: The WebContent process is sandboxed and should not be able to induce reads of arbitrary local files. But because the UI process accepted any filePath string over IPC, a compromised renderer could register an attachment pointing at a sensitive file (the test uses /etc/passwd), and the UI process — which is not subject to the renderer sandbox — would read it, letting the attacker recover its contents through the attachment element. This turns a renderer compromise into arbitrary local-file disclosure.
OBSERVED: The fix adds a per-process allowlist HashSet<String> m_allowedAttachmentFilePaths on WebProcessProxy, with addAllowedAttachmentFilePath(const String&) (adding non-empty paths) and isAllowedAttachmentFilePath(const String&) const. Paths are added to the allowlist precisely at the points where the UI process legitimately hands file paths to the web process: in WebPasteboardProxy::getPasteboardPathnamesForType, allPasteboardItemInfo, and informationForItemAtIndex (via the new addAllowedAttachmentFilePaths helper, covering the iOS transcoding continuations too), and in WebPageProxy::performDragOperation for each dragData.fileNames() entry.
OBSERVED: The enforcement point is a new MESSAGE_CHECK_BASE(WebProcessProxy::fromConnection(connection)->isAllowedAttachmentFilePath(filePath), connection) in registerAttachmentIdentifierFromFilePath, which terminates the web process if it names an unlisted path. Separately, registerAttachmentsFromSerializedData gains MESSAGE_CHECK_BASE(IdentifierToAttachmentMap::isValidKey(serializedData.identifier), connection) per item, hardening that adjacent path.
INFERRED: Because the pasteboard/drag helpers add paths only when the UI process itself surfaced them to the renderer (including transcoded HEIC replacements on iOS), the allowlist represents the set of files the user actually exposed; anything else is treated as a forged path and is fatal.
Key code
Allowlist enforcement and storage
// WebPageProxy::registerAttachmentIdentifierFromFilePath
MESSAGE_CHECK_BASE(protect(preferences())->attachmentElementEnabled(), connection);
MESSAGE_CHECK_BASE(IdentifierToAttachmentMap::isValidKey(identifier), connection);
MESSAGE_CHECK_BASE(WebProcessProxy::fromConnection(connection)->isAllowedAttachmentFilePath(filePath), connection);
// WebProcessProxy.cpp
void WebProcessProxy::addAllowedAttachmentFilePath(const String& filePath)
{
if (!filePath.isEmpty())
m_allowedAttachmentFilePaths.add(filePath);
}
bool WebProcessProxy::isAllowedAttachmentFilePath(const String& filePath) const
{
return m_allowedAttachmentFilePaths.contains(filePath);
}
// WebPasteboardProxyCocoa.mm helper
static void addAllowedAttachmentFilePaths(const IPC::Connection& connection, std::optional<WebPageProxyIdentifier> pageID, const Vector<String>& paths)
{
if (!pageID)
return;
RefPtr page = WebProcessProxy::webPage(*pageID);
if (!page)
return;
for (auto& path : paths)
WebProcessProxy::fromConnection(connection)->addAllowedAttachmentFilePath(path);
}
Patch walkthrough
Source/WebKit/UIProcess/WebProcessProxy.h— Declares addAllowedAttachmentFilePath/isAllowedAttachmentFilePath and the private HashSet<String> m_allowedAttachmentFilePaths member, all under ENABLE(ATTACHMENT_ELEMENT).Source/WebKit/UIProcess/WebProcessProxy.cpp— Implements the two methods: add inserts non-empty paths into the set; isAllowed returns whether the set contains the path.Source/WebKit/UIProcess/Cocoa/WebPasteboardProxyCocoa.mm— Adds the addAllowedAttachmentFilePaths helper and calls it wherever pasteboard file paths are returned to the web process: getPasteboardPathnamesForType, and every branch of allPasteboardItemInfo and informationForItemAtIndex, including the iOS HEIC transcoding continuations (now capturing protectedConnection and pageID so post-transcode paths are also allowlisted).Source/WebKit/UIProcess/WebPageProxy.cpp— In performDragOperation, allowlists each dragData.fileNames() path for the main-frame process. In registerAttachmentIdentifierFromFilePath, adds the MESSAGE_CHECK_BASE against isAllowedAttachmentFilePath. In registerAttachmentsFromSerializedData, adds a per-item isValidKey MESSAGE_CHECK_BASE.Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKAttachmentTests.mm— Adds RegisterAttachmentIdentifierFromFilePathWithUnauthorizedPathTerminatesProcess (sends a forged /etc/passwd registration via IPCTestingAPI and expects WebContent termination) and PastedFileURLsUseAuthorizedPaths (verifies legitimately pasted file URLs still work).
Background
Attachment elements — WebKit’s attachment element feature (ENABLE(ATTACHMENT_ELEMENT)) lets rich-text editing embed file attachments. The renderer registers an attachment identifier bound to a file path, and the UI process reads that file’s data. The UI process is the trusted party performing the read, so it must ensure the path was one the user legitimately exposed.
RegisterAttachmentIdentifierFromFilePath IPC — The message by which the WebContent process asks the UI process to create an attachment from a file at a given path. Since the path is a renderer-supplied string, without validation it is a direct request for the trusted UI process to open an attacker-chosen file.
Pasteboard / drag-and-drop path provenance — Legitimate file paths reach the renderer only because the UI process returns them from pasteboard queries (getPasteboardPathnamesForType, allPasteboardItemInfo, informationForItemAtIndex) or drag operations (performDragOperation). Recording those exact paths gives a trustworthy allowlist of what the user actually shared.
HEIC transcoding continuations (iOS) — On iOS, pasteboard image paths may be transcoded from HEIC to another format on a background queue, replacing the path the renderer will see. The patch threads protectedConnection and pageID through those continuations so the final (possibly transcoded) upload paths are added to the allowlist, avoiding false rejections.
MESSAGE_CHECK_BASE — An IPC-validation macro that terminates the offending web process when its predicate fails. Here it enforces that any filePath in an attachment registration is present in m_allowedAttachmentFilePaths, converting a silent arbitrary-read into a fatal, non-exploitable rejection.
Vulnerability window
- Feature design — Attachment registration accepts a renderer-supplied file path and the UI process reads it, with only attachment-enabled and valid-key checks.
- Gap — No provenance check ties the path to something the user actually exposed, so a compromised renderer can name any local file.
- Report — Tracked as bugs.webkit.org 309698 / rdar://170082216 as missing validation for incoming file paths when attachment elements are enabled.
- Fix — A per-WebProcessProxy allowlist is populated at every legitimate pasteboard/drag path-handoff and enforced with MESSAGE_CHECK_BASE at registration.
- Tests — A macOS API test forges a /etc/passwd registration and expects process termination; another confirms genuinely pasted file URLs still register and carry data.
Proof of concept
The added API test enables attachments and IPCTestingAPI, then from the page sends a WebPageProxy_RegisterAttachmentIdentifierFromFilePath IPC naming /etc/passwd — a path the UI process never handed to this renderer. Pre-patch the UI process would proceed to read that file into an attachment; post-patch isAllowedAttachmentFilePath(’/etc/passwd’) is false, the MESSAGE_CHECK_BASE fires, and the test observes WebContent process termination. PastedFileURLsUseAuthorizedPaths confirms the legitimate pasteboard flow still succeeds.
TEST(WKAttachmentTestsMac, RegisterAttachmentIdentifierFromFilePathWithUnauthorizedPathTerminatesProcess)
{
RetainPtr configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[configuration _setAttachmentElementEnabled:YES];
WKPreferencesSetCustomPasteboardDataEnabled((__bridge WKPreferencesRef)[configuration preferences], YES);
for (_WKFeature *feature in [WKPreferences _features]) {
if ([feature.key isEqualToString:@"IPCTestingAPIEnabled"])
[[configuration preferences] _setEnabled:YES forFeature:feature];
if ([feature.key isEqualToString:@"IgnoreInvalidMessageWhenIPCTestingAPIEnabled"])
[[configuration preferences] _setEnabled:NO forFeature:feature];
}
RetainPtr webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 500, 500) configuration:configuration.get()]);
[webView synchronouslyLoadHTMLString:attachmentEditingTestMarkup];
RetainPtr navigationDelegate = adoptNS([TestNavigationDelegate new]);
[webView setNavigationDelegate:navigationDelegate.get()];
[webView evaluateJavaScript:
@"IPC.sendMessage('UI', IPC.webPageProxyID, IPC.messages.WebPageProxy_RegisterAttachmentIdentifierFromFilePath.name, ["
" {type: 'String', value: 'fake-identifier'},"
" {type: 'String', value: 'application/octet-stream'},"
" {type: 'String', value: '/etc/passwd'}"
"])"
completionHandler:nil];
[navigationDelegate waitForWebContentProcessDidTerminate];
}
Exploitation
- Prerequisite — Requires a compromised WebContent process (attachments enabled) able to send RegisterAttachmentIdentifierFromFilePath; the test uses IPCTestingAPI, absent from production, so real abuse follows an initial renderer bug.
- Path selection — The attacker registers an attachment whose filePath points at a sensitive local file outside the sandbox (config files, keychains, user documents).
- Read via trusted UI process — Pre-patch the UI process, not bound by the renderer sandbox, reads the file into the attachment’s data, which the renderer can then read back — an arbitrary local-file disclosure primitive.
- Impact bound — This is confidentiality escape, not code execution; it does not by itself grant memory corruption. Post-patch the same attempt terminates the renderer, yielding at most a crash for unauthorized paths.
Detection & hunting
For defenders and SOC / detection engineers:
- Unauthorized-path terminations —
- Attachment paths outside pasteboard/drag provenance —
- Sensitive path strings in IPC —
Audit directions
- Other renderer-supplied path IPCs —
- Allowlist population completeness —
- Path canonicalization —
- registerAttachmentsFromSerializedData —