Bug 211103

Summary: Improve SandboxExtension::HandleArray to reduce boilerplate
Product: WebKit Reporter: Brent Fulgham <bfulgham>
Component: WebKit2Assignee: Brent Fulgham <bfulgham>
Status: RESOLVED FIXED    
Severity: Normal CC: bfulgham, pvollan, tsavell, webkit-bug-importer
Priority: P2 Keywords: InRadar
Version: Safari 13   
Hardware: Unspecified   
OS: Unspecified   
See Also: https://bugs.webkit.org/show_bug.cgi?id=211253
Attachments:
Description Flags
Patch
none
Patch
none
Patch
none
Patch
none
Patch for landing none

Description Brent Fulgham 2020-04-27 16:47:29 PDT
There are a number of boilerplate patterns needed when using SandboxExtension::HandleArray. We could make these simpler and less error prone by improving the class.
Comment 1 Radar WebKit Bug Importer 2020-04-28 11:42:06 PDT
<rdar://problem/62533632>
Comment 2 Brent Fulgham 2020-04-28 12:11:25 PDT
Created attachment 397866 [details]
Patch
Comment 3 Per Arne Vollan 2020-04-28 12:41:44 PDT
Comment on attachment 397866 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=397866&action=review

I think this is a great idea. R=me.

> Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm:296
> +Optional<SandboxExtension::HandleArray> SandboxExtension::createHandlesForFiles(const String& logLabel, const Vector<String>& paths, Type type)

Since it seems this method is only called with the ReadOnly type, maybe the type argument can be removed?

> Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm:299
> +    if (paths.isEmpty())
> +        return WTF::nullopt;

If the method did not return an Optional, but only a HandleArray, this if statement could be removed.

> Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm:304
> +    for (size_t i = 0; i < paths.size(); ++i) {

Can this be a modern for loop?

> Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm:306
> +        if (!SandboxExtension::createHandle(paths[i], type, handleArray[i])) {
> +            // This can legitimately fail if a directory containing the file is deleted after the file was chosen.

Perhaps you could add an ASSERT_NOT_REACHED() here, so we can catch it in the debugger?

> Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm:379
> +Optional<SandboxExtension::HandleArray> SandboxExtension::createHandlesForMachLookup(const Vector<String>& services, Optional<audit_token_t> auditToken, OptionSet<Flags> flags)

Could this simply return a HandleArray?

> Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm:387
> +    for (size_t i = 0; i < services.size(); ++i) {

Can a modern for loop be used?

> Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm:389
> +        if (!SandboxExtension::createHandleForMachLookup(services[i], auditToken, handleArray[i], flags))
> +            return WTF::nullopt;

I think it would be good to keep going, even if this call should fail for a service. Also, I think it would be good to add an ASSERT_NOT_REACHED() in the case it fails.

> Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm:452
> +        if (auto mediaExtensionHandles = SandboxExtension::createHandlesForMachLookup(mediaRelatedMachServices(), WTF::nullopt))
> +            parameters.mediaExtensionHandles = WTFMove(*mediaExtensionHandles);

If the function simply returned a HandleArray, this could be just one line.
Comment 4 Brent Fulgham 2020-04-29 16:12:31 PDT
Created attachment 398006 [details]
Patch
Comment 5 Brent Fulgham 2020-04-29 16:35:45 PDT
Comment on attachment 397866 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=397866&action=review

>> Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm:296
>> +Optional<SandboxExtension::HandleArray> SandboxExtension::createHandlesForFiles(const String& logLabel, const Vector<String>& paths, Type type)
> 
> Since it seems this method is only called with the ReadOnly type, maybe the type argument can be removed?

Good point. I'll try that.

>> Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm:299
>> +        return WTF::nullopt;
> 
> If the method did not return an Optional, but only a HandleArray, this if statement could be removed.

Will do!

>> Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm:304
>> +    for (size_t i = 0; i < paths.size(); ++i) {
> 
> Can this be a modern for loop?

Sure -- that way I can only fill the handle array with paths that succeed, rather than null handles. I'm not sure if that ever happens, but it doesn't seem like it's valid to consume them.

>> Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm:379
>> +Optional<SandboxExtension::HandleArray> SandboxExtension::createHandlesForMachLookup(const Vector<String>& services, Optional<audit_token_t> auditToken, OptionSet<Flags> flags)
> 
> Could this simply return a HandleArray?

Sure.

>> Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm:387
>> +    for (size_t i = 0; i < services.size(); ++i) {
> 
> Can a modern for loop be used?

Will do.

>> Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm:389
>> +            return WTF::nullopt;
> 
> I think it would be good to keep going, even if this call should fail for a service. Also, I think it would be good to add an ASSERT_NOT_REACHED() in the case it fails.

Will do!

>> Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm:452
>> +            parameters.mediaExtensionHandles = WTFMove(*mediaExtensionHandles);
> 
> If the function simply returned a HandleArray, this could be just one line.

Fixed!
Comment 6 Brent Fulgham 2020-04-29 16:52:02 PDT
Created attachment 398009 [details]
Patch
Comment 7 Brent Fulgham 2020-04-29 18:20:45 PDT
Created attachment 398014 [details]
Patch
Comment 8 Per Arne Vollan 2020-04-29 19:04:54 PDT
Comment on attachment 398014 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=398014&action=review

R=me.

> Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm:296
> +static SandboxExtension::HandleArray createHandlesForInput(const Vector<String>& services, Function<bool(const String&, SandboxExtension::Handle& handle)>&& createFunction)

The name of the first parameter does not seem to describe all variations of possible input types, since it can be services, paths, and IOKit classes. Perhaps this function could be renamed 'createHandlesForResources', and the first parameter could be renamed 'resources', or something similar?

> Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm:300
> +    handleArray.allocate(services.size());

Is allocating with size 0 gracefully handled by HandleArray? On a related note, would it be better to typedef HandleArray to Vector<Handle>? That's outside the scope of this patch, though. In that case, allocating up front would not be needed, we could just append in the loop.

> Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm:401
> +        parameters.dynamicIOKitExtensionHandles = createHandlesForIOKitClassExtensions(agxCompilerClasses(), WTF::nullopt);

I think this should be SandboxExtension::createHandlesForIOKitClassExtensions(agxCompilerClasses(), WTF::nullopt);
Comment 9 Brent Fulgham 2020-04-29 19:37:25 PDT
Comment on attachment 398014 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=398014&action=review

>> Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm:296
>> +static SandboxExtension::HandleArray createHandlesForInput(const Vector<String>& services, Function<bool(const String&, SandboxExtension::Handle& handle)>&& createFunction)
> 
> The name of the first parameter does not seem to describe all variations of possible input types, since it can be services, paths, and IOKit classes. Perhaps this function could be renamed 'createHandlesForResources', and the first parameter could be renamed 'resources', or something similar?

Good idea.

>> Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm:300
>> +    handleArray.allocate(services.size());
> 
> Is allocating with size 0 gracefully handled by HandleArray? On a related note, would it be better to typedef HandleArray to Vector<Handle>? That's outside the scope of this patch, though. In that case, allocating up front would not be needed, we could just append in the loop.

Maybe. I'll take a look at that in a future patch.

>> Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm:401
>> +        parameters.dynamicIOKitExtensionHandles = createHandlesForIOKitClassExtensions(agxCompilerClasses(), WTF::nullopt);
> 
> I think this should be SandboxExtension::createHandlesForIOKitClassExtensions(agxCompilerClasses(), WTF::nullopt);

Whoops!
Comment 10 Brent Fulgham 2020-04-29 19:38:49 PDT
Created attachment 398022 [details]
Patch for landing
Comment 11 EWS 2020-04-29 20:03:22 PDT
Committed r260932: <https://trac.webkit.org/changeset/260932>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 398022 [details].
Comment 12 Per Arne Vollan 2020-04-30 08:03:36 PDT
Comment on attachment 398022 [details]
Patch for landing

View in context: https://bugs.webkit.org/attachment.cgi?id=398022&action=review

> Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm:313
> +SandboxExtension::HandleArray SandboxExtension::createReadOnlyHandlesForFiles(const String& logLabel, const Vector<String>& paths)

You could also pass an error handler function instead of the log label String, which would be invoked for each error. The caller would then be responsible for customizing the logging.
Comment 13 Truitt Savell 2020-04-30 08:58:51 PDT
It looks like the changes in https://trac.webkit.org/changeset/260932/webkit

broke TestWebKitAPI.WebKit.UploadDirectory on Mac Debug with an Assert

history:
https://results.webkit.org/?suite=api-tests&suite=api-tests&suite=api-tests&suite=api-tests&test=TestWebKitAPI.WebKit.UploadDirectory&test=TestWebKitAPI.URLSchemeHandler.DisableCORSCredentials&test=TestWebKitAPI.MultipleClientCertificateConnections.NonPersistentDataStore&test=TestWebKitAPI.Fullscreen.Delegate

Crash Log:
Crashed

    TestWebKitAPI.WebKit.UploadDirectory
        ASSERTION FAILED: ok
        /Volumes/Data/slave/catalina-debug/build/Source/WebKit/Shared/Cocoa/SandboxExtensionCocoa.mm(506) : static bool WebKit::SandboxExtension::consumePermanently(const WebKit::SandboxExtension::HandleArray &)
        1   0x108267609 WTFCrash
        2   0x11147aeeb WTFCrashWithInfo(int, char const*, char const*, int)
        3   0x111c649ac WebKit::SandboxExtension::consumePermanently(WebKit::SandboxExtension::HandleArray const&)
        4   0x11171c71b IPC::FormDataReference::decode(IPC::Decoder&)
        5   0x11171c49c WTF::Optional<IPC::FormDataReference> IPC::ArgumentCoder<IPC::FormDataReference>::decode<IPC::FormDataReference, (void*)0>(IPC::Decoder&)
Comment 15 Truitt Savell 2020-04-30 10:53:34 PDT
It looks like this also broke these two tests:
http/tests/misc/form-submit-file-cross-site-redirect.html
http/tests/misc/form-submit-file-cross-site.html

History:
https://results.webkit.org/?suite=layout-tests&suite=layout-tests&test=http%2Ftests%2Fmisc%2Fform-submit-file-cross-site.html&test=http%2Ftests%2Fmisc%2Fform-submit-file-cross-site-redirect.html

Results:
https://build.webkit.org/results/Apple-Catalina-Debug-WK2-Tests/r260952%20(3943)/results.html
Comment 16 Brent Fulgham 2020-04-30 11:10:00 PDT
(In reply to Truitt Savell from comment #14)
> Better history link:
> https://results.webkit.org/?suite=api-tests&test=TestWebKitAPI.WebKit.
> UploadDirectory

Interesting. This test is triggering a new assert I added to detect when we are unable to consume a sandbox extension.

In this case, an extension is being sent from the WebContent process (which cannot issue extensions) to the UIProcess, and the UIProcess is correctly reporting that it cannot consume this extension.

I suspect the code resulting in a sandbox extension being passed from the WCP to the UIProcess is wrong, so this may have uncovered a real bug.

Note: We should consider disabling this API test until we fix the underlying bug. We should not roll this patch out.
Comment 17 Brent Fulgham 2020-04-30 11:11:41 PDT
(In reply to Truitt Savell from comment #15)
> It looks like this also broke these two tests:
> http/tests/misc/form-submit-file-cross-site-redirect.html
> http/tests/misc/form-submit-file-cross-site.html
> 
> History:
> https://results.webkit.org/?suite=layout-tests&suite=layout-
> tests&test=http%2Ftests%2Fmisc%2Fform-submit-file-cross-site.
> html&test=http%2Ftests%2Fmisc%2Fform-submit-file-cross-site-redirect.html
> 
> Results:
> https://build.webkit.org/results/Apple-Catalina-Debug-WK2-Tests/
> r260952%20(3943)/results.html

These two crashes are the same issue. WCP is sending a sandbox extension to the UIProcess.

There is never a case where the WCP has access to something the UIProcess does not. Access always flows from the UIProcess to its helper processes.
Comment 18 Truitt Savell 2020-04-30 14:17:08 PDT
New bug to track this regression: https://bugs.webkit.org/show_bug.cgi?id=211253