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