WKContentRuleLists should have a maximum FileProtection of CompleteUnlessOpen
authorachristensen@apple.com <achristensen@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 24 Apr 2019 17:46:28 +0000 (17:46 +0000)
committerachristensen@apple.com <achristensen@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 24 Apr 2019 17:46:28 +0000 (17:46 +0000)
https://bugs.webkit.org/show_bug.cgi?id=197078
<rdar://problem/49564348>

Reviewed by Geoff Garen.

Source/WebKit:

r242735 was a fix for crashes when using mmap'd memory in apps with default FileProtection of NSFileProtectionComplete.
It is more memory efficient and just as secure to reduce the FileProtection of these files to NSFileProtectionCompleteUnlessOpen.

* NetworkProcess/cache/NetworkCacheFileSystem.cpp:
(WebKit::NetworkCache::isSafeToUseMemoryMapForPath):
(WebKit::NetworkCache::makeSafeToUseMemoryMapForPath):
(WebKit::NetworkCache::pathRegisteredAsUnsafeToMemoryMapForTesting): Deleted.
(WebKit::NetworkCache::registerPathAsUnsafeToMemoryMapForTesting): Deleted.
* NetworkProcess/cache/NetworkCacheFileSystem.h:
* NetworkProcess/cache/NetworkCacheFileSystemCocoa.mm: Added.
(WebKit::NetworkCache::isSafeToUseMemoryMapForPath):
(WebKit::NetworkCache::makeSafeToUseMemoryMapForPath):
* Shared/WebCompiledContentRuleList.cpp:
(WebKit::WebCompiledContentRuleList::conditionsApplyOnlyToDomain const):
(WebKit::WebCompiledContentRuleList::filtersWithoutConditionsBytecode const):
(WebKit::WebCompiledContentRuleList::filtersWithConditionsBytecode const):
(WebKit::WebCompiledContentRuleList::topURLFiltersBytecode const):
(WebKit::WebCompiledContentRuleList::actions const):
(WebKit::WebCompiledContentRuleList::usesCopiedMemory const): Deleted.
* Shared/WebCompiledContentRuleList.h:
* Shared/WebCompiledContentRuleListData.cpp:
(WebKit::WebCompiledContentRuleListData::encode const):
(WebKit::WebCompiledContentRuleListData::decode):
(WebKit::WebCompiledContentRuleListData::size const): Deleted.
(WebKit::WebCompiledContentRuleListData::dataPointer const): Deleted.
* Shared/WebCompiledContentRuleListData.h:
(WebKit::WebCompiledContentRuleListData::WebCompiledContentRuleListData):
* SourcesCocoa.txt:
* UIProcess/API/APIContentRuleList.cpp:
(API::ContentRuleList::usesCopiedMemory const): Deleted.
* UIProcess/API/APIContentRuleList.h:
* UIProcess/API/APIContentRuleListStore.cpp:
(API::openAndMapOrCopyContentRuleList):
(API::compiledToFile):
(API::createExtension):
(API::ContentRuleListStore::getContentRuleListSource):
(API::ContentRuleListStore::readContentsOfFile): Deleted.
(API::MappedOrCopiedData::dataPointer const): Deleted.
* UIProcess/API/APIContentRuleListStore.h:
* UIProcess/API/Cocoa/APIContentRuleListStoreCocoa.mm:
(API::ContentRuleListStore::readContentsOfFile): Deleted.
* UIProcess/API/Cocoa/WKContentRuleListStore.mm:
(+[WKContentRuleListStore _registerPathAsUnsafeToMemoryMapForTesting:]): Deleted.
* UIProcess/API/Cocoa/WKContentRuleListStorePrivate.h:
* UIProcess/API/Cocoa/_WKUserContentFilter.mm:
(-[_WKUserContentFilter usesCopiedMemory]): Deleted.
* UIProcess/API/Cocoa/_WKUserContentFilterPrivate.h:
* WebKit.xcodeproj/project.pbxproj:

Tools:

* TestWebKitAPI/Tests/WebKitCocoa/WKContentExtensionStore.mm:
(TEST_F):
(-[TestSchemeHandlerSubresourceShouldBeBlocked webView:startURLSchemeTask:]): Deleted.
(-[TestSchemeHandlerSubresourceShouldBeBlocked webView:stopURLSchemeTask:]): Deleted.
Unfortunately, setting the NSFileProtectionKey attribute is only supported on iOS devices.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@244597 268f45cc-cd09-0410-ab3c-d52691b4dbfc

21 files changed:
Source/WebKit/ChangeLog
Source/WebKit/NetworkProcess/cache/NetworkCacheFileSystem.cpp
Source/WebKit/NetworkProcess/cache/NetworkCacheFileSystem.h
Source/WebKit/NetworkProcess/cache/NetworkCacheFileSystemCocoa.mm [new file with mode: 0644]
Source/WebKit/Shared/WebCompiledContentRuleList.cpp
Source/WebKit/Shared/WebCompiledContentRuleList.h
Source/WebKit/Shared/WebCompiledContentRuleListData.cpp
Source/WebKit/Shared/WebCompiledContentRuleListData.h
Source/WebKit/SourcesCocoa.txt
Source/WebKit/UIProcess/API/APIContentRuleList.cpp
Source/WebKit/UIProcess/API/APIContentRuleList.h
Source/WebKit/UIProcess/API/APIContentRuleListStore.cpp
Source/WebKit/UIProcess/API/APIContentRuleListStore.h
Source/WebKit/UIProcess/API/Cocoa/APIContentRuleListStoreCocoa.mm
Source/WebKit/UIProcess/API/Cocoa/WKContentRuleListStore.mm
Source/WebKit/UIProcess/API/Cocoa/WKContentRuleListStorePrivate.h
Source/WebKit/UIProcess/API/Cocoa/_WKUserContentFilter.mm
Source/WebKit/UIProcess/API/Cocoa/_WKUserContentFilterPrivate.h
Source/WebKit/WebKit.xcodeproj/project.pbxproj
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebKitCocoa/WKContentExtensionStore.mm

