Blink roll 25b6bd3a7a131ffe68d809546ad1a20707915cdc:3a503f41ae42e5b79cfcd2ff10e65afde...
[chromium-blink-merge.git] / content / common / sandbox_mac.mm
blob118919f20a3491f284216ff8d945b7889bf4b665
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/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"
41 extern "C" {
42 void CGSSetDenyWindowServerConnections(bool);
43 void CGSShutdownServerConnections();
46 namespace content {
47 namespace {
49 // Is the sandbox currently active.
50 bool gSandboxIsActive = false;
52 struct SandboxTypeToResourceIDMapping {
53   SandboxType sandbox_type;
54   int sandbox_profile_resource_id;
57 // Mapping from sandbox process types to resource IDs containing the sandbox
58 // profile for all process types known to content.
59 SandboxTypeToResourceIDMapping 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 },
66 COMPILE_ASSERT(arraysize(kDefaultSandboxTypeToResourceIDMapping) == \
67                size_t(SANDBOX_TYPE_AFTER_LAST_TYPE), \
68                sandbox_type_to_resource_id_mapping_incorrect);
70 // Try to escape |c| as a "SingleEscapeCharacter" (\n, etc).  If successful,
71 // returns true and appends the escape sequence to |dst|.
72 bool 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   }
98   if (!append) {
99     return false;
100   }
102   dst->append(append);
103   return true;
106 // Errors quoting strings for the Sandbox profile are always fatal, report them
107 // in a central place.
108 NOINLINE 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;
115 }  // namespace
117 // static
118 NSString* 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   }
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     }
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   }
147   return [sandbox_command stringByAppendingString:@")"];
150 // static
151 bool Sandbox::QuotePlainString(const std::string& src_utf8, std::string* dst) {
152   dst->clear();
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;
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     }
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     }
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;
186 // static
187 bool 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     '\\',
193     // Metacharacters
194     '^',
195     '.',
196     '[',
197     ']',
198     '$',
199     '(',
200     ')',
201     '|',
203     // Quantifiers
204     '*',
205     '+',
206     '?',
207     '{',
208     '}',
209   };
211   // Anchor regex at start of path.
212   dst->assign("^");
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;
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     }
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     }
237     dst->push_back(static_cast<char>(c));
238   }
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("(/|$)");
245   return true;
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
256 // static
257 void Sandbox::SandboxWarmup(int sandbox_type) {
258   base::mac::ScopedNSAutoreleasePool scoped_pool;
260   { // CGColorSpaceCreateWithName(), CGBitmapContextCreate() - 10.5.6
261     base::ScopedCFTypeRef<CGColorSpaceRef> rgb_colorspace(
262         CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB));
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));
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());
279     // CGColorSpaceCreateSystemDefaultCMYK - 10.6
280     base::ScopedCFTypeRef<CGColorSpaceRef> cmyk_colorspace(
281         CGColorSpaceCreateWithName(kCGColorSpaceGenericCMYK));
282   }
284   { // localtime() - 10.5.6
285     time_t tv = {0};
286     localtime(&tv);
287   }
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   }
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   }
305   {
306     // Allow access to /dev/urandom.
307     base::GetUrandomFD();
308   }
310   { // IOSurfaceLookup() - 10.7
311     // Needed by zero-copy texture update framework - crbug.com/323338
312     base::ScopedCFTypeRef<IOSurfaceRef> io_surface(IOSurfaceLookup(0));
313   }
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   }
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   }
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   }
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   }
347 // static
348 NSString* 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.
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);
366   NSString* sandbox_command = AllowMetadataForPath(allowed_dir_canonical);
367   sandbox_command = [sandbox_command
368       substringToIndex:[sandbox_command length] - 1];  // strip trailing ')'
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;
382 // Load the appropriate template for the given sandbox type.
383 // Returns the template as an NSString or nil on error.
384 NSString* 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;
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   }
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   }
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   }
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]);
430   base::scoped_nsobject<NSString> sandbox_data(
431       [[NSString alloc] initWithBytes:sandbox_definition.data()
432                                length:sandbox_definition.length()
433                              encoding:NSUTF8StringEncoding]);
435   // Prefix sandbox_data with common_sandbox_prefix_data.
436   return [common_sandbox_prefix_data stringByAppendingString:sandbox_data];
439 // static
440 bool 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];
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   }
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   }
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;
481         case SandboxSubstring::LITERAL:
482           if (!QuotePlainString(replacement.value(), &new_piece))
483             FatalStringQuoteException(replacement.value());
484           break;
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   }
496   // Build final output string.
497   final_sandbox_profile_str->reserve(output_string_length);
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;
508 // Turns on the OS X sandbox for this process.
510 // static
511 bool 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   }
521   NSString* sandbox_data = LoadSandboxTemplate(sandbox_type);
522   if (!sandbox_data) {
523     return false;
524   }
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);
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   }
541   NSMutableArray* tokens_to_remove = [NSMutableArray array];
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   }
553   bool lion_or_later = base::mac::IsOSLionOrLater();
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   }
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];
568   base::FilePath home_dir_canonical =
569       GetCanonicalSandboxPath(base::FilePath(home_dir));
571   substitutions["USER_HOMEDIR_AS_LITERAL"] =
572       SandboxSubstring(home_dir_canonical.value(),
573           SandboxSubstring::LITERAL);
575   if (lion_or_later) {
576     // >=10.7 Sandbox rules.
577     [tokens_to_remove addObject:@";10.7_OR_ABOVE"];
578   }
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
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   }
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;
616 // static
617 bool Sandbox::SandboxIsCurrentlyActive() {
618   return gSandboxIsActive;
621 // static
622 base::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   }
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   }
637   return base::FilePath(canonical_path);
640 }  // namespace content