/*
 * Copyright (C) 2012-2018 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"

#if PLATFORM(MAC) || PLATFORM(MACCATALYST)
#import "AuxiliaryProcess.h"

#import "ApplicationServicesSPI.h"
#import "CodeSigning.h"
#import "QuarantineSPI.h"
#import "SandboxInitializationParameters.h"
#import "SandboxUtilities.h"
#import "WKFoundation.h"
#import "XPCServiceEntryPoint.h"
#import <WebCore/FileHandle.h>
#import <WebCore/SystemVersion.h>
#import <mach-o/dyld.h>
#import <mach/mach.h>
#import <mach/task.h>
#import <pal/crypto/CryptoDigest.h>
#import <pal/spi/cocoa/LaunchServicesSPI.h>
#import <pwd.h>
#import <stdlib.h>
#import <sys/sysctl.h>
#import <sysexits.h>
#import <wtf/DataLog.h>
#import <wtf/FileSystem.h>
#import <wtf/RandomNumber.h>
#import <wtf/Scope.h>
#import <wtf/SystemTracing.h>
#import <wtf/WallTime.h>
#import <wtf/spi/darwin/SandboxSPI.h>
#import <wtf/text/Base64.h>
#import <wtf/text/StringBuilder.h>

#if USE(APPLE_INTERNAL_SDK)
#import <rootless.h>
#endif

#if PLATFORM(MAC)
#define USE_CACHE_COMPILED_SANDBOX 1
#else
#define USE_CACHE_COMPILED_SANDBOX 0
#endif

#if PLATFORM(MACCATALYST) && USE(APPLE_INTERNAL_SDK)
enum LSSessionID {
    kLSDefaultSessionID = -2,
};
#endif

typedef bool (^LSServerConnectionAllowedBlock) ( CFDictionaryRef optionsRef );
extern "C" void _LSSetApplicationLaunchServicesServerConnectionStatus(uint64_t flags, LSServerConnectionAllowedBlock block);
extern "C" CFDictionaryRef _LSApplicationCheckIn(LSSessionID sessionID, CFDictionaryRef applicationInfo);

namespace WebKit {
using namespace WebCore;

#if USE(CACHE_COMPILED_SANDBOX)
using SandboxProfile = typename std::remove_pointer<sandbox_profile_t>::type;
struct SandboxProfileDeleter {
    void operator()(SandboxProfile* ptr)
    {
        sandbox_free_profile(ptr);
    }
};
using SandboxProfilePtr = std::unique_ptr<SandboxProfile, SandboxProfileDeleter>;

using SandboxParameters = typename std::remove_pointer<sandbox_params_t>::type;
struct SandboxParametersDeleter {
    void operator()(SandboxParameters* ptr)
    {
        sandbox_free_params(ptr);
    }
};
using SandboxParametersPtr = std::unique_ptr<SandboxParameters, SandboxParametersDeleter>;

struct CachedSandboxHeader {
    uint32_t versionNumber;
    uint32_t libsandboxVersion;
    uint32_t headerSize;
    uint32_t builtinSize; // If a builtin doesn't exist, this is UINT_MAX.
    uint32_t dataSize;
};
// The file is layed out on disk like:
// byte 0
// CachedSandboxHeader <- sizeof(CachedSandboxHeader) bytes
// SandboxHeader <- CachedSandboxHeader::headerSize bytes
// [SandboxBuiltin] optional. Present if CachedSandboxHeader::builtinSize is not UINT_MAX. If present, builtinSize bytes (not including null termination).
// SandboxData <- CachedSandboxHeader::dataSize bytes
// byte N

struct SandboxInfo {
    SandboxInfo(const String& parentDirectoryPath, const String& directoryPath, const String& filePath, const SandboxParametersPtr& sandboxParameters, const CString& header, const AuxiliaryProcess::ProcessType& processType, const SandboxInitializationParameters& initializationParameters, const String& profileOrProfilePath, bool isProfilePath)
        : parentDirectoryPath { parentDirectoryPath }
        , directoryPath { directoryPath }
        , filePath { filePath }
        , sandboxParameters { sandboxParameters }
        , header { header }
        , processType { processType }
        , initializationParameters { initializationParameters }
        , profileOrProfilePath { profileOrProfilePath }
        , isProfilePath { isProfilePath }
    {
    }

    const String& parentDirectoryPath;
    const String& directoryPath;
    const String& filePath;
    const SandboxParametersPtr& sandboxParameters;
    const CString& header;
    const AuxiliaryProcess::ProcessType& processType;
    const SandboxInitializationParameters& initializationParameters;
    const String& profileOrProfilePath;
    const bool isProfilePath;
};

constexpr uint32_t CachedSandboxVersionNumber = 0;
#endif // USE(CACHE_COMPILED_SANDBOX)

static void initializeTimerCoalescingPolicy()
{
    // Set task_latency and task_throughput QOS tiers as appropriate for a visible application.
    struct task_qos_policy qosinfo = { LATENCY_QOS_TIER_0, THROUGHPUT_QOS_TIER_0 };
    kern_return_t kr = task_policy_set(mach_task_self(), TASK_BASE_QOS_POLICY, (task_policy_t)&qosinfo, TASK_QOS_POLICY_COUNT);
    ASSERT_UNUSED(kr, kr == KERN_SUCCESS);
}

void AuxiliaryProcess::launchServicesCheckIn()
{
    _LSSetApplicationLaunchServicesServerConnectionStatus(0, 0);
    RetainPtr<CFDictionaryRef> unused = _LSApplicationCheckIn(kLSDefaultSessionID, CFBundleGetInfoDictionary(CFBundleGetMainBundle()));
}

void AuxiliaryProcess::platformInitialize()
{
    initializeTimerCoalescingPolicy();
    [[NSFileManager defaultManager] changeCurrentDirectoryPath:[[NSBundle mainBundle] bundlePath]];
}

static OSStatus enableSandboxStyleFileQuarantine()
{
#if !PLATFORM(MACCATALYST)
    qtn_proc_t quarantineProperties = qtn_proc_alloc();
    auto quarantinePropertiesDeleter = makeScopeExit([quarantineProperties]() {
        qtn_proc_free(quarantineProperties);
    });


    if (qtn_proc_init_with_self(quarantineProperties)) {
        // See <rdar://problem/13463752>.
        qtn_proc_init(quarantineProperties);
    }

    if (auto error = qtn_proc_set_flags(quarantineProperties, QTN_FLAG_SANDBOX))
        return error;

    // QTN_FLAG_SANDBOX is silently ignored if security.mac.qtn.sandbox_enforce sysctl is 0.
    // In that case, quarantine falls back to advisory QTN_FLAG_DOWNLOAD.
    return qtn_proc_apply_to_self(quarantineProperties);
#else
    return false;
#endif
}

#if USE(CACHE_COMPILED_SANDBOX)
static Optional<Vector<char>> fileContents(const String& path, bool shouldLock = false, OptionSet<FileSystem::FileLockMode> lockMode = FileSystem::FileLockMode::Exclusive)
{
    FileHandle file = shouldLock ? FileHandle(path, FileSystem::FileOpenMode::Read, lockMode) : FileHandle(path, FileSystem::FileOpenMode::Read);
    file.open();
    if (!file)
        return WTF::nullopt;

    char chunk[4096];
    constexpr size_t chunkSize = WTF_ARRAY_LENGTH(chunk);
    size_t contentSize = 0;
    Vector<char> contents;
    contents.reserveInitialCapacity(chunkSize);
    while (size_t bytesRead = file.read(chunk, chunkSize)) {
        contents.append(chunk, bytesRead);
        contentSize += bytesRead;
    }
    contents.resize(contentSize);

    return contents;
}

#if USE(APPLE_INTERNAL_SDK)
// These strings must match the last segment of the "com.apple.rootless.storage.<this part must match>" entry in each
// process's restricted entitlements file (ex. Configurations/Networking-OSX-restricted.entitlements).
constexpr const char* processStorageClass(AuxiliaryProcess::ProcessType type)
{
    switch (type) {
    case AuxiliaryProcess::ProcessType::WebContent:
        return "WebKitWebContentSandbox";
    case AuxiliaryProcess::ProcessType::Network:
        return "WebKitNetworkingSandbox";
    case AuxiliaryProcess::ProcessType::Plugin:
        return "WebKitPluginSandbox";
#if ENABLE(GPU_PROCESS)
    case AuxiliaryProcess::ProcessType::GPU:
        return "WebKitGPUSandbox";
#endif
    }
}
#endif // USE(APPLE_INTERNAL_SDK)

static Optional<CString> setAndSerializeSandboxParameters(const SandboxInitializationParameters& initializationParameters, const SandboxParametersPtr& sandboxParameters, const String& profileOrProfilePath, bool isProfilePath)
{
    StringBuilder builder;
    for (size_t i = 0; i < initializationParameters.count(); ++i) {
        const char* name = initializationParameters.name(i);
        const char* value = initializationParameters.value(i);
        if (sandbox_set_param(sandboxParameters.get(), name, value)) {
            WTFLogAlways("%s: Could not set sandbox parameter: %s\n", getprogname(), strerror(errno));
            CRASH();
        }
        builder.append(name, ':', value, ':');
    }
    if (isProfilePath) {
        auto contents = fileContents(profileOrProfilePath);
        if (!contents)
            return WTF::nullopt;
        builder.appendCharacters(contents->data(), contents->size());
    } else
        builder.append(profileOrProfilePath);
    return builder.toString().ascii();
}

static String sandboxDataVaultParentDirectory()
{
    char temp[PATH_MAX];
    size_t length = confstr(_CS_DARWIN_USER_CACHE_DIR, temp, sizeof(temp));
    if (!length) {
        WTFLogAlways("%s: Could not retrieve user cache directory path: %s\n", getprogname(), strerror(errno));
        exit(EX_NOPERM);
    }
    RELEASE_ASSERT(length <= sizeof(temp));
    char resolvedPath[PATH_MAX];
    if (!realpath(temp, resolvedPath)) {
        WTFLogAlways("%s: Could not canonicalize user cache directory path: %s\n", getprogname(), strerror(errno));
        exit(EX_NOPERM);
    }
    return resolvedPath;
}

static String sandboxDirectory(AuxiliaryProcess::ProcessType processType, const String& parentDirectory)
{
    StringBuilder directory;
    directory.append(parentDirectory);
    switch (processType) {
    case AuxiliaryProcess::ProcessType::WebContent:
        directory.append("/com.apple.WebKit.WebContent.Sandbox");
        break;
    case AuxiliaryProcess::ProcessType::Network:
        directory.append("/com.apple.WebKit.Networking.Sandbox");
        break;
    case AuxiliaryProcess::ProcessType::Plugin:
        directory.append("/com.apple.WebKit.Plugin.Sandbox");
        break;
#if ENABLE(GPU_PROCESS)
    case AuxiliaryProcess::ProcessType::GPU:
        directory.append("/com.apple.WebKit.GPU.Sandbox");
        break;
#endif
    }

#if !USE(APPLE_INTERNAL_SDK)
    // Add .OpenSource suffix so that open source builds don't try to access a data vault used by system Safari.
    directory.append(".OpenSource");
#endif

    return directory.toString();
}

static String sandboxFilePath(const String& directoryPath, const CString& header)
{
    StringBuilder sandboxFile;
    sandboxFile.append(directoryPath);
    sandboxFile.append("/CompiledSandbox+");

    // Make the filename semi-unique based on the contents of the header.
    auto crypto = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_256);
    crypto->addBytes(header.data(), header.length());
    Vector<uint8_t> hash = crypto->computeHash();
    String readableHash = WTF::base64URLEncode(hash.data(), hash.size());

    sandboxFile.append(readableHash);
    return sandboxFile.toString();
}

static bool ensureSandboxCacheDirectory(const SandboxInfo& info)
{
    if (!FileSystem::fileIsDirectory(info.parentDirectoryPath, FileSystem::ShouldFollowSymbolicLinks::Yes)) {
        FileSystem::makeAllDirectories(info.parentDirectoryPath);
        if (!FileSystem::fileIsDirectory(info.parentDirectoryPath, FileSystem::ShouldFollowSymbolicLinks::Yes)) {
            WTFLogAlways("%s: Could not create sandbox directory\n", getprogname());
            return false;
        }
    }

#if USE(APPLE_INTERNAL_SDK)
    const char* storageClass = processStorageClass(info.processType);
    CString directoryPath = FileSystem::fileSystemRepresentation(info.directoryPath);
    if (directoryPath.isNull())
        return false;

    auto makeDataVault = [&] {
        do {
            if (!rootless_mkdir_datavault(directoryPath.data(), 0700, storageClass))
                return true;
        } while (errno == EAGAIN);
        return false;
    };

    if (makeDataVault())
        return true;

    if (errno == EEXIST) {
        // The directory already exists. First we'll check if it is a data vault. If it is then
        // we are the ones who created it and we can continue. If it is not a datavault then we'll just
        // delete it and try to make a new one.
        if (!rootless_check_datavault_flag(directoryPath.data(), storageClass))
            return true;

        bool isDirectory = FileSystem::fileIsDirectory(info.directoryPath, FileSystem::ShouldFollowSymbolicLinks::No);
        if (isDirectory) {
            if (!FileSystem::deleteNonEmptyDirectory(info.directoryPath))
                return false;
        } else {
            if (!FileSystem::deleteFile(info.directoryPath))
                return false;
        }

        if (!makeDataVault())
            return false;
    } else {
        WTFLogAlways("%s: Sandbox directory couldn't be created: ", getprogname(), strerror(errno));
        return false;
    }
#else
    bool hasSandboxDirectory = FileSystem::fileIsDirectory(info.directoryPath, FileSystem::ShouldFollowSymbolicLinks::Yes);
    if (!hasSandboxDirectory) {
        if (FileSystem::makeAllDirectories(info.directoryPath)) {
            ASSERT(FileSystem::fileIsDirectory(info.directoryPath, FileSystem::ShouldFollowSymbolicLinks::Yes));
            hasSandboxDirectory = true;
        } else {
            // We may have raced with someone else making it. That's ok.
            hasSandboxDirectory = FileSystem::fileIsDirectory(info.directoryPath, FileSystem::ShouldFollowSymbolicLinks::Yes);
        }
    }

    if (!hasSandboxDirectory) {
        // Bailing because we don't have a sandbox directory.
        return false;
    }
#endif // USE(APPLE_INTERNAL_SDK)

    return true;
}

static bool writeSandboxDataToCacheFile(const SandboxInfo& info, const Vector<char>& cacheFile)
{
    FileHandle file { info.filePath, FileSystem::FileOpenMode::Write, FileSystem::FileLockMode::Exclusive };
    return file.write(cacheFile.data(), cacheFile.size()) == safeCast<int>(cacheFile.size());
}

static SandboxProfilePtr compileAndCacheSandboxProfile(const SandboxInfo& info)
{
    if (!ensureSandboxCacheDirectory(info))
        return nullptr;

    char* error = nullptr;
    CString profileOrProfilePath = info.isProfilePath ? FileSystem::fileSystemRepresentation(info.profileOrProfilePath) : info.profileOrProfilePath.utf8();
    if (profileOrProfilePath.isNull())
        return nullptr;
    SandboxProfilePtr sandboxProfile { info.isProfilePath ? sandbox_compile_file(profileOrProfilePath.data(), info.sandboxParameters.get(), &error) : sandbox_compile_string(profileOrProfilePath.data(), info.sandboxParameters.get(), &error) };
    if (!sandboxProfile) {
        WTFLogAlways("%s: Could not compile WebContent sandbox: %s\n", getprogname(), error);
        return nullptr;
    }

    const bool haveBuiltin = sandboxProfile->builtin;
    int32_t libsandboxVersion = NSVersionOfRunTimeLibrary("sandbox");
    RELEASE_ASSERT(libsandboxVersion > 0);
    CachedSandboxHeader cachedHeader {
        CachedSandboxVersionNumber,
        static_cast<uint32_t>(libsandboxVersion),
        safeCast<uint32_t>(info.header.length()),
        haveBuiltin ? safeCast<uint32_t>(strlen(sandboxProfile->builtin)) : std::numeric_limits<uint32_t>::max(),
        safeCast<uint32_t>(sandboxProfile->size)
    };
    const size_t expectedFileSize = sizeof(cachedHeader) + cachedHeader.headerSize + (haveBuiltin ? cachedHeader.builtinSize : 0) + cachedHeader.dataSize;

    Vector<char> cacheFile;
    cacheFile.reserveInitialCapacity(expectedFileSize);
    cacheFile.append(bitwise_cast<uint8_t*>(&cachedHeader), sizeof(CachedSandboxHeader));
    cacheFile.append(info.header.data(), info.header.length());
    if (haveBuiltin)
        cacheFile.append(sandboxProfile->builtin, cachedHeader.builtinSize);
    cacheFile.append(sandboxProfile->data, cachedHeader.dataSize);

    if (!writeSandboxDataToCacheFile(info, cacheFile))
        WTFLogAlways("%s: Unable to cache compiled sandbox\n", getprogname());

    return sandboxProfile;
}

static bool tryApplyCachedSandbox(const SandboxInfo& info)
{
#if USE(APPLE_INTERNAL_SDK)
    CString directoryPath = FileSystem::fileSystemRepresentation(info.directoryPath);
    if (directoryPath.isNull())
        return false;
    if (rootless_check_datavault_flag(directoryPath.data(), processStorageClass(info.processType)))
        return false;
#endif

    auto contents = fileContents(info.filePath, true, FileSystem::FileLockMode::Shared);
    if (!contents || contents->isEmpty())
        return false;
    Vector<char> cachedSandboxContents = WTFMove(*contents);
    if (sizeof(CachedSandboxHeader) > cachedSandboxContents.size())
        return false;

    // This data may be corrupted if the sandbox file was cached on a different platform with different endianness
    CachedSandboxHeader cachedSandboxHeader;
    memcpy(&cachedSandboxHeader, cachedSandboxContents.data(), sizeof(CachedSandboxHeader));
    int32_t libsandboxVersion = NSVersionOfRunTimeLibrary("sandbox");
    RELEASE_ASSERT(libsandboxVersion > 0);
    if (static_cast<uint32_t>(libsandboxVersion) != cachedSandboxHeader.libsandboxVersion)
        return false;
    if (cachedSandboxHeader.versionNumber != CachedSandboxVersionNumber)
        return false;
    const bool haveBuiltin = cachedSandboxHeader.builtinSize != std::numeric_limits<uint32_t>::max();

    // These values are computed based on the disk layout specified below the definition of the CachedSandboxHeader struct
    // and must be changed if the layout changes.
    const char* sandboxHeaderPtr = bitwise_cast<char *>(cachedSandboxContents.data()) + sizeof(CachedSandboxHeader);
    const char* sandboxBuiltinPtr = sandboxHeaderPtr + cachedSandboxHeader.headerSize;
    unsigned char* sandboxDataPtr = bitwise_cast<unsigned char*>(haveBuiltin ? sandboxBuiltinPtr + cachedSandboxHeader.builtinSize : sandboxBuiltinPtr);

    size_t expectedFileSize = sizeof(CachedSandboxHeader) + cachedSandboxHeader.headerSize + cachedSandboxHeader.dataSize;
    if (haveBuiltin)
        expectedFileSize += cachedSandboxHeader.builtinSize;
    if (cachedSandboxContents.size() != expectedFileSize)
        return false;
    if (cachedSandboxHeader.headerSize != info.header.length())
        return false;
    if (memcmp(sandboxHeaderPtr, info.header.data(), info.header.length()))
        return false;

    SandboxProfile profile { };
    CString builtin;
    profile.builtin = nullptr;
    profile.size = cachedSandboxHeader.dataSize;
    if (haveBuiltin) {
        builtin = CString::newUninitialized(cachedSandboxHeader.builtinSize, profile.builtin);
        if (builtin.isNull())
            return false;
        memcpy(profile.builtin, sandboxBuiltinPtr, cachedSandboxHeader.builtinSize);
    }
    ASSERT(static_cast<void *>(sandboxDataPtr + profile.size) <= static_cast<void *>(cachedSandboxContents.data() + cachedSandboxContents.size()));
    profile.data = sandboxDataPtr;

    if (sandbox_apply(&profile)) {
        WTFLogAlways("%s: Could not apply cached sandbox: %s\n", getprogname(), strerror(errno));
        return false;
    }

    return true;
}
#endif // USE(CACHE_COMPILED_SANDBOX)

static inline const NSBundle *webKit2Bundle()
{
    const static NSBundle *bundle = [NSBundle bundleForClass:NSClassFromString(@"WKWebView")];
    return bundle;
}

static void getSandboxProfileOrProfilePath(const SandboxInitializationParameters& parameters, String& profileOrProfilePath, bool& isProfilePath)
{
    switch (parameters.mode()) {
    case SandboxInitializationParameters::ProfileSelectionMode::UseDefaultSandboxProfilePath:
        profileOrProfilePath = [webKit2Bundle() pathForResource:[[NSBundle mainBundle] bundleIdentifier] ofType:@"sb"];
        isProfilePath = true;
        return;
    case SandboxInitializationParameters::ProfileSelectionMode::UseOverrideSandboxProfilePath:
        profileOrProfilePath = parameters.overrideSandboxProfilePath();
        isProfilePath = true;
        return;
    case SandboxInitializationParameters::ProfileSelectionMode::UseSandboxProfile:
        profileOrProfilePath = parameters.sandboxProfile();
        isProfilePath = false;
        return;
    }
}

static bool compileAndApplySandboxSlowCase(const String& profileOrProfilePath, bool isProfilePath, const SandboxInitializationParameters& parameters)
{
    char* errorBuf;
    CString temp = isProfilePath ? FileSystem::fileSystemRepresentation(profileOrProfilePath) : profileOrProfilePath.utf8();
    uint64_t flags = isProfilePath ? SANDBOX_NAMED_EXTERNAL : 0;
    ALLOW_DEPRECATED_DECLARATIONS_BEGIN
    if (sandbox_init_with_parameters(temp.data(), flags, parameters.namedParameterArray(), &errorBuf)) {
        ALLOW_DEPRECATED_DECLARATIONS_END
        WTFLogAlways("%s: Could not initialize sandbox profile [%s], error '%s'\n", getprogname(), temp.data(), errorBuf);
        for (size_t i = 0, count = parameters.count(); i != count; ++i)
            WTFLogAlways("%s=%s\n", parameters.name(i), parameters.value(i));
        return false;
    }
    return true;
}

static bool applySandbox(const AuxiliaryProcessInitializationParameters& parameters, const SandboxInitializationParameters& sandboxInitializationParameters, const String& dataVaultParentDirectory)
{
    String profileOrProfilePath;
    bool isProfilePath;
    getSandboxProfileOrProfilePath(sandboxInitializationParameters, profileOrProfilePath, isProfilePath);
    if (profileOrProfilePath.isEmpty()) {
        WTFLogAlways("%s: Profile path is invalid\n", getprogname());
        CRASH();
    }

#if USE(CACHE_COMPILED_SANDBOX)
    // The plugin process's DARWIN_USER_TEMP_DIR and DARWIN_USER_CACHE_DIR sandbox parameters are randomized so
    // so the compiled sandbox should not be cached because it won't be reused.
    if (parameters.processType == AuxiliaryProcess::ProcessType::Plugin)
        return compileAndApplySandboxSlowCase(profileOrProfilePath, isProfilePath, sandboxInitializationParameters);

    SandboxParametersPtr sandboxParameters { sandbox_create_params() };
    if (!sandboxParameters) {
        WTFLogAlways("%s: Could not create sandbox parameters\n", getprogname());
        CRASH();
    }
    auto header = setAndSerializeSandboxParameters(sandboxInitializationParameters, sandboxParameters, profileOrProfilePath, isProfilePath);
    if (!header) {
        WTFLogAlways("%s: Sandbox parameters are invalid\n", getprogname());
        CRASH();
    }

    String directoryPath { sandboxDirectory(parameters.processType, dataVaultParentDirectory) };
    String filePath = sandboxFilePath(directoryPath, *header);
    SandboxInfo info {
        dataVaultParentDirectory,
        directoryPath,
        filePath,
        sandboxParameters,
        *header,
        parameters.processType,
        sandboxInitializationParameters,
        profileOrProfilePath,
        isProfilePath
    };

    if (tryApplyCachedSandbox(info))
        return true;

    SandboxProfilePtr sandboxProfile = compileAndCacheSandboxProfile(info);
    if (!sandboxProfile)
        return compileAndApplySandboxSlowCase(profileOrProfilePath, isProfilePath, sandboxInitializationParameters);

    if (sandbox_apply(sandboxProfile.get())) {
        WTFLogAlways("%s: Could not apply compiled sandbox: %s\n", getprogname(), strerror(errno));
        CRASH();
    }

    return true;
#else
    UNUSED_PARAM(parameters);
    UNUSED_PARAM(dataVaultParentDirectory);
    return compileAndApplySandboxSlowCase(profileOrProfilePath, isProfilePath, sandboxInitializationParameters);
#endif // USE(CACHE_COMPILED_SANDBOX)
}

static void initializeSandboxParameters(const AuxiliaryProcessInitializationParameters& parameters, SandboxInitializationParameters& sandboxParameters)
{
    // Verify user directory suffix.
    if (sandboxParameters.userDirectorySuffix().isNull()) {
        auto userDirectorySuffix = parameters.extraInitializationData.find("user-directory-suffix");
        if (userDirectorySuffix != parameters.extraInitializationData.end())
            sandboxParameters.setUserDirectorySuffix([makeString(userDirectorySuffix->value, '/', String([[NSBundle mainBundle] bundleIdentifier])) fileSystemRepresentation]);
        else {
            String clientIdentifier = codeSigningIdentifier(parameters.connectionIdentifier.xpcConnection.get());
            if (clientIdentifier.isNull())
                clientIdentifier = parameters.clientIdentifier;
            String defaultUserDirectorySuffix = makeString(String([[NSBundle mainBundle] bundleIdentifier]), '+', clientIdentifier);
            sandboxParameters.setUserDirectorySuffix(defaultUserDirectorySuffix);
        }
    }

    String osSystemMarketingVersion = systemMarketingVersion();
    Vector<String> osVersionParts = osSystemMarketingVersion.split('.');
    if (osVersionParts.size() < 2) {
        WTFLogAlways("%s: Couldn't find OS Version\n", getprogname());
        exit(EX_NOPERM);
    }
    String osVersion = osVersionParts[0] + '.' + osVersionParts[1];
    sandboxParameters.addParameter("_OS_VERSION", osVersion.utf8().data());

    // Use private temporary and cache directories.
    setenv("DIRHELPER_USER_DIR_SUFFIX", FileSystem::fileSystemRepresentation(sandboxParameters.userDirectorySuffix()).data(), 1);
    char temporaryDirectory[PATH_MAX];
    if (!confstr(_CS_DARWIN_USER_TEMP_DIR, temporaryDirectory, sizeof(temporaryDirectory))) {
        WTFLogAlways("%s: couldn't retrieve private temporary directory path: %d\n", getprogname(), errno);
        exit(EX_NOPERM);
    }
    setenv("TMPDIR", temporaryDirectory, 1);

    sandboxParameters.addPathParameter("WEBKIT2_FRAMEWORK_DIR", [[webKit2Bundle() bundlePath] stringByDeletingLastPathComponent]);
    sandboxParameters.addConfDirectoryParameter("DARWIN_USER_TEMP_DIR", _CS_DARWIN_USER_TEMP_DIR);
    sandboxParameters.addConfDirectoryParameter("DARWIN_USER_CACHE_DIR", _CS_DARWIN_USER_CACHE_DIR);

    char buffer[4096];
    int bufferSize = sizeof(buffer);
    struct passwd pwd;
    struct passwd* result = 0;
    if (getpwuid_r(getuid(), &pwd, buffer, bufferSize, &result) || !result) {
        WTFLogAlways("%s: Couldn't find home directory\n", getprogname());
        exit(EX_NOPERM);
    }

    sandboxParameters.addPathParameter("HOME_DIR", pwd.pw_dir);
    String path = String::fromUTF8(pwd.pw_dir);
    path.append("/Library");
    sandboxParameters.addPathParameter("HOME_LIBRARY_DIR", FileSystem::fileSystemRepresentation(path).data());
    path.append("/Preferences");
    sandboxParameters.addPathParameter("HOME_LIBRARY_PREFERENCES_DIR", FileSystem::fileSystemRepresentation(path).data());
}

void AuxiliaryProcess::initializeSandbox(const AuxiliaryProcessInitializationParameters& parameters, SandboxInitializationParameters& sandboxParameters)
{
    TraceScope traceScope(InitializeSandboxStart, InitializeSandboxEnd);

#if USE(CACHE_COMPILED_SANDBOX)
    // This must be called before initializeSandboxParameters so that the path does not include the user directory suffix.
    // We don't want the user directory suffix because we want all processes of the same type to use the same cache directory.
    String dataVaultParentDirectory { sandboxDataVaultParentDirectory() };
#else
    String dataVaultParentDirectory;
#endif

    initializeSandboxParameters(parameters, sandboxParameters);

    if (!applySandbox(parameters, sandboxParameters, dataVaultParentDirectory)) {
        WTFLogAlways("%s: Unable to apply sandbox\n", getprogname());
        CRASH();
    }

    if (shouldOverrideQuarantine()) {
        // This will override LSFileQuarantineEnabled from Info.plist unless sandbox quarantine is globally disabled.
        OSStatus error = enableSandboxStyleFileQuarantine();
        if (error) {
            WTFLogAlways("%s: Couldn't enable sandbox style file quarantine: %ld\n", getprogname(), static_cast<long>(error));
            exit(EX_NOPERM);
        }
    }
}

#if USE(APPKIT)
void AuxiliaryProcess::stopNSAppRunLoop()
{
    ASSERT([NSApp isRunning]);
    [NSApp stop:nil];

    NSEvent *event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0.0 windowNumber:0 context:nil subtype:0 data1:0 data2:0];
    [NSApp postEvent:event atStart:true];
}
#endif

#if !PLATFORM(MACCATALYST) && ENABLE(WEBPROCESS_NSRUNLOOP)
void AuxiliaryProcess::stopNSRunLoop()
{
    ASSERT([NSRunLoop mainRunLoop]);
    [[NSRunLoop mainRunLoop] performBlock:^{
        exit(0);
    }];
}
#endif

void AuxiliaryProcess::setQOS(int latencyQOS, int throughputQOS)
{
    if (!latencyQOS && !throughputQOS)
        return;

    struct task_qos_policy qosinfo = {
        latencyQOS ? LATENCY_QOS_TIER_0 + latencyQOS - 1 : LATENCY_QOS_TIER_UNSPECIFIED,
        throughputQOS ? THROUGHPUT_QOS_TIER_0 + throughputQOS - 1 : THROUGHPUT_QOS_TIER_UNSPECIFIED
    };

    task_policy_set(mach_task_self(), TASK_OVERRIDE_QOS_POLICY, (task_policy_t)&qosinfo, TASK_QOS_POLICY_COUNT);
}

#if PLATFORM(MAC)
bool AuxiliaryProcess::isSystemWebKit()
{
    static bool isSystemWebKit = []() -> bool {
#if HAVE(ALTERNATE_SYSTEM_LAYOUT)
        if ([[webKit2Bundle() bundlePath] hasPrefix:@"/Library/Apple/System/"])
            return true;
#endif
        return [[webKit2Bundle() bundlePath] hasPrefix:@"/System/"];
    }();
    return isSystemWebKit;
}
#endif

} // namespace WebKit

#endif