index a6ace39..fe7989a 100644 (file)
@@ -1,3 +1,60 @@
+2019-04-24  Alex Christensen  <achristensen@webkit.org>
+
+        WKContentRuleLists should have a maximum FileProtection of CompleteUnlessOpen
+        https://bugs.webkit.org/show_bug.cgi?id=197078
+        <rdar://problem/49564348>
+
+        Reviewed by Geoff Garen.
+
+        r242735 was a fix for crashes when using mmap'd memory in apps with default FileProtection of NSFileProtectionComplete.
+        It is more memory efficient and just as secure to reduce the FileProtection of these files to NSFileProtectionCompleteUnlessOpen.
+
+        * NetworkProcess/cache/NetworkCacheFileSystem.cpp:
+        (WebKit::NetworkCache::isSafeToUseMemoryMapForPath):
+        (WebKit::NetworkCache::makeSafeToUseMemoryMapForPath):
+        (WebKit::NetworkCache::pathRegisteredAsUnsafeToMemoryMapForTesting): Deleted.
+        (WebKit::NetworkCache::registerPathAsUnsafeToMemoryMapForTesting): Deleted.
+        * NetworkProcess/cache/NetworkCacheFileSystem.h:
+        * NetworkProcess/cache/NetworkCacheFileSystemCocoa.mm: Added.
+        (WebKit::NetworkCache::isSafeToUseMemoryMapForPath):
+        (WebKit::NetworkCache::makeSafeToUseMemoryMapForPath):
+        * Shared/WebCompiledContentRuleList.cpp:
+        (WebKit::WebCompiledContentRuleList::conditionsApplyOnlyToDomain const):
+        (WebKit::WebCompiledContentRuleList::filtersWithoutConditionsBytecode const):
+        (WebKit::WebCompiledContentRuleList::filtersWithConditionsBytecode const):
+        (WebKit::WebCompiledContentRuleList::topURLFiltersBytecode const):
+        (WebKit::WebCompiledContentRuleList::actions const):
+        (WebKit::WebCompiledContentRuleList::usesCopiedMemory const): Deleted.
+        * Shared/WebCompiledContentRuleList.h:
+        * Shared/WebCompiledContentRuleListData.cpp:
+        (WebKit::WebCompiledContentRuleListData::encode const):
+        (WebKit::WebCompiledContentRuleListData::decode):
+        (WebKit::WebCompiledContentRuleListData::size const): Deleted.
+        (WebKit::WebCompiledContentRuleListData::dataPointer const): Deleted.
+        * Shared/WebCompiledContentRuleListData.h:
+        (WebKit::WebCompiledContentRuleListData::WebCompiledContentRuleListData):
+        * SourcesCocoa.txt:
+        * UIProcess/API/APIContentRuleList.cpp:
+        (API::ContentRuleList::usesCopiedMemory const): Deleted.
+        * UIProcess/API/APIContentRuleList.h:
+        * UIProcess/API/APIContentRuleListStore.cpp:
+        (API::openAndMapOrCopyContentRuleList):
+        (API::compiledToFile):
+        (API::createExtension):
+        (API::ContentRuleListStore::getContentRuleListSource):
+        (API::ContentRuleListStore::readContentsOfFile): Deleted.
+        (API::MappedOrCopiedData::dataPointer const): Deleted.
+        * UIProcess/API/APIContentRuleListStore.h:
+        * UIProcess/API/Cocoa/APIContentRuleListStoreCocoa.mm:
+        (API::ContentRuleListStore::readContentsOfFile): Deleted.
+        * UIProcess/API/Cocoa/WKContentRuleListStore.mm:
+        (+[WKContentRuleListStore _registerPathAsUnsafeToMemoryMapForTesting:]): Deleted.
+        * UIProcess/API/Cocoa/WKContentRuleListStorePrivate.h:
+        * UIProcess/API/Cocoa/_WKUserContentFilter.mm:
+        (-[_WKUserContentFilter usesCopiedMemory]): Deleted.
+        * UIProcess/API/Cocoa/_WKUserContentFilterPrivate.h:
+        * WebKit.xcodeproj/project.pbxproj:
+
 2019-04-24  David Kilzer  <ddkilzer@apple.com>
 
         Fix build due to missing SPI declaration of kAXSFullKeyboardAccessEnabledNotification
index 1fe8849..7f7b466 100644 (file)
@@ -145,51 +145,15 @@ void updateFileModificationTimeIfNeeded(const String& path)
 #endif
 }
 
-static String& pathRegisteredAsUnsafeToMemoryMapForTesting()
+#if !PLATFORM(IOS_FAMILY)
+bool isSafeToUseMemoryMapForPath(const String&)
 {
-    static NeverDestroyed<String> path;
-    return path.get();
+    return true;
 }
-
-void registerPathAsUnsafeToMemoryMapForTesting(const String& path)
+void makeSafeToUseMemoryMapForPath(const String&)
 {
-    pathRegisteredAsUnsafeToMemoryMapForTesting() = path;
 }
-
-    
-bool isSafeToUseMemoryMapForPath(const String& path)
-{
-    if (path == pathRegisteredAsUnsafeToMemoryMapForTesting())
-        return false;
-
-#if PLATFORM(IOS_FAMILY) && !PLATFORM(IOS_FAMILY_SIMULATOR)
-    struct {
-        uint32_t length;
-        uint32_t protectionClass;
-    } attrBuffer;
-
-    attrlist attrList = { };
-    attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
-    attrList.commonattr = ATTR_CMN_DATA_PROTECT_FLAGS;
-    int32_t error = getattrlist(FileSystem::fileSystemRepresentation(path).data(), &attrList, &attrBuffer, sizeof(attrBuffer), FSOPT_NOFOLLOW);
-    if (error) {
-        RELEASE_LOG_ERROR(Network, "Unable to get cache directory protection class, disabling use of shared mapped memory");
-        return false;
-    }
-
-    // For stricter protection classes shared maps could disappear when device is locked.
-    const uint32_t fileProtectionCompleteUntilFirstUserAuthentication = 3;
-    bool isSafe = attrBuffer.protectionClass >= fileProtectionCompleteUntilFirstUserAuthentication;
-
-    if (!isSafe)
-        RELEASE_LOG(Network, "Disallowing use of shared mapped memory due to container protection class %u", attrBuffer.protectionClass);
-
-    return isSafe;
-#else
-    UNUSED_PARAM(path);
-    return true;
 #endif
-}
 
 }
 }
