blob: ca915a739e840817ccab7d2c5ea0f24691125422 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/common/sandbox_mac.h"
#import <Cocoa/Cocoa.h>
#include <stddef.h>
#include <stdint.h>
#include <CoreFoundation/CFTimeZone.h>
extern "C" {
#include <sandbox.h>
}
#include <signal.h>
#include <sys/param.h>
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/mac/bundle_locations.h"
#include "base/mac/foundation_util.h"
#include "base/mac/mac_util.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/mac/scoped_nsautorelease_pool.h"
#include "base/mac/scoped_nsobject.h"
#include "base/macros.h"
#include "base/rand_util.h"
#include "base/strings/string16.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/sys_info.h"
#include "content/common/gpu/media/vt_video_decode_accelerator_mac.h"
#include "content/grit/content_resources.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "third_party/icu/source/common/unicode/uchar.h"
#include "ui/base/layout.h"
#include "ui/gl/gl_surface.h"
extern "C" {
void CGSSetDenyWindowServerConnections(bool);
void CGSShutdownServerConnections();
void* sandbox_create_params();
int sandbox_set_param(void* params, const char* key, const char* value);
void* sandbox_compile_string(const char* profile_str,
void* params,
char** error);
int sandbox_apply(void* profile);
void sandbox_free_params(void* params);
void sandbox_free_profile(void* profile);
};
namespace content {
namespace {
// Is the sandbox currently active.
bool gSandboxIsActive = false;
struct SandboxTypeToResourceIDMapping {
SandboxType sandbox_type;
int sandbox_profile_resource_id;
};
// This is the internal definition of the structure used by sandbox parameters
// on OS X 10.6.
struct SandboxParams {
void* buf;
size_t count;
size_t size;
};
// Mapping from sandbox process types to resource IDs containing the sandbox
// profile for all process types known to content.
SandboxTypeToResourceIDMapping kDefaultSandboxTypeToResourceIDMapping[] = {
{ SANDBOX_TYPE_RENDERER, IDR_RENDERER_SANDBOX_PROFILE },
{ SANDBOX_TYPE_UTILITY, IDR_UTILITY_SANDBOX_PROFILE },
{ SANDBOX_TYPE_GPU, IDR_GPU_SANDBOX_PROFILE },
{ SANDBOX_TYPE_PPAPI, IDR_PPAPI_SANDBOX_PROFILE },
};
static_assert(arraysize(kDefaultSandboxTypeToResourceIDMapping) == \
size_t(SANDBOX_TYPE_AFTER_LAST_TYPE), \
"sandbox type to resource id mapping incorrect");
// Try to escape |c| as a "SingleEscapeCharacter" (\n, etc). If successful,
// returns true and appends the escape sequence to |dst|.
bool EscapeSingleChar(char c, std::string* dst) {
const char *append = NULL;
switch (c) {
case '\b':
append = "\\b";
break;
case '\f':
append = "\\f";
break;
case '\n':
append = "\\n";
break;
case '\r':
append = "\\r";
break;
case '\t':
append = "\\t";
break;
case '\\':
append = "\\\\";
break;
case '"':
append = "\\\"";
break;
}
if (!append) {
return false;
}
dst->append(append);
return true;
}
// Errors quoting strings for the Sandbox profile are always fatal, report them
// in a central place.
NOINLINE void FatalStringQuoteException(const std::string& str) {
// Copy bad string to the stack so it's recorded in the crash dump.
char bad_string[256] = {0};
base::strlcpy(bad_string, str.c_str(), arraysize(bad_string));
DLOG(FATAL) << "String quoting failed " << bad_string;
}
} // namespace
SandboxCompiler::SandboxCompiler(const std::string& profile_str)
: params_map_(), profile_str_(profile_str) {
}
SandboxCompiler::~SandboxCompiler() {
}
bool SandboxCompiler::InsertBooleanParam(const std::string& key, bool value) {
return params_map_.insert(std::make_pair(key, value ? "TRUE" : "FALSE"))
.second;
}
bool SandboxCompiler::InsertStringParam(const std::string& key,
const std::string& value) {
return params_map_.insert(std::make_pair(key, value)).second;
}
void SandboxCompiler::FreeSandboxResources(void* profile,
void* params,
char* error) {
if (error)
sandbox_free_error(error);
if (params)
sandbox_free_params(params);
if (profile)
sandbox_free_profile(profile);
}
bool SandboxCompiler::CompileAndApplyProfile(std::string* error) {
char* error_internal = nullptr;
void* profile = nullptr;
void* params = nullptr;
if (!params_map_.empty()) {
if (base::mac::IsOSSnowLeopard()) {
// This is a workaround for 10.6, see crbug.com/509114.
// Check that there is no integer overflow.
base::CheckedNumeric<size_t> checked_size = params_map_.size();
checked_size *= 2;
if (!checked_size.IsValid())
return false;
SandboxParams* internal_params =
static_cast<SandboxParams*>(malloc(sizeof(SandboxParams)));
internal_params->buf = calloc(checked_size.ValueOrDie(), sizeof(void*));
internal_params->count = 0;
internal_params->size = checked_size.ValueOrDie();
params = internal_params;
} else {
params = sandbox_create_params();
if (!params)
return false;
}
for (const auto& kv : params_map_)
sandbox_set_param(params, kv.first.c_str(), kv.second.c_str());
}
profile =
sandbox_compile_string(profile_str_.c_str(), params, &error_internal);
if (!profile) {
error->assign(error_internal);
FreeSandboxResources(profile, params, error_internal);
return false;
}
int result = sandbox_apply(profile);
FreeSandboxResources(profile, params, error_internal);
return result == 0;
}
// static
bool Sandbox::QuotePlainString(const std::string& src_utf8, std::string* dst) {
dst->clear();
const char* src = src_utf8.c_str();
int32_t length = src_utf8.length();
int32_t position = 0;
while (position < length) {
UChar32 c;
U8_NEXT(src, position, length, c); // Macro increments |position|.
DCHECK_GE(c, 0);
if (c < 0)
return false;
if (c < 128) { // EscapeSingleChar only handles ASCII.
char as_char = static_cast<char>(c);
if (EscapeSingleChar(as_char, dst)) {
continue;
}
}
if (c < 32 || c > 126) {
// Any characters that aren't printable ASCII get the \u treatment.
unsigned int as_uint = static_cast<unsigned int>(c);
base::StringAppendF(dst, "\\u%04X", as_uint);
continue;
}
// If we got here we know that the character in question is strictly
// in the ASCII range so there's no need to do any kind of encoding
// conversion.
dst->push_back(static_cast<char>(c));
}
return true;
}
// static
bool Sandbox::QuoteStringForRegex(const std::string& str_utf8,
std::string* dst) {
// Characters with special meanings in sandbox profile syntax.
const char regex_special_chars[] = {
'\\',
// Metacharacters
'^',
'.',
'[',
']',
'$',
'(',
')',
'|',
// Quantifiers
'*',
'+',
'?',
'{',
'}',
};
// Anchor regex at start of path.
dst->assign("^");
const char* src = str_utf8.c_str();
int32_t length = str_utf8.length();
int32_t position = 0;
while (position < length) {
UChar32 c;
U8_NEXT(src, position, length, c); // Macro increments |position|.
DCHECK_GE(c, 0);
if (c < 0)
return false;
// The Mac sandbox regex parser only handles printable ASCII characters.
// 33 >= c <= 126
if (c < 32 || c > 125) {
return false;
}
for (size_t i = 0; i < arraysize(regex_special_chars); ++i) {
if (c == regex_special_chars[i]) {
dst->push_back('\\');
break;
}
}
dst->push_back(static_cast<char>(c));
}
// Make sure last element of path is interpreted as a directory. Leaving this
// off would allow access to files if they start with the same name as the
// directory.
dst->append("(/|$)");
return true;
}
// Warm up System APIs that empirically need to be accessed before the Sandbox
// is turned on.
// This method is layed out in blocks, each one containing a separate function
// that needs to be warmed up. The OS version on which we found the need to
// enable the function is also noted.
// This function is tested on the following OS versions:
// 10.5.6, 10.6.0
// static
void Sandbox::SandboxWarmup(int sandbox_type) {
base::mac::ScopedNSAutoreleasePool scoped_pool;
{ // CGColorSpaceCreateWithName(), CGBitmapContextCreate() - 10.5.6
base::ScopedCFTypeRef<CGColorSpaceRef> rgb_colorspace(
CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB));
// Allocate a 1x1 image.
char data[4];
base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate(
data,
1,
1,
8,
1 * 4,
rgb_colorspace,
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host));
// Load in the color profiles we'll need (as a side effect).
ignore_result(base::mac::GetSRGBColorSpace());
ignore_result(base::mac::GetSystemColorSpace());
// CGColorSpaceCreateSystemDefaultCMYK - 10.6
base::ScopedCFTypeRef<CGColorSpaceRef> cmyk_colorspace(
CGColorSpaceCreateWithName(kCGColorSpaceGenericCMYK));
}
{ // localtime() - 10.5.6
time_t tv = {0};
localtime(&tv);
}
{ // Gestalt() tries to read /System/Library/CoreServices/SystemVersion.plist
// on 10.5.6
int32_t tmp;
base::SysInfo::OperatingSystemVersionNumbers(&tmp, &tmp, &tmp);
}
{ // CGImageSourceGetStatus() - 10.6
// Create a png with just enough data to get everything warmed up...
char png_header[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
NSData* data = [NSData dataWithBytes:png_header
length:arraysize(png_header)];
base::ScopedCFTypeRef<CGImageSourceRef> img(
CGImageSourceCreateWithData((CFDataRef)data, NULL));
CGImageSourceGetStatus(img);
}
{
// Allow access to /dev/urandom.
base::GetUrandomFD();
}
{ // IOSurfaceLookup() - 10.7
// Needed by zero-copy texture update framework - crbug.com/323338
base::ScopedCFTypeRef<IOSurfaceRef> io_surface(IOSurfaceLookup(0));
}
// Process-type dependent warm-up.
if (sandbox_type == SANDBOX_TYPE_UTILITY) {
// CFTimeZoneCopyZone() tries to read /etc and /private/etc/localtime - 10.8
// Needed by Media Galleries API Picasa - crbug.com/151701
CFTimeZoneCopySystem();
}
if (sandbox_type == SANDBOX_TYPE_GPU) {
// Preload either the desktop GL or the osmesa so, depending on the
// --use-gl flag.
gfx::GLSurface::InitializeOneOff();
// Preload VideoToolbox.
InitializeVideoToolbox();
}
if (sandbox_type == SANDBOX_TYPE_PPAPI) {
// Preload AppKit color spaces used for Flash/ppapi. http://crbug.com/348304
NSColor* color = [NSColor controlTextColor];
[color colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
}
if (sandbox_type == SANDBOX_TYPE_RENDERER &&
base::mac::IsOSMountainLionOrLater()) {
// Now disconnect from WindowServer, after all objects have been warmed up.
// Shutting down the connection requires connecting to WindowServer,
// so do this before actually engaging the sandbox. This is only done on
// 10.8 and higher because doing it on earlier OSes causes layout tests to
// fail <http://crbug.com/397642#c48>. This may cause two log messages to
// be printed to the system logger on certain OS versions.
CGSSetDenyWindowServerConnections(true);
CGSShutdownServerConnections();
}
}
// Load the appropriate template for the given sandbox type.
// Returns the template as an NSString or nil on error.
NSString* LoadSandboxTemplate(int sandbox_type) {
// We use a custom sandbox definition to lock things down as tightly as
// possible.
int sandbox_profile_resource_id = -1;
// Find resource id for sandbox profile to use for the specific sandbox type.
for (size_t i = 0;
i < arraysize(kDefaultSandboxTypeToResourceIDMapping);
++i) {
if (kDefaultSandboxTypeToResourceIDMapping[i].sandbox_type ==
sandbox_type) {
sandbox_profile_resource_id =
kDefaultSandboxTypeToResourceIDMapping[i].sandbox_profile_resource_id;
break;
}
}
if (sandbox_profile_resource_id == -1) {
// Check if the embedder knows about this sandbox process type.
bool sandbox_type_found =
GetContentClient()->GetSandboxProfileForSandboxType(
sandbox_type, &sandbox_profile_resource_id);
CHECK(sandbox_type_found) << "Unknown sandbox type " << sandbox_type;
}
base::StringPiece sandbox_definition =
GetContentClient()->GetDataResource(
sandbox_profile_resource_id, ui::SCALE_FACTOR_NONE);
if (sandbox_definition.empty()) {
LOG(FATAL) << "Failed to load the sandbox profile (resource id "
<< sandbox_profile_resource_id << ")";
return nil;
}
base::StringPiece common_sandbox_definition =
GetContentClient()->GetDataResource(
IDR_COMMON_SANDBOX_PROFILE, ui::SCALE_FACTOR_NONE);
if (common_sandbox_definition.empty()) {
LOG(FATAL) << "Failed to load the common sandbox profile";
return nil;
}
base::scoped_nsobject<NSString> common_sandbox_prefix_data(
[[NSString alloc] initWithBytes:common_sandbox_definition.data()
length:common_sandbox_definition.length()
encoding:NSUTF8StringEncoding]);
base::scoped_nsobject<NSString> sandbox_data(
[[NSString alloc] initWithBytes:sandbox_definition.data()
length:sandbox_definition.length()
encoding:NSUTF8StringEncoding]);
// Prefix sandbox_data with common_sandbox_prefix_data.
return [common_sandbox_prefix_data stringByAppendingString:sandbox_data];
}
// Turns on the OS X sandbox for this process.
// static
bool Sandbox::EnableSandbox(int sandbox_type,
const base::FilePath& allowed_dir) {
// Sanity - currently only SANDBOX_TYPE_UTILITY supports a directory being
// passed in.
if (sandbox_type < SANDBOX_TYPE_AFTER_LAST_TYPE &&
sandbox_type != SANDBOX_TYPE_UTILITY) {
DCHECK(allowed_dir.empty())
<< "Only SANDBOX_TYPE_UTILITY allows a custom directory parameter.";
}
NSString* sandbox_data = LoadSandboxTemplate(sandbox_type);
if (!sandbox_data) {
return false;
}
SandboxCompiler compiler([sandbox_data UTF8String]);
if (!allowed_dir.empty()) {
// Add the sandbox parameters necessary to access the given directory.
base::FilePath allowed_dir_canonical = GetCanonicalSandboxPath(allowed_dir);
std::string regex;
if (!QuoteStringForRegex(allowed_dir_canonical.value(), &regex)) {
FatalStringQuoteException(allowed_dir_canonical.value());
return false;
}
if (!compiler.InsertStringParam("PERMITTED_DIR", regex))
return false;
}
// Enable verbose logging if enabled on the command line. (See common.sb
// for details).
const base::CommandLine* command_line =
base::CommandLine::ForCurrentProcess();
bool enable_logging =
command_line->HasSwitch(switches::kEnableSandboxLogging);;
if (!compiler.InsertBooleanParam("ENABLE_LOGGING", enable_logging))
return false;
// Without this, the sandbox will print a message to the system log every
// time it denies a request. This floods the console with useless spew.
if (!compiler.InsertBooleanParam("DISABLE_SANDBOX_DENIAL_LOGGING",
!enable_logging))
return false;
// Splice the path of the user's home directory into the sandbox profile
// (see renderer.sb for details).
std::string home_dir = [NSHomeDirectory() fileSystemRepresentation];
base::FilePath home_dir_canonical =
GetCanonicalSandboxPath(base::FilePath(home_dir));
std::string quoted_home_dir;
if (!QuotePlainString(home_dir_canonical.value(), &quoted_home_dir)) {
FatalStringQuoteException(home_dir_canonical.value());
return false;
}
if (!compiler.InsertStringParam("USER_HOMEDIR_AS_LITERAL", quoted_home_dir))
return false;
bool lion_or_later = base::mac::IsOSLionOrLater();
if (!compiler.InsertBooleanParam("LION_OR_LATER", lion_or_later))
return false;
bool elcap_or_later = base::mac::IsOSElCapitanOrLater();
if (!compiler.InsertBooleanParam("ELCAP_OR_LATER", elcap_or_later))
return false;
#if defined(COMPONENT_BUILD)
// dlopen() fails without file-read-metadata access if the executable image
// contains LC_RPATH load commands. The components build uses those.
// See http://crbug.com/127465
if (base::mac::IsOSSnowLeopard()) {
if (!compiler.InsertBooleanParam("COMPONENT_BUILD_WORKAROUND", true))
return false;
}
#endif
// Initialize sandbox.
std::string error_str;
bool success = compiler.CompileAndApplyProfile(&error_str);
DLOG_IF(FATAL, !success) << "Failed to initialize sandbox: " << error_str;
gSandboxIsActive = success;
return success;
}
// static
bool Sandbox::SandboxIsCurrentlyActive() {
return gSandboxIsActive;
}
// static
base::FilePath Sandbox::GetCanonicalSandboxPath(const base::FilePath& path) {
base::ScopedFD fd(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)));
if (!fd.is_valid()) {
DPLOG(FATAL) << "GetCanonicalSandboxPath() failed for: "
<< path.value();
return path;
}
base::FilePath::CharType canonical_path[MAXPATHLEN];
if (HANDLE_EINTR(fcntl(fd.get(), F_GETPATH, canonical_path)) != 0) {
DPLOG(FATAL) << "GetCanonicalSandboxPath() failed for: "
<< path.value();
return path;
}
return base::FilePath(canonical_path);
}
} // namespace content