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. 4 5#include "content/common/sandbox_mac.h" 6 7#import <Cocoa/Cocoa.h> 8 9#include <CoreFoundation/CFTimeZone.h> 10extern "C" { 11#include <sandbox.h> 12} 13#include <signal.h> 14#include <sys/param.h> 15 16#include "base/basictypes.h" 17#include "base/command_line.h" 18#include "base/compiler_specific.h" 19#include "base/files/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/grit/content_resources.h" 35#include "content/public/common/content_client.h" 36#include "content/public/common/content_switches.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 41extern "C" { 42void CGSSetDenyWindowServerConnections(bool); 43void CGSShutdownServerConnections(); 44}; 45 46namespace content { 47namespace { 48 49// Is the sandbox currently active. 50bool gSandboxIsActive = false; 51 52struct SandboxTypeToResourceIDMapping { 53 SandboxType sandbox_type; 54 int sandbox_profile_resource_id; 55}; 56 57// Mapping from sandbox process types to resource IDs containing the sandbox 58// profile for all process types known to content. 59SandboxTypeToResourceIDMapping kDefaultSandboxTypeToResourceIDMapping[] = { 60 { SANDBOX_TYPE_RENDERER, IDR_RENDERER_SANDBOX_PROFILE }, 61 { SANDBOX_TYPE_UTILITY, IDR_UTILITY_SANDBOX_PROFILE }, 62 { SANDBOX_TYPE_GPU, IDR_GPU_SANDBOX_PROFILE }, 63 { SANDBOX_TYPE_PPAPI, IDR_PPAPI_SANDBOX_PROFILE }, 64}; 65 66COMPILE_ASSERT(arraysize(kDefaultSandboxTypeToResourceIDMapping) == \ 67 size_t(SANDBOX_TYPE_AFTER_LAST_TYPE), \ 68 sandbox_type_to_resource_id_mapping_incorrect); 69 70// Try to escape |c| as a "SingleEscapeCharacter" (\n, etc). If successful, 71// returns true and appends the escape sequence to |dst|. 72bool EscapeSingleChar(char c, std::string* dst) { 73 const char *append = NULL; 74 switch (c) { 75 case '\b': 76 append = "\\b"; 77 break; 78 case '\f': 79 append = "\\f"; 80 break; 81 case '\n': 82 append = "\\n"; 83 break; 84 case '\r': 85 append = "\\r"; 86 break; 87 case '\t': 88 append = "\\t"; 89 break; 90 case '\\': 91 append = "\\\\"; 92 break; 93 case '"': 94 append = "\\\""; 95 break; 96 } 97 98 if (!append) { 99 return false; 100 } 101 102 dst->append(append); 103 return true; 104} 105 106// Errors quoting strings for the Sandbox profile are always fatal, report them 107// in a central place. 108NOINLINE void FatalStringQuoteException(const std::string& str) { 109 // Copy bad string to the stack so it's recorded in the crash dump. 110 char bad_string[256] = {0}; 111 base::strlcpy(bad_string, str.c_str(), arraysize(bad_string)); 112 DLOG(FATAL) << "String quoting failed " << bad_string; 113} 114 115} // namespace 116 117// static 118NSString* Sandbox::AllowMetadataForPath(const base::FilePath& allowed_path) { 119 // Collect a list of all parent directories. 120 base::FilePath last_path = allowed_path; 121 std::vector<base::FilePath> subpaths; 122 for (base::FilePath path = allowed_path; 123 path.value() != last_path.value(); 124 path = path.DirName()) { 125 subpaths.push_back(path); 126 last_path = path; 127 } 128 129 // Iterate through all parents and allow stat() on them explicitly. 130 NSString* sandbox_command = @"(allow file-read-metadata "; 131 for (std::vector<base::FilePath>::reverse_iterator i = subpaths.rbegin(); 132 i != subpaths.rend(); 133 ++i) { 134 std::string subdir_escaped; 135 if (!QuotePlainString(i->value(), &subdir_escaped)) { 136 FatalStringQuoteException(i->value()); 137 return nil; 138 } 139 140 NSString* subdir_escaped_ns = 141 base::SysUTF8ToNSString(subdir_escaped.c_str()); 142 sandbox_command = 143 [sandbox_command stringByAppendingFormat:@"(literal \"%@\")", 144 subdir_escaped_ns]; 145 } 146 147 return [sandbox_command stringByAppendingString:@")"]; 148} 149 150// static 151bool Sandbox::QuotePlainString(const std::string& src_utf8, std::string* dst) { 152 dst->clear(); 153 154 const char* src = src_utf8.c_str(); 155 int32_t length = src_utf8.length(); 156 int32_t position = 0; 157 while (position < length) { 158 UChar32 c; 159 U8_NEXT(src, position, length, c); // Macro increments |position|. 160 DCHECK_GE(c, 0); 161 if (c < 0) 162 return false; 163 164 if (c < 128) { // EscapeSingleChar only handles ASCII. 165 char as_char = static_cast<char>(c); 166 if (EscapeSingleChar(as_char, dst)) { 167 continue; 168 } 169 } 170 171 if (c < 32 || c > 126) { 172 // Any characters that aren't printable ASCII get the \u treatment. 173 unsigned int as_uint = static_cast<unsigned int>(c); 174 base::StringAppendF(dst, "\\u%04X", as_uint); 175 continue; 176 } 177 178 // If we got here we know that the character in question is strictly 179 // in the ASCII range so there's no need to do any kind of encoding 180 // conversion. 181 dst->push_back(static_cast<char>(c)); 182 } 183 return true; 184} 185 186// static 187bool Sandbox::QuoteStringForRegex(const std::string& str_utf8, 188 std::string* dst) { 189 // Characters with special meanings in sandbox profile syntax. 190 const char regex_special_chars[] = { 191 '\\', 192 193 // Metacharacters 194 '^', 195 '.', 196 '[', 197 ']', 198 '$', 199 '(', 200 ')', 201 '|', 202 203 // Quantifiers 204 '*', 205 '+', 206 '?', 207 '{', 208 '}', 209 }; 210 211 // Anchor regex at start of path. 212 dst->assign("^"); 213 214 const char* src = str_utf8.c_str(); 215 int32_t length = str_utf8.length(); 216 int32_t position = 0; 217 while (position < length) { 218 UChar32 c; 219 U8_NEXT(src, position, length, c); // Macro increments |position|. 220 DCHECK_GE(c, 0); 221 if (c < 0) 222 return false; 223 224 // The Mac sandbox regex parser only handles printable ASCII characters. 225 // 33 >= c <= 126 226 if (c < 32 || c > 125) { 227 return false; 228 } 229 230 for (size_t i = 0; i < arraysize(regex_special_chars); ++i) { 231 if (c == regex_special_chars[i]) { 232 dst->push_back('\\'); 233 break; 234 } 235 } 236 237 dst->push_back(static_cast<char>(c)); 238 } 239 240 // Make sure last element of path is interpreted as a directory. Leaving this 241 // off would allow access to files if they start with the same name as the 242 // directory. 243 dst->append("(/|$)"); 244 245 return true; 246} 247 248// Warm up System APIs that empirically need to be accessed before the Sandbox 249// is turned on. 250// This method is layed out in blocks, each one containing a separate function 251// that needs to be warmed up. The OS version on which we found the need to 252// enable the function is also noted. 253// This function is tested on the following OS versions: 254// 10.5.6, 10.6.0 255 256// static 257void Sandbox::SandboxWarmup(int sandbox_type) { 258 base::mac::ScopedNSAutoreleasePool scoped_pool; 259 260 { // CGColorSpaceCreateWithName(), CGBitmapContextCreate() - 10.5.6 261 base::ScopedCFTypeRef<CGColorSpaceRef> rgb_colorspace( 262 CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)); 263 264 // Allocate a 1x1 image. 265 char data[4]; 266 base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate( 267 data, 268 1, 269 1, 270 8, 271 1 * 4, 272 rgb_colorspace, 273 kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host)); 274 275 // Load in the color profiles we'll need (as a side effect). 276 ignore_result(base::mac::GetSRGBColorSpace()); 277 ignore_result(base::mac::GetSystemColorSpace()); 278 279 // CGColorSpaceCreateSystemDefaultCMYK - 10.6 280 base::ScopedCFTypeRef<CGColorSpaceRef> cmyk_colorspace( 281 CGColorSpaceCreateWithName(kCGColorSpaceGenericCMYK)); 282 } 283 284 { // localtime() - 10.5.6 285 time_t tv = {0}; 286 localtime(&tv); 287 } 288 289 { // Gestalt() tries to read /System/Library/CoreServices/SystemVersion.plist 290 // on 10.5.6 291 int32 tmp; 292 base::SysInfo::OperatingSystemVersionNumbers(&tmp, &tmp, &tmp); 293 } 294 295 { // CGImageSourceGetStatus() - 10.6 296 // Create a png with just enough data to get everything warmed up... 297 char png_header[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; 298 NSData* data = [NSData dataWithBytes:png_header 299 length:arraysize(png_header)]; 300 base::ScopedCFTypeRef<CGImageSourceRef> img( 301 CGImageSourceCreateWithData((CFDataRef)data, NULL)); 302 CGImageSourceGetStatus(img); 303 } 304 305 { 306 // Allow access to /dev/urandom. 307 base::GetUrandomFD(); 308 } 309 310 { // IOSurfaceLookup() - 10.7 311 // Needed by zero-copy texture update framework - crbug.com/323338 312 base::ScopedCFTypeRef<IOSurfaceRef> io_surface(IOSurfaceLookup(0)); 313 } 314 315 // Process-type dependent warm-up. 316 if (sandbox_type == SANDBOX_TYPE_UTILITY) { 317 // CFTimeZoneCopyZone() tries to read /etc and /private/etc/localtime - 10.8 318 // Needed by Media Galleries API Picasa - crbug.com/151701 319 CFTimeZoneCopySystem(); 320 } 321 322 if (sandbox_type == SANDBOX_TYPE_GPU) { 323 // Preload either the desktop GL or the osmesa so, depending on the 324 // --use-gl flag. 325 gfx::GLSurface::InitializeOneOff(); 326 } 327 328 if (sandbox_type == SANDBOX_TYPE_PPAPI) { 329 // Preload AppKit color spaces used for Flash/ppapi. http://crbug.com/348304 330 NSColor* color = [NSColor controlTextColor]; 331 [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; 332 } 333 334 if (sandbox_type == SANDBOX_TYPE_RENDERER && 335 base::mac::IsOSMountainLionOrLater()) { 336 // Now disconnect from WindowServer, after all objects have been warmed up. 337 // Shutting down the connection requires connecting to WindowServer, 338 // so do this before actually engaging the sandbox. This is only done on 339 // 10.8 and higher because doing it on earlier OSes causes layout tests to 340 // fail <http://crbug.com/397642#c48>. This may cause two log messages to 341 // be printed to the system logger on certain OS versions. 342 CGSSetDenyWindowServerConnections(true); 343 CGSShutdownServerConnections(); 344 } 345} 346 347// static 348NSString* Sandbox::BuildAllowDirectoryAccessSandboxString( 349 const base::FilePath& allowed_dir, 350 SandboxVariableSubstitions* substitutions) { 351 // A whitelist is used to determine which directories can be statted 352 // This means that in the case of an /a/b/c/d/ directory, we may be able to 353 // stat the leaf directory, but not its parent. 354 // The extension code in Chrome calls realpath() which fails if it can't call 355 // stat() on one of the parent directories in the path. 356 // The solution to this is to allow statting the parent directories themselves 357 // but not their contents. We need to add a separate rule for each parent 358 // directory. 359 360 // The sandbox only understands "real" paths. This resolving step is 361 // needed so the caller doesn't need to worry about things like /var 362 // being a link to /private/var (like in the paths CreateNewTempDirectory() 363 // returns). 364 base::FilePath allowed_dir_canonical = GetCanonicalSandboxPath(allowed_dir); 365 366 NSString* sandbox_command = AllowMetadataForPath(allowed_dir_canonical); 367 sandbox_command = [sandbox_command 368 substringToIndex:[sandbox_command length] - 1]; // strip trailing ')' 369 370 // Finally append the leaf directory. Unlike its parents (for which only 371 // stat() should be allowed), the leaf directory needs full access. 372 (*substitutions)["ALLOWED_DIR"] = 373 SandboxSubstring(allowed_dir_canonical.value(), 374 SandboxSubstring::REGEX); 375 sandbox_command = 376 [sandbox_command 377 stringByAppendingString:@") (allow file-read* file-write*" 378 " (regex #\"@ALLOWED_DIR@\") )"]; 379 return sandbox_command; 380} 381 382// Load the appropriate template for the given sandbox type. 383// Returns the template as an NSString or nil on error. 384NSString* LoadSandboxTemplate(int sandbox_type) { 385 // We use a custom sandbox definition to lock things down as tightly as 386 // possible. 387 int sandbox_profile_resource_id = -1; 388 389 // Find resource id for sandbox profile to use for the specific sandbox type. 390 for (size_t i = 0; 391 i < arraysize(kDefaultSandboxTypeToResourceIDMapping); 392 ++i) { 393 if (kDefaultSandboxTypeToResourceIDMapping[i].sandbox_type == 394 sandbox_type) { 395 sandbox_profile_resource_id = 396 kDefaultSandboxTypeToResourceIDMapping[i].sandbox_profile_resource_id; 397 break; 398 } 399 } 400 if (sandbox_profile_resource_id == -1) { 401 // Check if the embedder knows about this sandbox process type. 402 bool sandbox_type_found = 403 GetContentClient()->GetSandboxProfileForSandboxType( 404 sandbox_type, &sandbox_profile_resource_id); 405 CHECK(sandbox_type_found) << "Unknown sandbox type " << sandbox_type; 406 } 407 408 base::StringPiece sandbox_definition = 409 GetContentClient()->GetDataResource( 410 sandbox_profile_resource_id, ui::SCALE_FACTOR_NONE); 411 if (sandbox_definition.empty()) { 412 LOG(FATAL) << "Failed to load the sandbox profile (resource id " 413 << sandbox_profile_resource_id << ")"; 414 return nil; 415 } 416 417 base::StringPiece common_sandbox_definition = 418 GetContentClient()->GetDataResource( 419 IDR_COMMON_SANDBOX_PROFILE, ui::SCALE_FACTOR_NONE); 420 if (common_sandbox_definition.empty()) { 421 LOG(FATAL) << "Failed to load the common sandbox profile"; 422 return nil; 423 } 424 425 base::scoped_nsobject<NSString> common_sandbox_prefix_data( 426 [[NSString alloc] initWithBytes:common_sandbox_definition.data() 427 length:common_sandbox_definition.length() 428 encoding:NSUTF8StringEncoding]); 429 430 base::scoped_nsobject<NSString> sandbox_data( 431 [[NSString alloc] initWithBytes:sandbox_definition.data() 432 length:sandbox_definition.length() 433 encoding:NSUTF8StringEncoding]); 434 435 // Prefix sandbox_data with common_sandbox_prefix_data. 436 return [common_sandbox_prefix_data stringByAppendingString:sandbox_data]; 437} 438 439// static 440bool Sandbox::PostProcessSandboxProfile( 441 NSString* sandbox_template, 442 NSArray* comments_to_remove, 443 SandboxVariableSubstitions& substitutions, 444 std::string *final_sandbox_profile_str) { 445 NSString* sandbox_data = [[sandbox_template copy] autorelease]; 446 447 // Remove comments, e.g. ;10.7_OR_ABOVE . 448 for (NSString* to_remove in comments_to_remove) { 449 sandbox_data = [sandbox_data stringByReplacingOccurrencesOfString:to_remove 450 withString:@""]; 451 } 452 453 // Split string on "@" characters. 454 std::vector<std::string> raw_sandbox_pieces; 455 if (Tokenize([sandbox_data UTF8String], "@", &raw_sandbox_pieces) == 0) { 456 DLOG(FATAL) << "Bad Sandbox profile, should contain at least one token (" 457 << [sandbox_data UTF8String] 458 << ")"; 459 return false; 460 } 461 462 // Iterate over string pieces and substitute variables, escaping as necessary. 463 size_t output_string_length = 0; 464 std::vector<std::string> processed_sandbox_pieces(raw_sandbox_pieces.size()); 465 for (std::vector<std::string>::iterator it = raw_sandbox_pieces.begin(); 466 it != raw_sandbox_pieces.end(); 467 ++it) { 468 std::string new_piece; 469 SandboxVariableSubstitions::iterator replacement_it = 470 substitutions.find(*it); 471 if (replacement_it == substitutions.end()) { 472 new_piece = *it; 473 } else { 474 // Found something to substitute. 475 SandboxSubstring& replacement = replacement_it->second; 476 switch (replacement.type()) { 477 case SandboxSubstring::PLAIN: 478 new_piece = replacement.value(); 479 break; 480 481 case SandboxSubstring::LITERAL: 482 if (!QuotePlainString(replacement.value(), &new_piece)) 483 FatalStringQuoteException(replacement.value()); 484 break; 485 486 case SandboxSubstring::REGEX: 487 if (!QuoteStringForRegex(replacement.value(), &new_piece)) 488 FatalStringQuoteException(replacement.value()); 489 break; 490 } 491 } 492 output_string_length += new_piece.size(); 493 processed_sandbox_pieces.push_back(new_piece); 494 } 495 496 // Build final output string. 497 final_sandbox_profile_str->reserve(output_string_length); 498 499 for (std::vector<std::string>::iterator it = processed_sandbox_pieces.begin(); 500 it != processed_sandbox_pieces.end(); 501 ++it) { 502 final_sandbox_profile_str->append(*it); 503 } 504 return true; 505} 506 507 508// Turns on the OS X sandbox for this process. 509 510// static 511bool Sandbox::EnableSandbox(int sandbox_type, 512 const base::FilePath& allowed_dir) { 513 // Sanity - currently only SANDBOX_TYPE_UTILITY supports a directory being 514 // passed in. 515 if (sandbox_type < SANDBOX_TYPE_AFTER_LAST_TYPE && 516 sandbox_type != SANDBOX_TYPE_UTILITY) { 517 DCHECK(allowed_dir.empty()) 518 << "Only SANDBOX_TYPE_UTILITY allows a custom directory parameter."; 519 } 520 521 NSString* sandbox_data = LoadSandboxTemplate(sandbox_type); 522 if (!sandbox_data) { 523 return false; 524 } 525 526 SandboxVariableSubstitions substitutions; 527 if (!allowed_dir.empty()) { 528 // Add the sandbox commands necessary to access the given directory. 529 // Note: this function must be called before PostProcessSandboxProfile() 530 // since the string it inserts contains variables that need substitution. 531 NSString* allowed_dir_sandbox_command = 532 BuildAllowDirectoryAccessSandboxString(allowed_dir, &substitutions); 533 534 if (allowed_dir_sandbox_command) { // May be nil if function fails. 535 sandbox_data = [sandbox_data 536 stringByReplacingOccurrencesOfString:@";ENABLE_DIRECTORY_ACCESS" 537 withString:allowed_dir_sandbox_command]; 538 } 539 } 540 541 NSMutableArray* tokens_to_remove = [NSMutableArray array]; 542 543 // Enable verbose logging if enabled on the command line. (See common.sb 544 // for details). 545 const base::CommandLine* command_line = 546 base::CommandLine::ForCurrentProcess(); 547 bool enable_logging = 548 command_line->HasSwitch(switches::kEnableSandboxLogging);; 549 if (enable_logging) { 550 [tokens_to_remove addObject:@";ENABLE_LOGGING"]; 551 } 552 553 bool lion_or_later = base::mac::IsOSLionOrLater(); 554 555 // Without this, the sandbox will print a message to the system log every 556 // time it denies a request. This floods the console with useless spew. 557 if (!enable_logging) { 558 substitutions["DISABLE_SANDBOX_DENIAL_LOGGING"] = 559 SandboxSubstring("(with no-log)"); 560 } else { 561 substitutions["DISABLE_SANDBOX_DENIAL_LOGGING"] = SandboxSubstring(""); 562 } 563 564 // Splice the path of the user's home directory into the sandbox profile 565 // (see renderer.sb for details). 566 std::string home_dir = [NSHomeDirectory() fileSystemRepresentation]; 567 568 base::FilePath home_dir_canonical = 569 GetCanonicalSandboxPath(base::FilePath(home_dir)); 570 571 substitutions["USER_HOMEDIR_AS_LITERAL"] = 572 SandboxSubstring(home_dir_canonical.value(), 573 SandboxSubstring::LITERAL); 574 575 if (lion_or_later) { 576 // >=10.7 Sandbox rules. 577 [tokens_to_remove addObject:@";10.7_OR_ABOVE"]; 578 } 579 580 substitutions["COMPONENT_BUILD_WORKAROUND"] = SandboxSubstring(""); 581#if defined(COMPONENT_BUILD) 582 // dlopen() fails without file-read-metadata access if the executable image 583 // contains LC_RPATH load commands. The components build uses those. 584 // See http://crbug.com/127465 585 if (base::mac::IsOSSnowLeopard()) { 586 base::FilePath bundle_executable = base::mac::NSStringToFilePath( 587 [base::mac::MainBundle() executablePath]); 588 NSString* sandbox_command = AllowMetadataForPath( 589 GetCanonicalSandboxPath(bundle_executable)); 590 substitutions["COMPONENT_BUILD_WORKAROUND"] = 591 SandboxSubstring(base::SysNSStringToUTF8(sandbox_command)); 592 } 593#endif 594 595 // All information needed to assemble the final profile has been collected. 596 // Merge it all together. 597 std::string final_sandbox_profile_str; 598 if (!PostProcessSandboxProfile(sandbox_data, tokens_to_remove, substitutions, 599 &final_sandbox_profile_str)) { 600 return false; 601 } 602 603 // Initialize sandbox. 604 char* error_buff = NULL; 605 int error = sandbox_init(final_sandbox_profile_str.c_str(), 0, &error_buff); 606 bool success = (error == 0 && error_buff == NULL); 607 DLOG_IF(FATAL, !success) << "Failed to initialize sandbox: " 608 << error 609 << " " 610 << error_buff; 611 sandbox_free_error(error_buff); 612 gSandboxIsActive = success; 613 return success; 614} 615 616// static 617bool Sandbox::SandboxIsCurrentlyActive() { 618 return gSandboxIsActive; 619} 620 621// static 622base::FilePath Sandbox::GetCanonicalSandboxPath(const base::FilePath& path) { 623 base::ScopedFD fd(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY))); 624 if (!fd.is_valid()) { 625 DPLOG(FATAL) << "GetCanonicalSandboxPath() failed for: " 626 << path.value(); 627 return path; 628 } 629 630 base::FilePath::CharType canonical_path[MAXPATHLEN]; 631 if (HANDLE_EINTR(fcntl(fd.get(), F_GETPATH, canonical_path)) != 0) { 632 DPLOG(FATAL) << "GetCanonicalSandboxPath() failed for: " 633 << path.value(); 634 return path; 635 } 636 637 return base::FilePath(canonical_path); 638} 639 640} // namespace content 641