Home | History | Annotate | Download | only in common
      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