1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/common/sandbox_mac.h"
7 #import <Cocoa/Cocoa.h>
9 #include <CoreFoundation/CFTimeZone.h>
14 #include <sys/param.h>
16 #include "base/basictypes.h"
17 #include "base/command_line.h"
18 #include "base/compiler_specific.h"
19 #include "base/file_util.h"
20 #include "base/files/scoped_file.h"
21 #include "base/mac/bundle_locations.h"
22 #include "base/mac/mac_util.h"
23 #include "base/mac/scoped_cftyperef.h"
24 #include "base/mac/scoped_nsautorelease_pool.h"
25 #include "base/mac/scoped_nsobject.h"
26 #include "base/rand_util.h"
27 #include "base/strings/string16.h"
28 #include "base/strings/string_piece.h"
29 #include "base/strings/string_util.h"
30 #include "base/strings/stringprintf.h"
31 #include "base/strings/sys_string_conversions.h"
32 #include "base/strings/utf_string_conversions.h"
33 #include "base/sys_info.h"
34 #include "content/public/common/content_client.h"
35 #include "content/public/common/content_switches.h"
36 #include "grit/content_resources.h"
37 #include "third_party/icu/source/common/unicode/uchar.h"
38 #include "ui/base/layout.h"
39 #include "ui/gl/gl_surface.h"
40 #include "ui/gl/io_surface_support_mac.h"
45 // Is the sandbox currently active.
46 bool gSandboxIsActive = false;
48 struct SandboxTypeToResourceIDMapping {
49 SandboxType sandbox_type;
50 int sandbox_profile_resource_id;
53 // Mapping from sandbox process types to resource IDs containing the sandbox
54 // profile for all process types known to content.
55 SandboxTypeToResourceIDMapping kDefaultSandboxTypeToResourceIDMapping[] = {
56 { SANDBOX_TYPE_RENDERER, IDR_RENDERER_SANDBOX_PROFILE },
57 { SANDBOX_TYPE_WORKER, IDR_WORKER_SANDBOX_PROFILE },
58 { SANDBOX_TYPE_UTILITY, IDR_UTILITY_SANDBOX_PROFILE },
59 { SANDBOX_TYPE_GPU, IDR_GPU_SANDBOX_PROFILE },
60 { SANDBOX_TYPE_PPAPI, IDR_PPAPI_SANDBOX_PROFILE },
63 COMPILE_ASSERT(arraysize(kDefaultSandboxTypeToResourceIDMapping) == \
64 size_t(SANDBOX_TYPE_AFTER_LAST_TYPE), \
65 sandbox_type_to_resource_id_mapping_incorrect);
67 // Try to escape |c| as a "SingleEscapeCharacter" (\n, etc). If successful,
68 // returns true and appends the escape sequence to |dst|.
69 bool EscapeSingleChar(char c, std::string* dst) {
70 const char *append = NULL;
103 // Errors quoting strings for the Sandbox profile are always fatal, report them
104 // in a central place.
105 NOINLINE void FatalStringQuoteException(const std::string& str) {
106 // Copy bad string to the stack so it's recorded in the crash dump.
107 char bad_string[256] = {0};
108 base::strlcpy(bad_string, str.c_str(), arraysize(bad_string));
109 DLOG(FATAL) << "String quoting failed " << bad_string;
115 NSString* Sandbox::AllowMetadataForPath(const base::FilePath& allowed_path) {
116 // Collect a list of all parent directories.
117 base::FilePath last_path = allowed_path;
118 std::vector<base::FilePath> subpaths;
119 for (base::FilePath path = allowed_path;
120 path.value() != last_path.value();
121 path = path.DirName()) {
122 subpaths.push_back(path);
126 // Iterate through all parents and allow stat() on them explicitly.
127 NSString* sandbox_command = @"(allow file-read-metadata ";
128 for (std::vector<base::FilePath>::reverse_iterator i = subpaths.rbegin();
129 i != subpaths.rend();
131 std::string subdir_escaped;
132 if (!QuotePlainString(i->value(), &subdir_escaped)) {
133 FatalStringQuoteException(i->value());
137 NSString* subdir_escaped_ns =
138 base::SysUTF8ToNSString(subdir_escaped.c_str());
140 [sandbox_command stringByAppendingFormat:@"(literal \"%@\")",
144 return [sandbox_command stringByAppendingString:@")"];
148 bool Sandbox::QuotePlainString(const std::string& src_utf8, std::string* dst) {
151 const char* src = src_utf8.c_str();
152 int32_t length = src_utf8.length();
153 int32_t position = 0;
154 while (position < length) {
156 U8_NEXT(src, position, length, c); // Macro increments |position|.
161 if (c < 128) { // EscapeSingleChar only handles ASCII.
162 char as_char = static_cast<char>(c);
163 if (EscapeSingleChar(as_char, dst)) {
168 if (c < 32 || c > 126) {
169 // Any characters that aren't printable ASCII get the \u treatment.
170 unsigned int as_uint = static_cast<unsigned int>(c);
171 base::StringAppendF(dst, "\\u%04X", as_uint);
175 // If we got here we know that the character in question is strictly
176 // in the ASCII range so there's no need to do any kind of encoding
178 dst->push_back(static_cast<char>(c));
184 bool Sandbox::QuoteStringForRegex(const std::string& str_utf8,
186 // Characters with special meanings in sandbox profile syntax.
187 const char regex_special_chars[] = {
208 // Anchor regex at start of path.
211 const char* src = str_utf8.c_str();
212 int32_t length = str_utf8.length();
213 int32_t position = 0;
214 while (position < length) {
216 U8_NEXT(src, position, length, c); // Macro increments |position|.
221 // The Mac sandbox regex parser only handles printable ASCII characters.
223 if (c < 32 || c > 125) {
227 for (size_t i = 0; i < arraysize(regex_special_chars); ++i) {
228 if (c == regex_special_chars[i]) {
229 dst->push_back('\\');
234 dst->push_back(static_cast<char>(c));
237 // Make sure last element of path is interpreted as a directory. Leaving this
238 // off would allow access to files if they start with the same name as the
240 dst->append("(/|$)");
245 // Warm up System APIs that empirically need to be accessed before the Sandbox
247 // This method is layed out in blocks, each one containing a separate function
248 // that needs to be warmed up. The OS version on which we found the need to
249 // enable the function is also noted.
250 // This function is tested on the following OS versions:
254 void Sandbox::SandboxWarmup(int sandbox_type) {
255 base::mac::ScopedNSAutoreleasePool scoped_pool;
257 { // CGColorSpaceCreateWithName(), CGBitmapContextCreate() - 10.5.6
258 base::ScopedCFTypeRef<CGColorSpaceRef> rgb_colorspace(
259 CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB));
261 // Allocate a 1x1 image.
263 base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate(
270 kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host));
272 // Load in the color profiles we'll need (as a side effect).
273 (void) base::mac::GetSRGBColorSpace();
274 (void) base::mac::GetSystemColorSpace();
276 // CGColorSpaceCreateSystemDefaultCMYK - 10.6
277 base::ScopedCFTypeRef<CGColorSpaceRef> cmyk_colorspace(
278 CGColorSpaceCreateWithName(kCGColorSpaceGenericCMYK));
281 { // localtime() - 10.5.6
286 { // Gestalt() tries to read /System/Library/CoreServices/SystemVersion.plist
289 base::SysInfo::OperatingSystemVersionNumbers(&tmp, &tmp, &tmp);
292 { // CGImageSourceGetStatus() - 10.6
293 // Create a png with just enough data to get everything warmed up...
294 char png_header[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
295 NSData* data = [NSData dataWithBytes:png_header
296 length:arraysize(png_header)];
297 base::ScopedCFTypeRef<CGImageSourceRef> img(
298 CGImageSourceCreateWithData((CFDataRef)data, NULL));
299 CGImageSourceGetStatus(img);
303 // Allow access to /dev/urandom.
304 base::GetUrandomFD();
307 { // IOSurfaceLookup() - 10.7
308 // Needed by zero-copy texture update framework - crbug.com/323338
309 IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize();
310 if (io_surface_support) {
311 base::ScopedCFTypeRef<CFTypeRef> io_surface(
312 io_surface_support->IOSurfaceLookup(0));
316 // Process-type dependent warm-up.
317 if (sandbox_type == SANDBOX_TYPE_UTILITY) {
318 // CFTimeZoneCopyZone() tries to read /etc and /private/etc/localtime - 10.8
319 // Needed by Media Galleries API Picasa - crbug.com/151701
320 CFTimeZoneCopySystem();
323 if (sandbox_type == SANDBOX_TYPE_GPU) {
324 // Preload either the desktop GL or the osmesa so, depending on the
326 gfx::GLSurface::InitializeOneOff();
329 if (sandbox_type == SANDBOX_TYPE_PPAPI) {
330 // Preload AppKit color spaces used for Flash/ppapi. http://crbug.com/348304
331 NSColor* color = [NSColor controlTextColor];
332 [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
337 NSString* Sandbox::BuildAllowDirectoryAccessSandboxString(
338 const base::FilePath& allowed_dir,
339 SandboxVariableSubstitions* substitutions) {
340 // A whitelist is used to determine which directories can be statted
341 // This means that in the case of an /a/b/c/d/ directory, we may be able to
342 // stat the leaf directory, but not its parent.
343 // The extension code in Chrome calls realpath() which fails if it can't call
344 // stat() on one of the parent directories in the path.
345 // The solution to this is to allow statting the parent directories themselves
346 // but not their contents. We need to add a separate rule for each parent
349 // The sandbox only understands "real" paths. This resolving step is
350 // needed so the caller doesn't need to worry about things like /var
351 // being a link to /private/var (like in the paths CreateNewTempDirectory()
353 base::FilePath allowed_dir_canonical = GetCanonicalSandboxPath(allowed_dir);
355 NSString* sandbox_command = AllowMetadataForPath(allowed_dir_canonical);
356 sandbox_command = [sandbox_command
357 substringToIndex:[sandbox_command length] - 1]; // strip trailing ')'
359 // Finally append the leaf directory. Unlike its parents (for which only
360 // stat() should be allowed), the leaf directory needs full access.
361 (*substitutions)["ALLOWED_DIR"] =
362 SandboxSubstring(allowed_dir_canonical.value(),
363 SandboxSubstring::REGEX);
366 stringByAppendingString:@") (allow file-read* file-write*"
367 " (regex #\"@ALLOWED_DIR@\") )"];
368 return sandbox_command;
371 // Load the appropriate template for the given sandbox type.
372 // Returns the template as an NSString or nil on error.
373 NSString* LoadSandboxTemplate(int sandbox_type) {
374 // We use a custom sandbox definition to lock things down as tightly as
376 int sandbox_profile_resource_id = -1;
378 // Find resource id for sandbox profile to use for the specific sandbox type.
380 i < arraysize(kDefaultSandboxTypeToResourceIDMapping);
382 if (kDefaultSandboxTypeToResourceIDMapping[i].sandbox_type ==
384 sandbox_profile_resource_id =
385 kDefaultSandboxTypeToResourceIDMapping[i].sandbox_profile_resource_id;
389 if (sandbox_profile_resource_id == -1) {
390 // Check if the embedder knows about this sandbox process type.
391 bool sandbox_type_found =
392 GetContentClient()->GetSandboxProfileForSandboxType(
393 sandbox_type, &sandbox_profile_resource_id);
394 CHECK(sandbox_type_found) << "Unknown sandbox type " << sandbox_type;
397 base::StringPiece sandbox_definition =
398 GetContentClient()->GetDataResource(
399 sandbox_profile_resource_id, ui::SCALE_FACTOR_NONE);
400 if (sandbox_definition.empty()) {
401 LOG(FATAL) << "Failed to load the sandbox profile (resource id "
402 << sandbox_profile_resource_id << ")";
406 base::StringPiece common_sandbox_definition =
407 GetContentClient()->GetDataResource(
408 IDR_COMMON_SANDBOX_PROFILE, ui::SCALE_FACTOR_NONE);
409 if (common_sandbox_definition.empty()) {
410 LOG(FATAL) << "Failed to load the common sandbox profile";
414 base::scoped_nsobject<NSString> common_sandbox_prefix_data(
415 [[NSString alloc] initWithBytes:common_sandbox_definition.data()
416 length:common_sandbox_definition.length()
417 encoding:NSUTF8StringEncoding]);
419 base::scoped_nsobject<NSString> sandbox_data(
420 [[NSString alloc] initWithBytes:sandbox_definition.data()
421 length:sandbox_definition.length()
422 encoding:NSUTF8StringEncoding]);
424 // Prefix sandbox_data with common_sandbox_prefix_data.
425 return [common_sandbox_prefix_data stringByAppendingString:sandbox_data];
429 bool Sandbox::PostProcessSandboxProfile(
430 NSString* sandbox_template,
431 NSArray* comments_to_remove,
432 SandboxVariableSubstitions& substitutions,
433 std::string *final_sandbox_profile_str) {
434 NSString* sandbox_data = [[sandbox_template copy] autorelease];
436 // Remove comments, e.g. ;10.7_OR_ABOVE .
437 for (NSString* to_remove in comments_to_remove) {
438 sandbox_data = [sandbox_data stringByReplacingOccurrencesOfString:to_remove
442 // Split string on "@" characters.
443 std::vector<std::string> raw_sandbox_pieces;
444 if (Tokenize([sandbox_data UTF8String], "@", &raw_sandbox_pieces) == 0) {
445 DLOG(FATAL) << "Bad Sandbox profile, should contain at least one token ("
446 << [sandbox_data UTF8String]
451 // Iterate over string pieces and substitute variables, escaping as necessary.
452 size_t output_string_length = 0;
453 std::vector<std::string> processed_sandbox_pieces(raw_sandbox_pieces.size());
454 for (std::vector<std::string>::iterator it = raw_sandbox_pieces.begin();
455 it != raw_sandbox_pieces.end();
457 std::string new_piece;
458 SandboxVariableSubstitions::iterator replacement_it =
459 substitutions.find(*it);
460 if (replacement_it == substitutions.end()) {
463 // Found something to substitute.
464 SandboxSubstring& replacement = replacement_it->second;
465 switch (replacement.type()) {
466 case SandboxSubstring::PLAIN:
467 new_piece = replacement.value();
470 case SandboxSubstring::LITERAL:
471 if (!QuotePlainString(replacement.value(), &new_piece))
472 FatalStringQuoteException(replacement.value());
475 case SandboxSubstring::REGEX:
476 if (!QuoteStringForRegex(replacement.value(), &new_piece))
477 FatalStringQuoteException(replacement.value());
481 output_string_length += new_piece.size();
482 processed_sandbox_pieces.push_back(new_piece);
485 // Build final output string.
486 final_sandbox_profile_str->reserve(output_string_length);
488 for (std::vector<std::string>::iterator it = processed_sandbox_pieces.begin();
489 it != processed_sandbox_pieces.end();
491 final_sandbox_profile_str->append(*it);
497 // Turns on the OS X sandbox for this process.
500 bool Sandbox::EnableSandbox(int sandbox_type,
501 const base::FilePath& allowed_dir) {
502 // Sanity - currently only SANDBOX_TYPE_UTILITY supports a directory being
504 if (sandbox_type < SANDBOX_TYPE_AFTER_LAST_TYPE &&
505 sandbox_type != SANDBOX_TYPE_UTILITY) {
506 DCHECK(allowed_dir.empty())
507 << "Only SANDBOX_TYPE_UTILITY allows a custom directory parameter.";
510 NSString* sandbox_data = LoadSandboxTemplate(sandbox_type);
515 SandboxVariableSubstitions substitutions;
516 if (!allowed_dir.empty()) {
517 // Add the sandbox commands necessary to access the given directory.
518 // Note: this function must be called before PostProcessSandboxProfile()
519 // since the string it inserts contains variables that need substitution.
520 NSString* allowed_dir_sandbox_command =
521 BuildAllowDirectoryAccessSandboxString(allowed_dir, &substitutions);
523 if (allowed_dir_sandbox_command) { // May be nil if function fails.
524 sandbox_data = [sandbox_data
525 stringByReplacingOccurrencesOfString:@";ENABLE_DIRECTORY_ACCESS"
526 withString:allowed_dir_sandbox_command];
530 NSMutableArray* tokens_to_remove = [NSMutableArray array];
532 // Enable verbose logging if enabled on the command line. (See common.sb
534 const CommandLine* command_line = CommandLine::ForCurrentProcess();
535 bool enable_logging =
536 command_line->HasSwitch(switches::kEnableSandboxLogging);;
537 if (enable_logging) {
538 [tokens_to_remove addObject:@";ENABLE_LOGGING"];
541 bool lion_or_later = base::mac::IsOSLionOrLater();
543 // Without this, the sandbox will print a message to the system log every
544 // time it denies a request. This floods the console with useless spew.
545 if (!enable_logging) {
546 substitutions["DISABLE_SANDBOX_DENIAL_LOGGING"] =
547 SandboxSubstring("(with no-log)");
549 substitutions["DISABLE_SANDBOX_DENIAL_LOGGING"] = SandboxSubstring("");
552 // Splice the path of the user's home directory into the sandbox profile
553 // (see renderer.sb for details).
554 std::string home_dir = [NSHomeDirectory() fileSystemRepresentation];
556 base::FilePath home_dir_canonical =
557 GetCanonicalSandboxPath(base::FilePath(home_dir));
559 substitutions["USER_HOMEDIR_AS_LITERAL"] =
560 SandboxSubstring(home_dir_canonical.value(),
561 SandboxSubstring::LITERAL);
564 // >=10.7 Sandbox rules.
565 [tokens_to_remove addObject:@";10.7_OR_ABOVE"];
568 substitutions["COMPONENT_BUILD_WORKAROUND"] = SandboxSubstring("");
569 #if defined(COMPONENT_BUILD)
570 // dlopen() fails without file-read-metadata access if the executable image
571 // contains LC_RPATH load commands. The components build uses those.
572 // See http://crbug.com/127465
573 if (base::mac::IsOSSnowLeopard()) {
574 base::FilePath bundle_executable = base::mac::NSStringToFilePath(
575 [base::mac::MainBundle() executablePath]);
576 NSString* sandbox_command = AllowMetadataForPath(
577 GetCanonicalSandboxPath(bundle_executable));
578 substitutions["COMPONENT_BUILD_WORKAROUND"] =
579 SandboxSubstring(base::SysNSStringToUTF8(sandbox_command));
583 // All information needed to assemble the final profile has been collected.
584 // Merge it all together.
585 std::string final_sandbox_profile_str;
586 if (!PostProcessSandboxProfile(sandbox_data, tokens_to_remove, substitutions,
587 &final_sandbox_profile_str)) {
591 // Initialize sandbox.
592 char* error_buff = NULL;
593 int error = sandbox_init(final_sandbox_profile_str.c_str(), 0, &error_buff);
594 bool success = (error == 0 && error_buff == NULL);
595 DLOG_IF(FATAL, !success) << "Failed to initialize sandbox: "
599 sandbox_free_error(error_buff);
600 gSandboxIsActive = success;
605 bool Sandbox::SandboxIsCurrentlyActive() {
606 return gSandboxIsActive;
610 base::FilePath Sandbox::GetCanonicalSandboxPath(const base::FilePath& path) {
611 base::ScopedFD fd(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)));
612 if (!fd.is_valid()) {
613 DPLOG(FATAL) << "GetCanonicalSandboxPath() failed for: "
618 base::FilePath::CharType canonical_path[MAXPATHLEN];
619 if (HANDLE_EINTR(fcntl(fd.get(), F_GETPATH, canonical_path)) != 0) {
620 DPLOG(FATAL) << "GetCanonicalSandboxPath() failed for: "
625 return base::FilePath(canonical_path);
628 } // namespace content