RESOLVED FIXED 237839
CoreIPC Hardening: Add user gesture check when saving images
https://bugs.webkit.org/show_bug.cgi?id=237839
Summary CoreIPC Hardening: Add user gesture check when saving images
Brent Fulgham
Reported 2022-03-14 10:28:06 PDT
We should check that a user gesture is being handled when the 'performActionOnElement' message handler is attempting to save images to the user's media Library.
Attachments
Patch (1.86 KB, patch)
2022-03-14 17:05 PDT, Brent Fulgham
no flags
Patch (9.49 KB, patch)
2022-03-15 11:35 PDT, Brent Fulgham
no flags
Patch (9.48 KB, patch)
2022-03-15 11:52 PDT, Brent Fulgham
no flags
Patch (14.82 KB, patch)
2022-03-16 10:29 PDT, Brent Fulgham
no flags
Patch (14.76 KB, patch)
2022-03-16 10:37 PDT, Brent Fulgham
ews-feeder: commit-queue-
Patch for landing (13.71 KB, patch)
2022-03-16 11:08 PDT, Brent Fulgham
ews-feeder: commit-queue-
Patch for landing (13.74 KB, patch)
2022-03-16 11:47 PDT, Brent Fulgham
no flags
Patch for landing (13.99 KB, patch)
2022-03-16 13:35 PDT, Brent Fulgham
no flags
Brent Fulgham
Comment 1 2022-03-14 10:28:25 PDT
Brent Fulgham
Comment 2 2022-03-14 17:05:18 PDT
Chris Dumez
Comment 3 2022-03-15 09:26:41 PDT
Comment on attachment 454637 [details] Patch Presumably, if the WebContent process is compromised enough that it can send bogus IPC, it is compromised enough that it can bypass this user gesture check no? My understanding was that any hardening should be done on IPC recipient side for this reason.
Chris Dumez
Comment 4 2022-03-15 09:33:09 PDT
One idea (may need refining) is that WebPageProxy::performActionOnElement() could generate some kind of permission token (e.g. a UUID) and send it with the WebPage::PerformActionOnElement IPC. Then, when WebPage::performActionOnElement() sends IPC back, it would have to pass this same token that the UIProcess could validate. This way, we'd be sure that the WebPageProxy::SaveImageToLibrary IPC was the legitimate result of a legit WebPage::PerformActionOnElement IPC from the UIProcess. Or something in that spirit.
Brent Fulgham
Comment 5 2022-03-15 09:40:43 PDT
(In reply to Chris Dumez from comment #3) > Comment on attachment 454637 [details] > Patch > > Presumably, if the WebContent process is compromised enough that it can send > bogus IPC, it is compromised enough that it can bypass this user gesture > check no? My understanding was that any hardening should be done on IPC > recipient side for this reason. Yes -- I misunderstood how the UserGestureIndicator worked -- this checks for the indicator, but it won't exist yet. We would need to pass the token with the message. I don't see examples of message passing UI tokens, Instead, we just send a boolean -- but I don't think that improves things at all.
Chris Dumez
Comment 6 2022-03-15 09:42:56 PDT
(In reply to Brent Fulgham from comment #5) > (In reply to Chris Dumez from comment #3) > > Comment on attachment 454637 [details] > > Patch > > > > Presumably, if the WebContent process is compromised enough that it can send > > bogus IPC, it is compromised enough that it can bypass this user gesture > > check no? My understanding was that any hardening should be done on IPC > > recipient side for this reason. > > Yes -- I misunderstood how the UserGestureIndicator worked -- this checks > for the indicator, but it won't exist yet. We would need to pass the token > with the message. > > I don't see examples of message passing UI tokens, Instead, we just send a > boolean -- but I don't think that improves things at all. As suggested above, we could send a UI token (like a UUID). If I remember correctly, I did just that for Geolocation permission.
Chris Dumez
Comment 7 2022-03-15 09:44:14 PDT
(In reply to Chris Dumez from comment #6) > (In reply to Brent Fulgham from comment #5) > > (In reply to Chris Dumez from comment #3) > > > Comment on attachment 454637 [details] > > > Patch > > > > > > Presumably, if the WebContent process is compromised enough that it can send > > > bogus IPC, it is compromised enough that it can bypass this user gesture > > > check no? My understanding was that any hardening should be done on IPC > > > recipient side for this reason. > > > > Yes -- I misunderstood how the UserGestureIndicator worked -- this checks > > for the indicator, but it won't exist yet. We would need to pass the token > > with the message. > > > > I don't see examples of message passing UI tokens, Instead, we just send a > > boolean -- but I don't think that improves things at all. > > As suggested above, we could send a UI token (like a UUID). If I remember > correctly, I did just that for Geolocation permission. See this example: DidReceiveGeolocationPermissionDecision(WebKit::GeolocationIdentifier geolocationID, String authorizationToken)
Brent Fulgham
Comment 8 2022-03-15 11:35:57 PDT
Brent Fulgham
Comment 9 2022-03-15 11:46:06 PDT
Comment on attachment 454734 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=454734&action=review It looks like I need to rebase my build before I can put this patch up. > Source/WebKit/UIProcess/WebPageProxy.h:2718 > + StdSet<uint64_t> m_userGestureTokens; @Chris: This duplicates work you did for the GeoLocation manager. Should we modify GeoLocation to make use of this authentication token storage?
Brent Fulgham
Comment 10 2022-03-15 11:52:40 PDT
Chris Dumez
Comment 11 2022-03-15 12:39:00 PDT
Comment on attachment 454738 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=454738&action=review > Source/WebKit/UIProcess/WebPageProxy.h:2397 > + bool isValidAuthorizationToken(const String& authorizationToken) const; Same comment as below about the name being so generic while this is not truly reusable. > Source/WebKit/UIProcess/WebPageProxy.h:2398 > + void revokeAuthorizationToken(const String& authorizationToken); ditto. > Source/WebKit/UIProcess/WebPageProxy.h:2716 > + StdSet<uint64_t> m_userGestureTokens; This is unused I think. > Source/WebKit/UIProcess/WebPageProxy.h:3074 > + HashSet<String> m_validAuthorizationTokens; I wonder a bit about the naming being so generic. It makes it look like this is directly reusable for other IPCs. However, I don't think that's true with the current implementation. I don't think you would want the WebProcess to use an auth token from PerformActionOnElement to do some other operation in the UIProcess that is not the result of the PerformActionOnElement IPC. E.g. It might need to be a HashMap from the IPC message name to the auth token. Alternatively, we could keep using a HashSet but the auth-token would be specific to the IPC message name (e.g. "PerformActionOnElement-[UUID]"). If we start using this more (which sounds plausible), then we'll like need something a bit better to better restrict this to certain IPCs. As a result, I would suggest using a more specific name for now (e.g. m_performActionOnElementAuthTokens). Alternatively, we can try and solve the "how to make it generic" problem now but I'd be OK just crossing that bridge when we get there and use a specific name for now. > Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm:605 > + if (!isValidAuthorizationToken(authorizationToken)) This does a double HashSet lookup which is unfortunate: once for the contains, and then again for the remove. This is not great practice. > Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm:617 > + auto authorizationToken = needsAuthorizationToken(action) ? createVersion4UUIDString() : String(); Do we really gain much by only passing an authorizationToken for SaveImage? Why can't we just always send one, seems like it would make the code simpler. With the sendWithAsyncReply() I propose below, the auth token will get nicely revoked no matter what. > Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm:620 > + m_process->send(Messages::WebPage::PerformActionOnElement(action, authorizationToken), m_webPageID); I think it would be nice to use sendWithAsyncReply() and revoke the auth token in the reply lambda. > Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm:3285 > + send(Messages::WebPageProxy::SaveImageToLibrary(SharedMemory::IPCHandle { WTFMove(handle), buffer->size() }, authorizationToken)); It is probably a good idea to do the same for the `send(Messages::WebPageProxy::WritePromisedAttachmentToPasteboard(WTFMove(attachmentInfo)));` call above in this function no?
Brent Fulgham
Comment 12 2022-03-15 13:17:43 PDT
Comment on attachment 454738 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=454738&action=review >> Source/WebKit/UIProcess/WebPageProxy.h:2397 >> + bool isValidAuthorizationToken(const String& authorizationToken) const; > > Same comment as below about the name being so generic while this is not truly reusable. Will do. >> Source/WebKit/UIProcess/WebPageProxy.h:2398 >> + void revokeAuthorizationToken(const String& authorizationToken); > > ditto. Will do. >> Source/WebKit/UIProcess/WebPageProxy.h:2716 >> + StdSet<uint64_t> m_userGestureTokens; > > This is unused I think. Whoops! >> Source/WebKit/UIProcess/WebPageProxy.h:3074 >> + HashSet<String> m_validAuthorizationTokens; > > I wonder a bit about the naming being so generic. It makes it look like this is directly reusable for other IPCs. However, I don't think that's true with the current implementation. > I don't think you would want the WebProcess to use an auth token from PerformActionOnElement to do some other operation in the UIProcess that is not the result of the PerformActionOnElement IPC. > > E.g. It might need to be a HashMap from the IPC message name to the auth token. Alternatively, we could keep using a HashSet but the auth-token would be specific to the IPC message name (e.g. "PerformActionOnElement-[UUID]"). > > If we start using this more (which sounds plausible), then we'll like need something a bit better to better restrict this to certain IPCs. As a result, I would suggest using a more specific name for now (e.g. m_performActionOnElementAuthTokens). > Alternatively, we can try and solve the "how to make it generic" problem now but I'd be OK just crossing that bridge when we get there and use a specific name for now. I'll use m_performActionOnElementAuthTokens for now. >> Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm:605 >> + if (!isValidAuthorizationToken(authorizationToken)) > > This does a double HashSet lookup which is unfortunate: once for the contains, and then again for the remove. This is not great practice. I'm not even sure this method is needed. I only use it in the 'saveImageToLibrary', which does a message check using the token. I could probably do a find and remove in that method without needing this specialized method. >> Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm:3285 >> + send(Messages::WebPageProxy::SaveImageToLibrary(SharedMemory::IPCHandle { WTFMove(handle), buffer->size() }, authorizationToken)); > > It is probably a good idea to do the same for the `send(Messages::WebPageProxy::WritePromisedAttachmentToPasteboard(WTFMove(attachmentInfo)));` call above in this function no? I wasn't sure if some sites might write to pasteboard based on script execution as a convenience for the user. I think we are less worried about pasteboard writes than pasteboard reads. But I'm happy to make that change if you think it's warranted.
Chris Dumez
Comment 13 2022-03-15 13:20:32 PDT
(In reply to Brent Fulgham from comment #12) > Comment on attachment 454738 [details] > Patch > > View in context: > https://bugs.webkit.org/attachment.cgi?id=454738&action=review > > >> Source/WebKit/UIProcess/WebPageProxy.h:2397 > >> + bool isValidAuthorizationToken(const String& authorizationToken) const; > > > > Same comment as below about the name being so generic while this is not truly reusable. > > Will do. > > >> Source/WebKit/UIProcess/WebPageProxy.h:2398 > >> + void revokeAuthorizationToken(const String& authorizationToken); > > > > ditto. > > Will do. > > >> Source/WebKit/UIProcess/WebPageProxy.h:2716 > >> + StdSet<uint64_t> m_userGestureTokens; > > > > This is unused I think. > > Whoops! > > >> Source/WebKit/UIProcess/WebPageProxy.h:3074 > >> + HashSet<String> m_validAuthorizationTokens; > > > > I wonder a bit about the naming being so generic. It makes it look like this is directly reusable for other IPCs. However, I don't think that's true with the current implementation. > > I don't think you would want the WebProcess to use an auth token from PerformActionOnElement to do some other operation in the UIProcess that is not the result of the PerformActionOnElement IPC. > > > > E.g. It might need to be a HashMap from the IPC message name to the auth token. Alternatively, we could keep using a HashSet but the auth-token would be specific to the IPC message name (e.g. "PerformActionOnElement-[UUID]"). > > > > If we start using this more (which sounds plausible), then we'll like need something a bit better to better restrict this to certain IPCs. As a result, I would suggest using a more specific name for now (e.g. m_performActionOnElementAuthTokens). > > Alternatively, we can try and solve the "how to make it generic" problem now but I'd be OK just crossing that bridge when we get there and use a specific name for now. > > I'll use m_performActionOnElementAuthTokens for now. > > >> Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm:605 > >> + if (!isValidAuthorizationToken(authorizationToken)) > > > > This does a double HashSet lookup which is unfortunate: once for the contains, and then again for the remove. This is not great practice. > > I'm not even sure this method is needed. I only use it in the > 'saveImageToLibrary', which does a message check using the token. I could > probably do a find and remove in that method without needing this > specialized method. > > >> Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm:3285 > >> + send(Messages::WebPageProxy::SaveImageToLibrary(SharedMemory::IPCHandle { WTFMove(handle), buffer->size() }, authorizationToken)); > > > > It is probably a good idea to do the same for the `send(Messages::WebPageProxy::WritePromisedAttachmentToPasteboard(WTFMove(attachmentInfo)));` call above in this function no? > > I wasn't sure if some sites might write to pasteboard based on script > execution as a convenience for the user. I think we are less worried about > pasteboard writes than pasteboard reads. But I'm happy to make that change > if you think it's warranted. I think we should add the restriction for WritePromisedAttachmentToPasteboard. I checked and the only call site for the WritePromisedAttachmentToPasteboard IPC is in WebPage::performActionOnElement(). WebPage::performActionOnElement() is only called as a result of the WebPage::PerformActionOnElement IPC from the UIProcess. Therefore, I think the check is safe and very similar to the image saving case. It is also not much added complexity I think.
Brent Fulgham
Comment 14 2022-03-15 13:56:17 PDT
(In reply to Chris Dumez from comment #13) > > I think we should add the restriction for > WritePromisedAttachmentToPasteboard. I checked and the only call site for > the WritePromisedAttachmentToPasteboard IPC is in > WebPage::performActionOnElement(). WebPage::performActionOnElement() is only > called as a result of the WebPage::PerformActionOnElement IPC from the > UIProcess. > > Therefore, I think the check is safe and very similar to the image saving > case. It is also not much added complexity I think. Will do!
Brent Fulgham
Comment 15 2022-03-16 10:29:45 PDT
Brent Fulgham
Comment 16 2022-03-16 10:37:08 PDT
Chris Dumez
Comment 17 2022-03-16 10:46:16 PDT
Comment on attachment 454858 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=454858&action=review r=me with changes. > Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm:608 > + sendWithAsyncReply(Messages::WebPage::PerformActionOnElement(action, authorizationToken), [weakThis = WeakPtr { *this }, authorizationToken] () mutable { authorizationToken = WTFMove(authorizationToken) > Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm:3243 > { I think you should just add the following at the beginning of the function: CompletionHandlerCallingScope callCompletionHandler(WTFMove(completionHandler)); Then you wouldn't need to call the completionHandler on every early return (which is a foot-gun).
Brent Fulgham
Comment 18 2022-03-16 10:53:12 PDT
Comment on attachment 454858 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=454858&action=review >> Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm:608 >> + sendWithAsyncReply(Messages::WebPage::PerformActionOnElement(action, authorizationToken), [weakThis = WeakPtr { *this }, authorizationToken] () mutable { > > authorizationToken = WTFMove(authorizationToken) I tried this initially, and it resulted in the message send getting an empty string (I guess because the PerformActionOnElement constructor runs after the 'authorizationToken' is moved into the lambda). >> Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm:3243 >> { > > I think you should just add the following at the beginning of the function: > CompletionHandlerCallingScope callCompletionHandler(WTFMove(completionHandler)); > > Then you wouldn't need to call the completionHandler on every early return (which is a foot-gun). Oh! I was thinking such a thing would be good to have -- I didn't know it existed already. That's great!
Chris Dumez
Comment 19 2022-03-16 10:56:32 PDT
Comment on attachment 454858 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=454858&action=review >>> Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm:608 >>> + sendWithAsyncReply(Messages::WebPage::PerformActionOnElement(action, authorizationToken), [weakThis = WeakPtr { *this }, authorizationToken] () mutable { >> >> authorizationToken = WTFMove(authorizationToken) > > I tried this initially, and it resulted in the message send getting an empty string (I guess because the PerformActionOnElement constructor runs after the 'authorizationToken' is moved into the lambda). Oh, you're right. My bad. I didn't realize it was being used on the same line.
Brent Fulgham
Comment 20 2022-03-16 11:08:34 PDT
Created attachment 454865 [details] Patch for landing
Brent Fulgham
Comment 21 2022-03-16 11:47:31 PDT
Created attachment 454873 [details] Patch for landing
EWS
Comment 22 2022-03-16 12:33:24 PDT
Patch 454865 does not build
Chris Dumez
Comment 23 2022-03-16 12:37:22 PDT
Comment on attachment 454873 [details] Patch for landing View in context: https://bugs.webkit.org/attachment.cgi?id=454873&action=review > Source/WebKit/UIProcess/WebPageProxy.cpp:10195 > +#if PLATFORM(IOS_FAMILY) This looks a bit weird. Seems like the whole writePromisedAttachmentToPasteboard function and (its IPC message) should be protected via `#if PLATFORM(IOS_FAMILY)`, not just the hardening. It makes it look like we're not doing hardening on macOS.
Brent Fulgham
Comment 24 2022-03-16 13:35:26 PDT
Created attachment 454890 [details] Patch for landing
EWS
Comment 25 2022-03-16 15:00:03 PDT
Committed r291371 (248503@main): <https://commits.webkit.org/248503@main> All reviewed patches have been landed. Closing bug and clearing flags on attachment 454890 [details].
Note You need to log in before you can comment on or make changes to this bug.