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