blob: 15bb465e0f9c218cc1a466bf7d99345ced0850c7 [file] [log] [blame]
/*
* Copyright (C) 2020-2021 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.
*/
#import "config.h"
#import "DefaultWebBrowserChecks.h"
#import "AuxiliaryProcess.h"
#import "Connection.h"
#import "Logging.h"
#import "TCCSoftLink.h"
#import <WebCore/RegistrableDomain.h>
#import <WebCore/RuntimeApplicationChecks.h>
#import <WebCore/VersionChecks.h>
#import <wtf/HashMap.h>
#import <wtf/NeverDestroyed.h>
#import <wtf/RobinHoodHashMap.h>
#import <wtf/RunLoop.h>
#import <wtf/WorkQueue.h>
#import <wtf/cocoa/Entitlements.h>
#import <wtf/text/StringHash.h>
namespace WebKit {
static bool isFullWebBrowser(const String&);
bool isRunningTest(const String& bundleID)
{
return bundleID == "com.apple.WebKit.TestWebKitAPI"_s || bundleID == "com.apple.WebKit.WebKitTestRunner"_s || bundleID == "org.webkit.WebKitTestRunnerApp"_s;
}
std::optional<Vector<WebCore::RegistrableDomain>> getAppBoundDomainsTesting(const String& bundleID)
{
if (bundleID.isNull())
return std::nullopt;
static auto appBoundDomainList = makeNeverDestroyed(MemoryCompactLookupOnlyRobinHoodHashMap<String, Vector<WebCore::RegistrableDomain>> {
{"inAppBrowserPrivacyTestIdentifier"_s, Vector<WebCore::RegistrableDomain> { WebCore::RegistrableDomain::uncheckedCreateFromRegistrableDomainString("127.0.0.1") }},
});
auto appBoundDomainIter = appBoundDomainList->find(bundleID);
if (appBoundDomainIter != appBoundDomainList->end())
return appBoundDomainIter->value;
return std::nullopt;
}
#if ASSERT_ENABLED
static bool isInWebKitChildProcess()
{
static bool isInSubProcess;
static dispatch_once_t once;
dispatch_once(&once, ^{
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
isInSubProcess = [bundleIdentifier hasPrefix:@"com.apple.WebKit.WebContent"]
|| [bundleIdentifier hasPrefix:@"com.apple.WebKit.Networking"]
|| [bundleIdentifier hasPrefix:@"com.apple.WebKit.GPU"]
|| [bundleIdentifier hasPrefix:@"com.apple.WebKit.WebAuthn"];
});
return isInSubProcess;
}
#endif
enum class ITPState : uint8_t {
Uninitialized,
Enabled,
Disabled
};
static std::atomic<ITPState> currentITPState = ITPState::Uninitialized;
bool hasRequestedCrossWebsiteTrackingPermission()
{
ASSERT(!isInWebKitChildProcess());
static std::atomic<bool> hasRequestedCrossWebsiteTrackingPermission = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSCrossWebsiteTrackingUsageDescription"];
return hasRequestedCrossWebsiteTrackingPermission;
}
static bool determineITPStateInternal(bool appWasLinkedOnOrAfter, const String& bundleIdentifier)
{
ASSERT(!RunLoop::isMain());
ASSERT(!isInWebKitChildProcess());
if (!appWasLinkedOnOrAfter && !isFullWebBrowser(bundleIdentifier))
return false;
if (!isFullWebBrowser(bundleIdentifier) && !hasRequestedCrossWebsiteTrackingPermission())
return true;
TCCAccessPreflightResult result = kTCCAccessPreflightDenied;
#if (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 140000) || (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 110000)
result = TCCAccessPreflight(get_TCC_kTCCServiceWebKitIntelligentTrackingPrevention(), nullptr);
#endif
return result != kTCCAccessPreflightDenied;
}
static RefPtr<WorkQueue>& itpQueue()
{
static NeverDestroyed<RefPtr<WorkQueue>> itpQueue;
return itpQueue;
}
void determineITPState()
{
ASSERT(RunLoop::isMain());
if (currentITPState != ITPState::Uninitialized)
return;
bool appWasLinkedOnOrAfter = linkedOnOrAfter(WebCore::SDKVersion::FirstWithSessionCleanupByDefault);
itpQueue() = WorkQueue::create("com.apple.WebKit.itpCheckQueue");
itpQueue()->dispatch([appWasLinkedOnOrAfter, bundleIdentifier = WebCore::applicationBundleIdentifier().isolatedCopy()] {
currentITPState = determineITPStateInternal(appWasLinkedOnOrAfter, bundleIdentifier) ? ITPState::Enabled : ITPState::Disabled;
RunLoop::main().dispatch([] {
itpQueue() = nullptr;
});
});
}
bool doesAppHaveITPEnabled()
{
ASSERT(!isInWebKitChildProcess());
ASSERT(RunLoop::isMain());
// If we're still computing the ITP state on the background thread, then synchronize with it.
if (itpQueue())
itpQueue()->dispatchSync([] { });
ASSERT(currentITPState != ITPState::Uninitialized);
return currentITPState == ITPState::Enabled;
}
bool doesParentProcessHaveITPEnabled(AuxiliaryProcess& auxiliaryProcess, bool hasRequestedCrossWebsiteTrackingPermission)
{
ASSERT(isInWebKitChildProcess());
ASSERT(RunLoop::isMain());
if (!isParentProcessAFullWebBrowser(auxiliaryProcess) && !hasRequestedCrossWebsiteTrackingPermission)
return true;
static bool itpEnabled { true };
static dispatch_once_t once;
dispatch_once(&once, ^{
TCCAccessPreflightResult result = kTCCAccessPreflightDenied;
#if (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 140000) || (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 110000)
RefPtr<IPC::Connection> connection = auxiliaryProcess.parentProcessConnection();
if (!connection) {
ASSERT_NOT_REACHED();
RELEASE_LOG_ERROR(IPC, "Unable to get parent process connection");
return;
}
auto auditToken = connection->getAuditToken();
if (!auditToken) {
ASSERT_NOT_REACHED();
RELEASE_LOG_ERROR(IPC, "Unable to get parent process audit token");
return;
}
result = TCCAccessPreflightWithAuditToken(get_TCC_kTCCServiceWebKitIntelligentTrackingPrevention(), auditToken.value(), nullptr);
#endif
itpEnabled = result != kTCCAccessPreflightDenied;
});
return itpEnabled;
}
static std::atomic<bool> hasCheckedUsageStrings = false;
bool hasProhibitedUsageStrings()
{
ASSERT(!isInWebKitChildProcess());
static bool hasProhibitedUsageStrings = false;
if (hasCheckedUsageStrings)
return hasProhibitedUsageStrings;
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
RELEASE_ASSERT(infoDictionary);
// See <rdar://problem/59979468> for details about how this list was selected.
auto prohibitedStrings = @[
@"NSHomeKitUsageDescription",
@"NSBluetoothAlwaysUsageDescription",
@"NSPhotoLibraryUsageDescription",
@"NSHealthShareUsageDescription",
@"NSHealthUpdateUsageDescription",
@"NSLocationAlwaysUsageDescription",
@"NSLocationAlwaysAndWhenInUseUsageDescription"
];
for (NSString *prohibitedString : prohibitedStrings) {
if ([infoDictionary objectForKey:prohibitedString]) {
String message = [NSString stringWithFormat:@"[In-App Browser Privacy] %@ used prohibited usage string %@.", [[NSBundle mainBundle] bundleIdentifier], prohibitedString];
WTFLogAlways(message.utf8().data());
hasProhibitedUsageStrings = true;
break;
}
}
hasCheckedUsageStrings = true;
return hasProhibitedUsageStrings;
}
bool isParentProcessAFullWebBrowser(AuxiliaryProcess& auxiliaryProcess)
{
ASSERT(isInWebKitChildProcess());
static bool fullWebBrowser { false };
static dispatch_once_t once;
dispatch_once(&once, ^{
RefPtr<IPC::Connection> connection = auxiliaryProcess.parentProcessConnection();
if (!connection) {
ASSERT_NOT_REACHED();
RELEASE_LOG_ERROR(IPC, "Unable to get parent process connection");
return;
}
auto auditToken = connection->getAuditToken();
if (!auditToken) {
ASSERT_NOT_REACHED();
RELEASE_LOG_ERROR(IPC, "Unable to get parent process audit token");
return;
}
fullWebBrowser = WTF::hasEntitlement(*auditToken, "com.apple.developer.web-browser");
});
return fullWebBrowser || isRunningTest(WebCore::applicationBundleIdentifier());
}
static bool isFullWebBrowser(const String& bundleIdentifier)
{
ASSERT(!isInWebKitChildProcess());
static bool fullWebBrowser = WTF::processHasEntitlement("com.apple.developer.web-browser");
return fullWebBrowser || isRunningTest(bundleIdentifier);
}
bool isFullWebBrowser()
{
ASSERT(!isInWebKitChildProcess());
ASSERT(RunLoop::isMain());
return isFullWebBrowser(WebCore::applicationBundleIdentifier());
}
} // namespace WebKit