blob: 3ccbdc6290ea812d8abeb57081c22f341c44f400 [file] [log] [blame]
/*
* Copyright (C) 2010-2020 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 "SandboxExtension.h"
#if ENABLE(SANDBOX_EXTENSIONS)
#import "DataReference.h"
#import "Decoder.h"
#import "Encoder.h"
#import <string.h>
#import <wtf/FileSystem.h>
#import <wtf/spi/darwin/SandboxSPI.h>
#import <wtf/text/CString.h>
namespace WebKit {
class SandboxExtensionImpl {
WTF_MAKE_FAST_ALLOCATED;
public:
static std::unique_ptr<SandboxExtensionImpl> create(const char* path, SandboxExtension::Type type, std::optional<audit_token_t> auditToken = std::nullopt, OptionSet<SandboxExtension::Flags> flags = SandboxExtension::Flags::Default)
{
std::unique_ptr<SandboxExtensionImpl> impl { new SandboxExtensionImpl(path, type, auditToken, flags) };
if (!impl->m_token)
return nullptr;
return impl;
}
SandboxExtensionImpl(const char* serializedFormat, size_t length)
: m_token { strndup(serializedFormat, length) }
{
}
~SandboxExtensionImpl()
{
if (!m_token)
return;
auto length = strlen(m_token);
memset_s(m_token, length, 0, length);
free(m_token);
}
bool WARN_UNUSED_RETURN consume()
{
m_handle = sandbox_extension_consume(m_token);
#if PLATFORM(IOS_FAMILY_SIMULATOR)
return !sandbox_check(getpid(), 0, SANDBOX_FILTER_NONE);
#else
if (m_handle == -1) {
LOG_ERROR("Could not create a sandbox extension for '%s', errno = %d", m_token, errno);
return false;
}
return true;
#endif
}
bool invalidate()
{
return !sandbox_extension_release(std::exchange(m_handle, 0));
}
const char* WARN_UNUSED_RETURN getSerializedFormat(size_t& length)
{
length = strlen(m_token);
return m_token;
}
private:
char* sandboxExtensionForType(const char* path, SandboxExtension::Type type, std::optional<audit_token_t> auditToken, OptionSet<SandboxExtension::Flags> flags)
{
uint32_t extensionFlags = 0;
if (flags & SandboxExtension::Flags::NoReport)
extensionFlags |= SANDBOX_EXTENSION_NO_REPORT;
if (flags & SandboxExtension::Flags::DoNotCanonicalize)
extensionFlags |= SANDBOX_EXTENSION_CANONICAL;
switch (type) {
case SandboxExtension::Type::ReadOnly:
return sandbox_extension_issue_file(APP_SANDBOX_READ, path, extensionFlags);
case SandboxExtension::Type::ReadWrite:
return sandbox_extension_issue_file(APP_SANDBOX_READ_WRITE, path, extensionFlags);
case SandboxExtension::Type::Mach:
if (!auditToken)
return sandbox_extension_issue_mach("com.apple.webkit.extension.mach", path, extensionFlags);
return sandbox_extension_issue_mach_to_process("com.apple.webkit.extension.mach", path, extensionFlags, *auditToken);
case SandboxExtension::Type::IOKit:
if (!auditToken)
return sandbox_extension_issue_iokit_registry_entry_class("com.apple.webkit.extension.iokit", path, extensionFlags);
return sandbox_extension_issue_iokit_registry_entry_class_to_process("com.apple.webkit.extension.iokit", path, extensionFlags, *auditToken);
case SandboxExtension::Type::Generic:
return sandbox_extension_issue_generic(path, extensionFlags);
case SandboxExtension::Type::ReadByProcess:
if (!auditToken)
return nullptr;
return sandbox_extension_issue_file_to_process(APP_SANDBOX_READ, path, extensionFlags, *auditToken);
}
}
SandboxExtensionImpl(const char* path, SandboxExtension::Type type, std::optional<audit_token_t> auditToken, OptionSet<SandboxExtension::Flags> flags)
: m_token { sandboxExtensionForType(path, type, auditToken, flags) }
{
}
char* m_token;
int64_t m_handle { 0 };
};
SandboxExtension::Handle::Handle()
{
}
SandboxExtension::Handle::Handle(Handle&&) = default;
SandboxExtension::Handle& SandboxExtension::Handle::operator=(Handle&&) = default;
SandboxExtension::Handle::~Handle()
{
if (m_sandboxExtension)
m_sandboxExtension->invalidate();
}
void SandboxExtension::Handle::encode(IPC::Encoder& encoder) const
{
if (!m_sandboxExtension) {
encoder << IPC::DataReference();
return;
}
size_t length = 0;
const char* serializedFormat = m_sandboxExtension->getSerializedFormat(length);
ASSERT(serializedFormat);
encoder << IPC::DataReference(reinterpret_cast<const uint8_t*>(serializedFormat), length);
// Encoding will destroy the sandbox extension locally.
m_sandboxExtension = 0;
}
auto SandboxExtension::Handle::decode(IPC::Decoder& decoder) -> std::optional<Handle>
{
IPC::DataReference dataReference;
if (!decoder.decode(dataReference))
return std::nullopt;
if (dataReference.isEmpty())
return {{ }};
Handle handle;
handle.m_sandboxExtension = makeUnique<SandboxExtensionImpl>(reinterpret_cast<const char*>(dataReference.data()), dataReference.size());
return WTFMove(handle);
}
RefPtr<SandboxExtension> SandboxExtension::create(Handle&& handle)
{
if (!handle.m_sandboxExtension)
return nullptr;
return adoptRef(new SandboxExtension(handle));
}
String stringByResolvingSymlinksInPath(const String& path)
{
char resolvedPath[PATH_MAX] = { 0 };
realpath(path.utf8().data(), resolvedPath);
return String::fromUTF8(resolvedPath);
}
String resolveAndCreateReadWriteDirectoryForSandboxExtension(const String& path)
{
NSError *error = nil;
NSString *nsPath = path;
if (![[NSFileManager defaultManager] createDirectoryAtPath:nsPath withIntermediateDirectories:YES attributes:nil error:&error]) {
NSLog(@"could not create directory \"%@\" for future sandbox extension, error %@", nsPath, error);
return { };
}
return resolvePathForSandboxExtension(path);
}
String resolvePathForSandboxExtension(const String& path)
{
String resolvedPath = stringByResolvingSymlinksInPath(path);
if (resolvedPath.isNull()) {
LOG_ERROR("Could not create a valid file system representation for the string '%s' of length %lu", resolvedPath.utf8().data(), resolvedPath.length());
return { };
}
return resolvedPath;
}
auto SandboxExtension::createHandleWithoutResolvingPath(const String& path, Type type) -> std::optional<Handle>
{
Handle handle;
ASSERT(!handle.m_sandboxExtension);
handle.m_sandboxExtension = SandboxExtensionImpl::create(path.utf8().data(), type, std::nullopt, SandboxExtension::Flags::DoNotCanonicalize);
if (!handle.m_sandboxExtension) {
LOG_ERROR("Could not create a sandbox extension for '%s'", path.utf8().data());
return std::nullopt;
}
return WTFMove(handle);
}
auto SandboxExtension::createHandle(const String& path, Type type) -> std::optional<Handle>
{
return createHandleWithoutResolvingPath(resolvePathForSandboxExtension(path), type);
}
template <typename T>
static Vector<SandboxExtension::Handle> createHandlesForResources(const Vector<T>& resources, Function<std::optional<SandboxExtension::Handle>(const T&)>&& createFunction)
{
Vector<SandboxExtension::Handle> handleArray;
for (const auto& resource : resources) {
if (auto handle = createFunction(resource))
handleArray.append(WTFMove(*handle));
}
return handleArray;
}
auto SandboxExtension::createReadOnlyHandlesForFiles(ASCIILiteral logLabel, const Vector<String>& paths) -> Vector<Handle>
{
return createHandlesForResources<String>(paths, [&logLabel] (const String& path) {
auto handle = SandboxExtension::createHandle(path, SandboxExtension::Type::ReadOnly);
if (!handle) {
// This can legitimately fail if a directory containing the file is deleted after the file was chosen.
// We also have reports of cases where this likely fails for some unknown reason, <rdar://problem/10156710>.
WTFLogAlways("%s: could not create a sandbox extension for '%s'\n", logLabel.characters(), path.utf8().data());
ASSERT_NOT_REACHED();
}
return handle;
});
}
auto SandboxExtension::createHandleForReadWriteDirectory(const String& path) -> std::optional<Handle>
{
String resolvedPath = resolveAndCreateReadWriteDirectoryForSandboxExtension(path);
if (resolvedPath.isNull())
return std::nullopt;
return SandboxExtension::createHandleWithoutResolvingPath(resolvedPath, SandboxExtension::Type::ReadWrite);
}
auto SandboxExtension::createHandleForTemporaryFile(const String& prefix, Type type) -> std::optional<std::pair<Handle, String>>
{
Handle handle;
ASSERT(!handle.m_sandboxExtension);
Vector<char> path(PATH_MAX);
if (!confstr(_CS_DARWIN_USER_TEMP_DIR, path.data(), path.size()))
return std::nullopt;
// Shrink the vector.
path.shrink(strlen(path.data()));
// FIXME: Change to a runtime assertion that the path ends with a slash once <rdar://problem/23579077> is
// fixed in all iOS Simulator versions that we use.
if (path.last() != '/')
path.append('/');
// Append the file name.
path.append(prefix.utf8().data(), prefix.length());
path.append('\0');
handle.m_sandboxExtension = SandboxExtensionImpl::create(FileSystem::fileSystemRepresentation(path.data()).data(), type);
if (!handle.m_sandboxExtension) {
WTFLogAlways("Could not create a sandbox extension for temporary file '%s'", path.data());
return std::nullopt;
}
return {{ WTFMove(handle), String(path.data()) }};
}
auto SandboxExtension::createHandleForGenericExtension(ASCIILiteral extensionClass) -> std::optional<Handle>
{
Handle handle;
ASSERT(!handle.m_sandboxExtension);
handle.m_sandboxExtension = SandboxExtensionImpl::create(extensionClass.characters(), Type::Generic);
if (!handle.m_sandboxExtension) {
WTFLogAlways("Could not create a '%s' sandbox extension", extensionClass.characters());
return std::nullopt;
}
return WTFMove(handle);
}
auto SandboxExtension::createHandleForMachLookup(ASCIILiteral service, std::optional<audit_token_t> auditToken, OptionSet<Flags> flags) -> std::optional<Handle>
{
Handle handle;
ASSERT(!handle.m_sandboxExtension);
handle.m_sandboxExtension = SandboxExtensionImpl::create(service.characters(), Type::Mach, auditToken, flags);
if (!handle.m_sandboxExtension) {
WTFLogAlways("Could not create a '%s' sandbox extension", service.characters());
return std::nullopt;
}
return WTFMove(handle);
}
auto SandboxExtension::createHandlesForMachLookup(const Vector<ASCIILiteral>& services, std::optional<audit_token_t> auditToken, OptionSet<Flags> flags) -> Vector<Handle>
{
return createHandlesForResources<ASCIILiteral>(services, [auditToken, flags] (const ASCIILiteral& service) -> std::optional<Handle> {
auto handle = SandboxExtension::createHandleForMachLookup(service, auditToken, flags);
ASSERT(handle);
return handle;
});
}
auto SandboxExtension::createHandleForReadByAuditToken(const String& path, audit_token_t auditToken) -> std::optional<Handle>
{
Handle handle;
ASSERT(!handle.m_sandboxExtension);
handle.m_sandboxExtension = SandboxExtensionImpl::create(path.utf8().data(), Type::ReadByProcess, auditToken);
if (!handle.m_sandboxExtension) {
LOG_ERROR("Could not create a sandbox extension for '%s'", path.utf8().data());
return std::nullopt;
}
return WTFMove(handle);
}
auto SandboxExtension::createHandleForIOKitClassExtension(ASCIILiteral ioKitClass, std::optional<audit_token_t> auditToken, OptionSet<Flags> flags) -> std::optional<Handle>
{
Handle handle;
ASSERT(!handle.m_sandboxExtension);
handle.m_sandboxExtension = SandboxExtensionImpl::create(ioKitClass.characters(), Type::IOKit, auditToken);
if (!handle.m_sandboxExtension) {
LOG_ERROR("Could not create a sandbox extension for '%s'", ioKitClass.characters());
return std::nullopt;
}
return WTFMove(handle);
}
auto SandboxExtension::createHandlesForIOKitClassExtensions(const Vector<ASCIILiteral>& iokitClasses, std::optional<audit_token_t> auditToken, OptionSet<Flags> flags) -> Vector<Handle>
{
return createHandlesForResources<ASCIILiteral>(iokitClasses, [auditToken, flags] (const ASCIILiteral& iokitClass) {
auto handle = SandboxExtension::createHandleForIOKitClassExtension(iokitClass, auditToken, flags);
ASSERT(handle);
return handle;
});
}
SandboxExtension::SandboxExtension(const Handle& handle)
: m_sandboxExtension(WTFMove(handle.m_sandboxExtension))
{
}
SandboxExtension::~SandboxExtension()
{
if (!m_sandboxExtension)
return;
ASSERT(!m_useCount);
}
bool SandboxExtension::revoke()
{
ASSERT(m_sandboxExtension);
ASSERT(m_useCount);
if (--m_useCount)
return true;
return m_sandboxExtension->invalidate();
}
bool SandboxExtension::consume()
{
ASSERT(m_sandboxExtension);
if (m_useCount++)
return true;
return m_sandboxExtension->consume();
}
bool SandboxExtension::consumePermanently()
{
ASSERT(m_sandboxExtension);
bool result = m_sandboxExtension->consume();
// Destroy the extension without invalidating it.
m_sandboxExtension = nullptr;
return result;
}
bool SandboxExtension::consumePermanently(const Handle& handle)
{
if (!handle.m_sandboxExtension)
return false;
bool result = handle.m_sandboxExtension->consume();
// Destroy the extension without invalidating it.
handle.m_sandboxExtension = nullptr;
return result;
}
bool SandboxExtension::consumePermanently(const Vector<Handle>& handleArray)
{
bool allSucceeded = true;
for (auto& handle : handleArray) {
if (!handle.m_sandboxExtension)
continue;
bool ok = SandboxExtension::consumePermanently(handle);
ASSERT(ok);
allSucceeded &= ok;
}
return allSucceeded;
}
} // namespace WebKit
#endif // ENABLE(SANDBOX_EXTENSIONS)