index c769707..cd8995c 100644 (file)
@@ -42,8 +42,8 @@ struct FileTimes {
 FileTimes fileTimes(const String& path);
 void updateFileModificationTimeIfNeeded(const String& path);
 
-bool isSafeToUseMemoryMapForPath(const String& path);
-void registerPathAsUnsafeToMemoryMapForTesting(const String&);
+bool isSafeToUseMemoryMapForPath(const String&);
+void makeSafeToUseMemoryMapForPath(const String&);
 
 }
 }
diff --git a/Source/WebKit/NetworkProcess/cache/NetworkCacheFileSystemCocoa.mm b/Source/WebKit/NetworkProcess/cache/NetworkCacheFileSystemCocoa.mm
new file mode 100644 (file)
index 0000000..9f68fe8
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "NetworkCacheFileSystem.h"
+
+#include "Logging.h"
+#include <wtf/Assertions.h>
+
+namespace WebKit {
+namespace NetworkCache {
+
+#if PLATFORM(IOS_FAMILY)
+bool isSafeToUseMemoryMapForPath(const String& path)
+{
+    // FIXME: For the network cache we should either use makeSafeToUseMemoryMapForPath instead of this
+    // or we should listen to UIApplicationProtectedDataWillBecomeUnavailable and unmap files.
+    NSError *error = nil;
+    NSDictionary<NSFileAttributeKey, id> *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error];
+    if (error) {
+        RELEASE_LOG_ERROR(Network, "Unable to get cache directory protection class, disabling use of shared mapped memory");
+        return false;
+    }
+    if ([[attributes objectForKey:NSFileProtectionKey] isEqualToString:NSFileProtectionComplete]) {
+        RELEASE_LOG(Network, "Disallowing use of shared mapped memory due to container protection class NSFileProtectionComplete");
+        return false;
+    }
+    return true;
+}
+
+void makeSafeToUseMemoryMapForPath(const String& path)
+{
+    if (isSafeToUseMemoryMapForPath(path))
+        return;
+
+    NSError *error = nil;
+    BOOL success = [[NSFileManager defaultManager] setAttributes:@{ NSFileProtectionKey: NSFileProtectionCompleteUnlessOpen } ofItemAtPath:path error:&error];
+    ASSERT(!error);
+    ASSERT_UNUSED(success, success);
+}
+#endif // PLATFORM(IOS_FAMILY)
+
+}
+}
index 6a67ba3..85f223c 100644 (file)
@@ -44,19 +44,14 @@ WebCompiledContentRuleList::~WebCompiledContentRuleList()
 {
 }
 
-bool WebCompiledContentRuleList::usesCopiedMemory() const
-{
-    return WTF::holds_alternative<RefPtr<WebCore::SharedBuffer>>(m_data.data);
-}
-
 bool WebCompiledContentRuleList::conditionsApplyOnlyToDomain() const
 {
-    return *reinterpret_cast<const uint32_t*>(reinterpret_cast<const uint8_t*>(m_data.dataPointer()) + m_data.conditionsApplyOnlyToDomainOffset);
+    return *reinterpret_cast<const uint32_t*>(reinterpret_cast<const uint8_t*>(m_data.data->data()) + m_data.conditionsApplyOnlyToDomainOffset);
 }
 
 const WebCore::ContentExtensions::DFABytecode* WebCompiledContentRuleList::filtersWithoutConditionsBytecode() const
 {
-    return static_cast<const WebCore::ContentExtensions::DFABytecode*>(m_data.dataPointer()) + m_data.filtersWithoutConditionsBytecodeOffset;
+    return static_cast<const WebCore::ContentExtensions::DFABytecode*>(m_data.data->data()) + m_data.filtersWithoutConditionsBytecodeOffset;
 }
 
 unsigned WebCompiledContentRuleList::filtersWithoutConditionsBytecodeLength() const
@@ -66,7 +61,7 @@ unsigned WebCompiledContentRuleList::filtersWithoutConditionsBytecodeLength() co
 
 const WebCore::ContentExtensions::DFABytecode* WebCompiledContentRuleList::filtersWithConditionsBytecode() const
 {
-    return static_cast<const WebCore::ContentExtensions::DFABytecode*>(m_data.dataPointer()) + m_data.filtersWithConditionsBytecodeOffset;
+    return static_cast<const WebCore::ContentExtensions::DFABytecode*>(m_data.data->data()) + m_data.filtersWithConditionsBytecodeOffset;
 }
 
 unsigned WebCompiledContentRuleList::filtersWithConditionsBytecodeLength() const
@@ -76,7 +71,7 @@ unsigned WebCompiledContentRuleList::filtersWithConditionsBytecodeLength() const
 
 const WebCore::ContentExtensions::DFABytecode* WebCompiledContentRuleList::topURLFiltersBytecode() const
 {
-    return static_cast<const WebCore::ContentExtensions::DFABytecode*>(m_data.dataPointer()) + m_data.topURLFiltersBytecodeOffset;
+    return static_cast<const WebCore::ContentExtensions::DFABytecode*>(m_data.data->data()) + m_data.topURLFiltersBytecodeOffset;
 }
 
 unsigned WebCompiledContentRuleList::topURLFiltersBytecodeLength() const
