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