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