@@ -86,7 +81,7 @@ unsigned WebCompiledContentRuleList::topURLFiltersBytecodeLength() const
 
 const WebCore::ContentExtensions::SerializedActionByte* WebCompiledContentRuleList::actions() const
 {
-    return static_cast<const WebCore::ContentExtensions::SerializedActionByte*>(m_data.dataPointer()) + m_data.actionsOffset;
+    return static_cast<const WebCore::ContentExtensions::SerializedActionByte*>(m_data.data->data()) + m_data.actionsOffset;
 }
 
 unsigned WebCompiledContentRuleList::actionsLength() const
index bdf5894..da3dcb7 100644 (file)
@@ -40,8 +40,6 @@ public:
 
     const WebCompiledContentRuleListData& data() const { return m_data; }
 
-    bool usesCopiedMemory() const;
-    
 private:
     WebCompiledContentRuleList(WebCompiledContentRuleListData&&);
 
index bb67f14..f1a597e 100644 (file)
 
 namespace WebKit {
 
-size_t WebCompiledContentRuleListData::size() const
-{
-    return WTF::switchOn(data, [] (const auto& sharedMemoryOrBuffer) {
-        return sharedMemoryOrBuffer->size();
-    });
-}
-
-const void* WebCompiledContentRuleListData::dataPointer() const
-{
-    return WTF::switchOn(data, [] (const auto& sharedMemoryOrBuffer) -> const void* {
-        return sharedMemoryOrBuffer->data();
-    });
-}
-
 void WebCompiledContentRuleListData::encode(IPC::Encoder& encoder) const
 {
-    if (auto sharedMemory = WTF::get_if<RefPtr<SharedMemory>>(data)) {
-        encoder << true;
-        SharedMemory::Handle handle;
-        sharedMemory->get()->createHandle(handle, SharedMemory::Protection::ReadOnly);
-        encoder << handle;
-    } else {
-        encoder << false;
-        encoder << IPC::SharedBufferDataReference { *WTF::get<RefPtr<WebCore::SharedBuffer>>(data) };
-    }
+    SharedMemory::Handle handle;
+    data->createHandle(handle, SharedMemory::Protection::ReadOnly);
+    encoder << handle;
 
     encoder << conditionsApplyOnlyToDomainOffset;
     encoder << actionsOffset;
@@ -73,24 +53,10 @@ void WebCompiledContentRuleListData::encode(IPC::Encoder& encoder) const
 
 Optional<WebCompiledContentRuleListData> WebCompiledContentRuleListData::decode(IPC::Decoder& decoder)
 {
-    Variant<RefPtr<SharedMemory>, RefPtr<WebCore::SharedBuffer>> data;
-
-    Optional<bool> hasSharedMemory;
-    decoder >> hasSharedMemory;
-    if (!hasSharedMemory)
+    SharedMemory::Handle handle;
+    if (!decoder.decode(handle))
         return WTF::nullopt;
-
-    if (*hasSharedMemory) {
-        SharedMemory::Handle handle;
-        if (!decoder.decode(handle))
-            return WTF::nullopt;
-        data = { SharedMemory::map(handle, SharedMemory::Protection::ReadOnly) };
-    } else {
-        IPC::DataReference dataReference;
-        if (!decoder.decode(dataReference))
-            return WTF::nullopt;
-        data = { RefPtr<WebCore::SharedBuffer>(WebCore::SharedBuffer::create(dataReference.data(), dataReference.size())) };
-    }
+    RefPtr<SharedMemory> data = SharedMemory::map(handle, SharedMemory::Protection::ReadOnly);
 
     Optional<unsigned> conditionsApplyOnlyToDomainOffset;
     decoder >> conditionsApplyOnlyToDomainOffset;
index fe5c974..1f03131 100644 (file)
@@ -41,7 +41,7 @@ namespace WebKit {
 
 class WebCompiledContentRuleListData {
 public:
-    WebCompiledContentRuleListData(Variant<RefPtr<SharedMemory>, RefPtr<WebCore::SharedBuffer>>&& data, unsigned conditionsApplyOnlyToDomainOffset, unsigned actionsOffset, unsigned actionsSize, unsigned filtersWithoutConditionsBytecodeOffset, unsigned filtersWithoutConditionsBytecodeSize, unsigned filtersWithConditionsBytecodeOffset, unsigned filtersWithConditionsBytecodeSize, unsigned topURLFiltersBytecodeOffset, unsigned topURLFiltersBytecodeSize)
+    WebCompiledContentRuleListData(RefPtr<SharedMemory>&& data, unsigned conditionsApplyOnlyToDomainOffset, unsigned actionsOffset, unsigned actionsSize, unsigned filtersWithoutConditionsBytecodeOffset, unsigned filtersWithoutConditionsBytecodeSize, unsigned filtersWithConditionsBytecodeOffset, unsigned filtersWithConditionsBytecodeSize, unsigned topURLFiltersBytecodeOffset, unsigned topURLFiltersBytecodeSize)
         : data(WTFMove(data))
         , conditionsApplyOnlyToDomainOffset(conditionsApplyOnlyToDomainOffset)
         , actionsOffset(actionsOffset)
@@ -58,10 +58,7 @@ public:
     void encode(IPC::Encoder&) const;
     static Optional<WebCompiledContentRuleListData> decode(IPC::Decoder&);
 
-    size_t size() const;
-    const void* dataPointer() const;
-    
-    Variant<RefPtr<SharedMemory>, RefPtr<WebCore::SharedBuffer>> data;
+    RefPtr<SharedMemory> data;
     unsigned conditionsApplyOnlyToDomainOffset { 0 };
     unsigned actionsOffset { 0 };
     unsigned actionsSize { 0 };
index fd38da3..b404e3a 100644 (file)
@@ -22,6 +22,7 @@
 // THE POSSIBILITY OF SUCH DAMAGE.
 
 NetworkProcess/cache/NetworkCacheDataCocoa.mm
+NetworkProcess/cache/NetworkCacheFileSystemCocoa.mm
 NetworkProcess/cache/NetworkCacheIOChannelCocoa.mm
 
 NetworkProcess/cocoa/NetworkActivityTrackerCocoa.mm
index df77ffa..b055633 100644 (file)
@@ -43,11 +43,6 @@ ContentRuleList::~ContentRuleList()
 {
 }
 
-bool ContentRuleList::usesCopiedMemory() const
-{
-    return m_compiledRuleList->usesCopiedMemory();
-}
-
 } // namespace API
 
 #endif // ENABLE(CONTENT_EXTENSIONS)
index 2e285d5..1559910 100644 (file)
@@ -49,8 +49,6 @@ public:
     const WTF::String& name() const { return m_name; }
     const WebKit::WebCompiledContentRuleList& compiledRuleList() const { return m_compiledRuleList.get(); }
 
-    bool usesCopiedMemory() const;
-
 private:
     WTF::String m_name;
     Ref<WebKit::WebCompiledContentRuleList> m_compiledRuleList;
index a4e1598..8249aa1 100644 (file)
@@ -201,40 +201,14 @@ static Optional<ContentRuleListMetaData> decodeContentRuleListMetaData(const T&
     return metaData;
 }
 
-#if !PLATFORM(COCOA)
-RefPtr<WebCore::SharedBuffer> ContentRuleListStore::readContentsOfFile(const WTF::String& filePath)
-{
-    ASSERT_NOT_REACHED();
-    return nullptr;
-}
-#endif
-
-struct MappedOrCopiedData {
+struct MappedData {
     ContentRuleListMetaData metaData;
-    Variant<WebKit::NetworkCache::Data, RefPtr<WebCore::SharedBuffer>> data;
-    
-    const uint8_t* dataPointer() const
-    {
-        return WTF::switchOn(data, [] (const WebKit::NetworkCache::Data& data) {
-            return data.data();
-        }, [] (const RefPtr<WebCore::SharedBuffer>& sharedBuffer) {
-            return reinterpret_cast<const uint8_t*>(sharedBuffer->data());
-        });
-    }
+    WebKit::NetworkCache::Data data;
 };
 
-static Optional<MappedOrCopiedData> openAndMapOrCopyContentRuleList(const WTF::String& path)
+static Optional<MappedData> openAndMapOrCopyContentRuleList(const WTF::String& path)
 {
-    if (!WebKit::NetworkCache::isSafeToUseMemoryMapForPath(path)) {
-        RefPtr<WebCore::SharedBuffer> buffer = ContentRuleListStore::readContentsOfFile(path);
-        if (!buffer)
-            return WTF::nullopt;
-        auto metaData = decodeContentRuleListMetaData(*buffer);
-        if (!metaData)
-            return WTF::nullopt;
-        return {{ WTFMove(*metaData), { buffer.releaseNonNull() }}};
-    }
-
+    WebKit::NetworkCache::makeSafeToUseMemoryMapForPath(path);
     WebKit::NetworkCache::Data fileData = mapFile(fileSystemRepresentation(path).data());
     if (fileData.isNull())
         return WTF::nullopt;
@@ -258,7 +232,7 @@ static bool writeDataToFile(const WebKit::NetworkCache::Data& fileData, Platform
     return success;
 }
 
-static Expected<MappedOrCopiedData, std::error_code> compiledToFile(WTF::String&& json, Vector<WebCore::ContentExtensions::ContentExtensionRule>&& parsedRules, const WTF::String& finalFilePath)
+static Expected<MappedData, std::error_code> compiledToFile(WTF::String&& json, Vector<WebCore::ContentExtensions::ContentExtensionRule>&& parsedRules, const WTF::String& finalFilePath)
 {
     using namespace WebCore::ContentExtensions;
 
@@ -411,34 +385,23 @@ static Expected<MappedOrCopiedData, std::error_code> compiledToFile(WTF::String&
         WTFLogAlways("Content Rule List compiling failed: Moving file failed.");
         return makeUnexpected(ContentRuleListStore::Error::CompileFailed);
     }
-
-    if (!isSafeToUseMemoryMapForPath(finalFilePath)) {
-        auto contents = ContentRuleListStore::readContentsOfFile(finalFilePath);
-        if (!contents)
-            return makeUnexpected(ContentRuleListStore::Error::CompileFailed);
-        return {{ WTFMove(metaData), WTFMove(contents) }};
-    }
+    
+    makeSafeToUseMemoryMapForPath(finalFilePath);
     
     return {{ WTFMove(metaData), WTFMove(mappedData) }};
 }
 
-static Ref<API::ContentRuleList> createExtension(const WTF::String& identifier, MappedOrCopiedData&& data)
+static Ref<API::ContentRuleList> createExtension(const WTF::String& identifier, MappedData&& data)
 {
-    RefPtr<WebKit::SharedMemory> sharedMemory;
-    if (auto mappedFileData = WTF::get_if<WebKit::NetworkCache::Data>(data.data)) {
-        sharedMemory = mappedFileData->tryCreateSharedMemory();
+    auto sharedMemory = data.data.tryCreateSharedMemory();
 
-        // Content extensions are always compiled to files, and at this point the file
-        // has been already mapped, therefore tryCreateSharedMemory() cannot fail.
-        ASSERT(sharedMemory);
-    }
-    auto mappedOrCopiedFileData = sharedMemory ?
-        Variant<RefPtr<WebKit::SharedMemory>, RefPtr<WebCore::SharedBuffer>> { sharedMemory }
-        : Variant<RefPtr<WebKit::SharedMemory>, RefPtr<WebCore::SharedBuffer>> { WTFMove(WTF::get<RefPtr<WebCore::SharedBuffer>>(data.data)) };
+    // Content extensions are always compiled to files, and at this point the file
+    // has been already mapped, therefore tryCreateSharedMemory() cannot fail.
+    ASSERT(sharedMemory);
 
     const size_t headerAndSourceSize = ContentRuleListFileHeaderSize + data.metaData.sourceSize;
     auto compiledContentRuleListData = WebKit::WebCompiledContentRuleListData(
-        WTFMove(mappedOrCopiedFileData),
+        WTFMove(sharedMemory),
         ConditionsApplyOnlyToDomainOffset,
         headerAndSourceSize,
         data.metaData.actionsSize,
@@ -456,7 +419,7 @@ static Ref<API::ContentRuleList> createExtension(const WTF::String& identifier,
         data.metaData.conditionedFiltersBytecodeSize
     );
     auto compiledContentRuleList = WebKit::WebCompiledContentRuleList::create(WTFMove(compiledContentRuleListData));
-    return API::ContentRuleList::create(identifier, WTFMove(compiledContentRuleList), WTF::holds_alternative<WebKit::NetworkCache::Data>(data.data) ? WTF::get<WebKit::NetworkCache::Data>(data.data) : WebKit::NetworkCache::Data { });
+    return API::ContentRuleList::create(identifier, WTFMove(compiledContentRuleList), WTFMove(data.data));
 }
 
 void ContentRuleListStore::lookupContentRuleList(const WTF::String& identifier, CompletionHandler<void(RefPtr<API::ContentRuleList>, std::error_code)> completionHandler)
@@ -588,14 +551,14 @@ void ContentRuleListStore::getContentRuleListSource(const WTF::String& identifie
                 complete({ });
                 return;
             }
-            bool is8Bit = contentRuleList->dataPointer()[ContentRuleListFileHeaderSize];
+            bool is8Bit = contentRuleList->data.data()[ContentRuleListFileHeaderSize];
             size_t start = ContentRuleListFileHeaderSize + sizeof(bool);
             size_t length = contentRuleList->metaData.sourceSize - sizeof(bool);
             if (is8Bit)
-                complete(WTF::String(contentRuleList->dataPointer() + start, length));
+                complete(WTF::String(contentRuleList->data.data() + start, length));
             else {
                 ASSERT(!(length % sizeof(UChar)));
-                complete(WTF::String(reinterpret_cast<const UChar*>(contentRuleList->dataPointer() + start), length / sizeof(UChar)));
+                complete(WTF::String(reinterpret_cast<const UChar*>(contentRuleList->data.data() + start), length / sizeof(UChar)));
             }
             return;
         }
index 9c47601..35fb28e 100644 (file)
@@ -76,8 +76,6 @@ public:
     void invalidateContentRuleListVersion(const WTF::String& identifier);
     void getContentRuleListSource(const WTF::String& identifier, CompletionHandler<void(WTF::String)>);
 
-    static RefPtr<WebCore::SharedBuffer> readContentsOfFile(const WTF::String& path);
-
 private:
     WTF::String defaultStorePath(bool legacyFilename);
     static ContentRuleListStore& legacyDefaultStore();
index 66ca6b4..0d5c438 100644 (file)
@@ -64,15 +64,6 @@ WTF::String ContentRuleListStore::defaultStorePath(bool legacyFilename)
     return contentRuleListStoreURL.absoluteURL.path.fileSystemRepresentation;
 }
 
-RefPtr<WebCore::SharedBuffer> ContentRuleListStore::readContentsOfFile(const String& filePath)
-{
-    ASSERT(!isMainThread());
-    NSData *data = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:filePath isDirectory:NO]];
-    if (!data)
-        return nullptr;
-    return WebCore::SharedBuffer::create(data);
-}
-
 } // namespace API
 
 #endif // ENABLE(CONTENT_EXTENSIONS)
index 7dc5f2d..640b995 100644 (file)
@@ -126,11 +126,6 @@ static WKErrorCode toWKErrorCode(const std::error_code& error)
 
 // For testing only.
 
-+ (void)_registerPathAsUnsafeToMemoryMapForTesting:(NSString *)filename
-{
-    WebKit::NetworkCache::registerPathAsUnsafeToMemoryMapForTesting(filename);
-}
-
 - (void)_removeAllContentRuleLists
 {
     _contentRuleListStore->synchronousRemoveAllContentRuleLists();
index bee0ab7..918a5bc 100644 (file)
@@ -31,7 +31,6 @@
 - (void)_removeAllContentRuleLists;
 - (void)_invalidateContentRuleListVersionForIdentifier:(NSString *)identifier;
 - (void)_getContentRuleListSourceForIdentifier:(NSString *)identifier completionHandler:(void (^)(NSString*))completionHandler;
-+ (void)_registerPathAsUnsafeToMemoryMapForTesting:(NSString *)filename;
 
 // NS_RELEASES_ARGUMENT to keep peak memory usage low.
 - (void)_compileContentRuleListForIdentifier:(NSString *)identifier encodedContentRuleList:(NSString *) NS_RELEASES_ARGUMENT encodedContentRuleList completionHandler:(void (^)(WKContentRuleList *, NSError *))completionHandler;
index 31e1519..4ef57b9 100644 (file)
@@ -56,9 +56,4 @@
     return self;
 }
 
-- (BOOL)usesCopiedMemory
-{
-    return _contentRuleList->_contentRuleList->usesCopiedMemory();
-}
-
 @end
index 5008b49..267ad51 100644 (file)
@@ -30,6 +30,5 @@
 @interface _WKUserContentFilter (WKPrivate)
 
 - (id)_initWithWKContentRuleList:(WKContentRuleList*)contentRuleList WK_API_AVAILABLE(macos(10.13), ios(11.0));
-@property (nonatomic, readonly) BOOL usesCopiedMemory;
 
 @end
index 018dbc4..8eec432 100644 (file)
                5CBC9B891C6524A500A8FDCF /* NetworkDataTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetworkDataTask.h; sourceTree = "<group>"; };
                5CBC9B8B1C65257300A8FDCF /* NetworkDataTaskCocoa.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = NetworkDataTaskCocoa.mm; sourceTree = "<group>"; };
                5CC5DB9121488E16006CB8A8 /* SharedBufferDataReference.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SharedBufferDataReference.h; sourceTree = "<group>"; };
+               5CD150762268F7F0006FB4AF /* NetworkCacheFileSystemCocoa.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = NetworkCacheFileSystemCocoa.mm; sourceTree = "<group>"; };
                5CD286491E722F440094FDC8 /* _WKUserContentFilterPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _WKUserContentFilterPrivate.h; sourceTree = "<group>"; };
                5CD2864A1E722F440094FDC8 /* WKContentRuleList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKContentRuleList.h; sourceTree = "<group>"; };
                5CD2864B1E722F440094FDC8 /* WKContentRuleList.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WKContentRuleList.mm; sourceTree = "<group>"; };
                                E413F59B1AC1ADB600345360 /* NetworkCacheEntry.h */,
                                E4697CCC1B25EB8F001B0A6C /* NetworkCacheFileSystem.cpp */,
                                834B250E1A831A8D00CFB150 /* NetworkCacheFileSystem.h */,
+                               5CD150762268F7F0006FB4AF /* NetworkCacheFileSystemCocoa.mm */,
                                E42E060B1AA7440D00B11699 /* NetworkCacheIOChannel.h */,
                                E42E060D1AA750E500B11699 /* NetworkCacheIOChannelCocoa.mm */,
                                E4436EC01A0CFDB200EAD204 /* NetworkCacheKey.cpp */,
index a8617e4..6231942 100644 (file)
@@ -1,5 +1,19 @@
 2019-04-24  Alex Christensen  <achristensen@webkit.org>
 
+        WKContentRuleLists should have a maximum FileProtection of CompleteUnlessOpen
+        https://bugs.webkit.org/show_bug.cgi?id=197078
+        <rdar://problem/49564348>
+
+        Reviewed by Geoff Garen.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/WKContentExtensionStore.mm:
+        (TEST_F):
+        (-[TestSchemeHandlerSubresourceShouldBeBlocked webView:startURLSchemeTask:]): Deleted.
+        (-[TestSchemeHandlerSubresourceShouldBeBlocked webView:stopURLSchemeTask:]): Deleted.
+        Unfortunately, setting the NSFileProtectionKey attribute is only supported on iOS devices.
+
+2019-04-24  Alex Christensen  <achristensen@webkit.org>
+
         Add unit test for r239322
         https://bugs.webkit.org/show_bug.cgi?id=197236
 
index d700165..1b8c8c8 100644 (file)
 
 #import "PlatformUtilities.h"
 #import "Test.h"
-#import "TestWKWebView.h"
 #import <WebKit/WKContentRuleList.h>
 #import <WebKit/WKContentRuleListStorePrivate.h>
-#import <WebKit/_WKUserContentFilterPrivate.h>
 #import <wtf/RetainPtr.h>
-#import <wtf/text/StringBuilder.h>
-#import <wtf/text/StringConcatenate.h>
-#import <wtf/text/StringConcatenateNumbers.h>
 
 class WKContentRuleListStoreTest : public testing::Test {
 public:
@@ -383,101 +378,59 @@ TEST_F(WKContentRuleListStoreTest, AddRemove)
     TestWebKitAPI::Util::run(&receivedAlert);
 }
 
-@interface TestSchemeHandlerSubresourceShouldBeBlocked : NSObject <WKURLSchemeHandler>
-@end
-@implementation TestSchemeHandlerSubresourceShouldBeBlocked
-- (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)task
-{
-    EXPECT_TRUE([task.request.URL.path isEqualToString:@"/shouldload"]);
-    [task didReceiveResponse:[[[NSURLResponse alloc] initWithURL:task.request.URL MIMEType:@"text/html" expectedContentLength:0 textEncodingName:nil] autorelease]];
-    [task didFinish];
-}
-- (void)webView:(WKWebView *)webView stopURLSchemeTask:(id <WKURLSchemeTask>)task
-{
-    EXPECT_TRUE(false);
-}
-@end
-
+#if PLATFORM(IOS_FAMILY)
 TEST_F(WKContentRuleListStoreTest, UnsafeMMap)
 {
     RetainPtr<NSString> tempDir = [NSTemporaryDirectory() stringByAppendingPathComponent:@"UnsafeMMapTest"];
     RetainPtr<WKContentRuleListStore> store = [WKContentRuleListStore storeWithURL:[NSURL fileURLWithPath:tempDir.get() isDirectory:YES]];
-    static NSString *identifier = @"TestRuleList";
-    static NSString *fileName = @"ContentRuleList-TestRuleList";
+    static NSString *compiledIdentifier = @"CompiledRuleList";
+    static NSString *copiedIdentifier = @"CopiedRuleList";
     static NSString *ruleListSourceString = @"[{\"action\":{\"type\":\"block\"},\"trigger\":{\"url-filter\":\"blockedsubresource\"}}]";
-    RetainPtr<NSString> filePath = [tempDir stringByAppendingPathComponent:fileName];
-
-    auto runTest = [&] (bool shouldUseCopiedMemory) {
-        EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:filePath.get()]);
-        
-        __block bool doneCompiling = false;
-        __block RetainPtr<WKContentRuleList> ruleList;
-        [store compileContentRuleListForIdentifier:identifier encodedContentRuleList:ruleListSourceString completionHandler:^(WKContentRuleList *filter, NSError *error) {
-            EXPECT_NOT_NULL(filter);
-            EXPECT_NULL(error);
-            doneCompiling = true;
-            ruleList = filter;
-            EXPECT_TRUE([[[[_WKUserContentFilter alloc] _initWithWKContentRuleList:filter] autorelease] usesCopiedMemory] == shouldUseCopiedMemory);
-        }];
-        TestWebKitAPI::Util::run(&doneCompiling);
-        
-        EXPECT_TRUE([[NSFileManager defaultManager] fileExistsAtPath:filePath.get()]);
-
-        auto handler = adoptNS([TestSchemeHandlerSubresourceShouldBeBlocked new]);
-        auto configuration = adoptNS([WKWebViewConfiguration new]);
-        [configuration setURLSchemeHandler:handler.get() forURLScheme:@"testmmap"];
-        [[configuration userContentController] addContentRuleList:ruleList.get()];
-        auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
-        [webView synchronouslyLoadHTMLString:@"<html>main resource content</html>" baseURL:[NSURL URLWithString:@"testmmap://webkit.org/mainresource"]];
-
-        auto loadingShouldSucceed = [&] (NSString *resourcePath, NSString *shouldSucceed) {
-            __block bool doneEvaluating = false;
-            [webView evaluateJavaScript:[NSString stringWithFormat:@"var caught = false; var xhr = new XMLHttpRequest(); xhr.open('GET', '%@', false); try{ xhr.send() } catch(e) { caught = true; }; caught != %@ ? 'success' : 'failure'", resourcePath, shouldSucceed] completionHandler:^(id result, NSError *error) {
-                EXPECT_NULL(error);
-                EXPECT_TRUE([@"success" isEqualToString:result]);
-                doneEvaluating = true;
-            }];
-            TestWebKitAPI::Util::run(&doneEvaluating);
-        };
-        loadingShouldSucceed(@"/shouldload", @"true");
-        loadingShouldSucceed(@"/blockedsubresource", @"false");
+    RetainPtr<NSString> compiledFilePath = [tempDir stringByAppendingPathComponent:@"ContentRuleList-CompiledRuleList"];
+    RetainPtr<NSString> copiedFilePath = [tempDir stringByAppendingPathComponent:@"ContentRuleList-CopiedRuleList"];
 
-        [[configuration userContentController] removeContentRuleList:ruleList.get()];
-        
-        __block bool doneLookingUp = false;
-        [store lookUpContentRuleListForIdentifier:identifier completionHandler:^(WKContentRuleList *filter, NSError *error) {
-            EXPECT_NOT_NULL(filter);
-            EXPECT_NULL(error);
-            
-            doneLookingUp = true;
-            
-            EXPECT_TRUE([[[[_WKUserContentFilter alloc] _initWithWKContentRuleList:filter] autorelease] usesCopiedMemory] == shouldUseCopiedMemory);
-            ruleList = filter;
-        }];
-        TestWebKitAPI::Util::run(&doneLookingUp);
+    __block bool doneCompiling = false;
+    [store compileContentRuleListForIdentifier:compiledIdentifier encodedContentRuleList:ruleListSourceString completionHandler:^(WKContentRuleList *filter, NSError *error) {
+        EXPECT_NOT_NULL(filter);
+        EXPECT_NULL(error);
+        doneCompiling = true;
+    }];
+    TestWebKitAPI::Util::run(&doneCompiling);
 
-        [[configuration userContentController] addContentRuleList:ruleList.get()];
-        loadingShouldSucceed(@"/shouldload", @"true");
-        loadingShouldSucceed(@"/blockedsubresource", @"false");
+    auto hasCompleteProtection = [] (const RetainPtr<NSString>& path) {
+        NSError *error = nil;
+        NSDictionary<NSFileAttributeKey, id> *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path.get() error:&error];
+        EXPECT_NULL(error);
+        return [[attributes objectForKey:NSFileProtectionKey] isEqualToString:NSFileProtectionComplete];
+    };
+    
+    NSError *error = nil;
+    [[NSFileManager defaultManager] copyItemAtPath:compiledFilePath.get() toPath:copiedFilePath.get() error:&error];
+    EXPECT_NULL(error);
+    EXPECT_FALSE(hasCompleteProtection(copiedFilePath));
+    [[NSFileManager defaultManager] setAttributes:@{ NSFileProtectionKey: NSFileProtectionComplete } ofItemAtPath:copiedFilePath.get() error:&error];
+    EXPECT_NULL(error);
+#if !PLATFORM(IOS_FAMILY_SIMULATOR)
+    EXPECT_TRUE(hasCompleteProtection(copiedFilePath));
+#endif
 
-        __block bool doneCheckingSource = false;
-        [store _getContentRuleListSourceForIdentifier:identifier completionHandler:^(NSString *source) {
-            EXPECT_TRUE([source isEqualToString:ruleListSourceString]);
-            doneCheckingSource = true;
-        }];
-        TestWebKitAPI::Util::run(&doneCheckingSource);
-        
-        __block bool doneRemoving = false;
-        [store removeContentRuleListForIdentifier:identifier completionHandler:^(NSError *error) {
+    __block bool doneLookingUp = false;
+    [store lookUpContentRuleListForIdentifier:copiedIdentifier completionHandler:^(WKContentRuleList *filter, NSError *error) {
+        EXPECT_NOT_NULL(filter);
+        EXPECT_NULL(error);
+        doneLookingUp = true;
+    }];
+    TestWebKitAPI::Util::run(&doneLookingUp);
+    EXPECT_FALSE(hasCompleteProtection(copiedFilePath));
+    
+    __block bool doneRemoving = false;
+    [store removeContentRuleListForIdentifier:compiledIdentifier completionHandler:^(NSError *error) {
+        EXPECT_NULL(error);
+        [store removeContentRuleListForIdentifier:copiedIdentifier completionHandler:^(NSError *error) {
             EXPECT_NULL(error);
             doneRemoving = true;
         }];
-        TestWebKitAPI::Util::run(&doneRemoving);
-
-        EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:filePath.get()]);
-    };
-    
-    runTest(false);
-    [WKContentRuleListStore _registerPathAsUnsafeToMemoryMapForTesting:filePath.get()];
-    runTest(true);
+    }];
+    TestWebKitAPI::Util::run(&doneRemoving);
 }
+#endif // PLATFORM(IOS_FAMILY)