Enable Resize_desktop_to_fit by default for Me2Me connections.
[chromium-blink-merge.git] / content / common / sandbox_mac.mm
blob8655252811ae8b803fdc27bee0631d8d2355b64a
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_util.h"
27 #include "base/stringprintf.h"
28 #include "base/strings/string_piece.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/public/common/unicode/uchar.h"
36 #include "ui/base/layout.h"
37 #include "ui/gl/gl_surface.h"
39 namespace content {
40 namespace {
42 // Is the sandbox currently active.
43 bool gSandboxIsActive = false;
45 struct SandboxTypeToResourceIDMapping {
46   SandboxType sandbox_type;
47   int sandbox_profile_resource_id;
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 },
60 COMPILE_ASSERT(arraysize(kDefaultSandboxTypeToResourceIDMapping) == \
61                size_t(SANDBOX_TYPE_AFTER_LAST_TYPE), \
62                sandbox_type_to_resource_id_mapping_incorrect);
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   }
92   if (!append) {
93     return false;
94   }
96   dst->append(append);
97   return true;
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;
109 }  // namespace
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   }
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     }
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   }
141   return [sandbox_command stringByAppendingString:@")"];
144 // static
145 bool Sandbox::QuotePlainString(const std::string& src_utf8, std::string* dst) {
146   dst->clear();
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;
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     }
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     }
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;
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     '\\',
187     // Metacharacters
188     '^',
189     '.',
190     '[',
191     ']',
192     '$',
193     '(',
194     ')',
195     '|',
197     // Quantifiers
198     '*',
199     '+',
200     '?',
201     '{',
202     '}',
203   };
205   // Anchor regex at start of path.
206   dst->assign("^");
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;
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     }
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     }
231     dst->push_back(static_cast<char>(c));
232   }
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("(/|$)");
239   return true;
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
250 // static
251 void Sandbox::SandboxWarmup(int sandbox_type) {
252   base::mac::ScopedNSAutoreleasePool scoped_pool;
254   { // CGColorSpaceCreateWithName(), CGBitmapContextCreate() - 10.5.6
255     base::mac::ScopedCFTypeRef<CGColorSpaceRef> rgb_colorspace(
256         CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB));
258     // Allocate a 1x1 image.
259     char data[4];
260     base::mac::ScopedCFTypeRef<CGContextRef> context(
261         CGBitmapContextCreate(data, 1, 1, 8, 1 * 4,
262                               rgb_colorspace,
263                               kCGImageAlphaPremultipliedFirst |
264                                   kCGBitmapByteOrder32Host));
266     // Load in the color profiles we'll need (as a side effect).
267     (void) base::mac::GetSRGBColorSpace();
268     (void) base::mac::GetSystemColorSpace();
270     // CGColorSpaceCreateSystemDefaultCMYK - 10.6
271     base::mac::ScopedCFTypeRef<CGColorSpaceRef> cmyk_colorspace(
272         CGColorSpaceCreateWithName(kCGColorSpaceGenericCMYK));
273   }
275   { // [-NSColor colorUsingColorSpaceName] - 10.5.6
276     NSColor* color = [NSColor controlTextColor];
277     [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
278   }
280   { // localtime() - 10.5.6
281     time_t tv = {0};
282     localtime(&tv);
283   }
285   { // Gestalt() tries to read /System/Library/CoreServices/SystemVersion.plist
286     // on 10.5.6
287     int32 tmp;
288     base::SysInfo::OperatingSystemVersionNumbers(&tmp, &tmp, &tmp);
289   }
291   {  // CGImageSourceGetStatus() - 10.6
292      // Create a png with just enough data to get everything warmed up...
293     char png_header[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
294     NSData* data = [NSData dataWithBytes:png_header
295                                   length:arraysize(png_header)];
296     base::mac::ScopedCFTypeRef<CGImageSourceRef> img(
297         CGImageSourceCreateWithData((CFDataRef)data,
298         NULL));
299     CGImageSourceGetStatus(img);
300   }
302   {
303     // Allow access to /dev/urandom.
304     base::GetUrandomFD();
305   }
307   // Process-type dependent warm-up.
308   if (sandbox_type == SANDBOX_TYPE_GPU) {
309     // Preload either the desktop GL or the osmesa so, depending on the
310     // --use-gl flag.
311     gfx::GLSurface::InitializeOneOff();
312   }
315 // static
316 NSString* Sandbox::BuildAllowDirectoryAccessSandboxString(
317     const base::FilePath& allowed_dir,
318     SandboxVariableSubstitions* substitutions) {
319   // A whitelist is used to determine which directories can be statted
320   // This means that in the case of an /a/b/c/d/ directory, we may be able to
321   // stat the leaf directory, but not its parent.
322   // The extension code in Chrome calls realpath() which fails if it can't call
323   // stat() on one of the parent directories in the path.
324   // The solution to this is to allow statting the parent directories themselves
325   // but not their contents.  We need to add a separate rule for each parent
326   // directory.
328   // The sandbox only understands "real" paths.  This resolving step is
329   // needed so the caller doesn't need to worry about things like /var
330   // being a link to /private/var (like in the paths CreateNewTempDirectory()
331   // returns).
332   base::FilePath allowed_dir_canonical = GetCanonicalSandboxPath(allowed_dir);
334   NSString* sandbox_command = AllowMetadataForPath(allowed_dir_canonical);
335   sandbox_command = [sandbox_command
336       substringToIndex:[sandbox_command length] - 1];  // strip trailing ')'
338   // Finally append the leaf directory.  Unlike its parents (for which only
339   // stat() should be allowed), the leaf directory needs full access.
340   (*substitutions)["ALLOWED_DIR"] =
341       SandboxSubstring(allowed_dir_canonical.value(),
342                        SandboxSubstring::REGEX);
343   sandbox_command =
344       [sandbox_command
345           stringByAppendingString:@") (allow file-read* file-write*"
346                                    " (regex #\"@ALLOWED_DIR@\") )"];
347   return sandbox_command;
350 // Load the appropriate template for the given sandbox type.
351 // Returns the template as an NSString or nil on error.
352 NSString* LoadSandboxTemplate(int sandbox_type) {
353   // We use a custom sandbox definition to lock things down as tightly as
354   // possible.
355   int sandbox_profile_resource_id = -1;
357   // Find resource id for sandbox profile to use for the specific sandbox type.
358   for (size_t i = 0;
359        i < arraysize(kDefaultSandboxTypeToResourceIDMapping);
360        ++i) {
361     if (kDefaultSandboxTypeToResourceIDMapping[i].sandbox_type ==
362         sandbox_type) {
363       sandbox_profile_resource_id =
364           kDefaultSandboxTypeToResourceIDMapping[i].sandbox_profile_resource_id;
365       break;
366     }
367   }
368   if (sandbox_profile_resource_id == -1) {
369     // Check if the embedder knows about this sandbox process type.
370     bool sandbox_type_found =
371         GetContentClient()->GetSandboxProfileForSandboxType(
372             sandbox_type, &sandbox_profile_resource_id);
373     CHECK(sandbox_type_found) << "Unknown sandbox type " << sandbox_type;
374   }
376   base::StringPiece sandbox_definition =
377       GetContentClient()->GetDataResource(
378           sandbox_profile_resource_id, ui::SCALE_FACTOR_NONE);
379   if (sandbox_definition.empty()) {
380     LOG(FATAL) << "Failed to load the sandbox profile (resource id "
381                << sandbox_profile_resource_id << ")";
382     return nil;
383   }
385   base::StringPiece common_sandbox_definition =
386       GetContentClient()->GetDataResource(
387           IDR_COMMON_SANDBOX_PROFILE, ui::SCALE_FACTOR_NONE);
388   if (common_sandbox_definition.empty()) {
389     LOG(FATAL) << "Failed to load the common sandbox profile";
390     return nil;
391   }
393   scoped_nsobject<NSString> common_sandbox_prefix_data(
394       [[NSString alloc] initWithBytes:common_sandbox_definition.data()
395                                length:common_sandbox_definition.length()
396                              encoding:NSUTF8StringEncoding]);
398   scoped_nsobject<NSString> sandbox_data(
399       [[NSString alloc] initWithBytes:sandbox_definition.data()
400                                length:sandbox_definition.length()
401                              encoding:NSUTF8StringEncoding]);
403   // Prefix sandbox_data with common_sandbox_prefix_data.
404   return [common_sandbox_prefix_data stringByAppendingString:sandbox_data];
407 // static
408 bool Sandbox::PostProcessSandboxProfile(
409         NSString* sandbox_template,
410         NSArray* comments_to_remove,
411         SandboxVariableSubstitions& substitutions,
412         std::string *final_sandbox_profile_str) {
413   NSString* sandbox_data = [[sandbox_template copy] autorelease];
415   // Remove comments, e.g. ;10.7_OR_ABOVE .
416   for (NSString* to_remove in comments_to_remove) {
417     sandbox_data = [sandbox_data stringByReplacingOccurrencesOfString:to_remove
418                                                            withString:@""];
419   }
421   // Split string on "@" characters.
422   std::vector<std::string> raw_sandbox_pieces;
423   if (Tokenize([sandbox_data UTF8String], "@", &raw_sandbox_pieces) == 0) {
424     DLOG(FATAL) << "Bad Sandbox profile, should contain at least one token ("
425                 << [sandbox_data UTF8String]
426                 << ")";
427     return false;
428   }
430   // Iterate over string pieces and substitute variables, escaping as necessary.
431   size_t output_string_length = 0;
432   std::vector<std::string> processed_sandbox_pieces(raw_sandbox_pieces.size());
433   for (std::vector<std::string>::iterator it = raw_sandbox_pieces.begin();
434        it != raw_sandbox_pieces.end();
435        ++it) {
436     std::string new_piece;
437     SandboxVariableSubstitions::iterator replacement_it =
438         substitutions.find(*it);
439     if (replacement_it == substitutions.end()) {
440       new_piece = *it;
441     } else {
442       // Found something to substitute.
443       SandboxSubstring& replacement = replacement_it->second;
444       switch (replacement.type()) {
445         case SandboxSubstring::PLAIN:
446           new_piece = replacement.value();
447           break;
449         case SandboxSubstring::LITERAL:
450           if (!QuotePlainString(replacement.value(), &new_piece))
451             FatalStringQuoteException(replacement.value());
452           break;
454         case SandboxSubstring::REGEX:
455           if (!QuoteStringForRegex(replacement.value(), &new_piece))
456             FatalStringQuoteException(replacement.value());
457           break;
458       }
459     }
460     output_string_length += new_piece.size();
461     processed_sandbox_pieces.push_back(new_piece);
462   }
464   // Build final output string.
465   final_sandbox_profile_str->reserve(output_string_length);
467   for (std::vector<std::string>::iterator it = processed_sandbox_pieces.begin();
468        it != processed_sandbox_pieces.end();
469        ++it) {
470     final_sandbox_profile_str->append(*it);
471   }
472   return true;
476 // Turns on the OS X sandbox for this process.
478 // static
479 bool Sandbox::EnableSandbox(int sandbox_type,
480                             const base::FilePath& allowed_dir) {
481   // Sanity - currently only SANDBOX_TYPE_UTILITY supports a directory being
482   // passed in.
483   if (sandbox_type < SANDBOX_TYPE_AFTER_LAST_TYPE &&
484       sandbox_type != SANDBOX_TYPE_UTILITY) {
485     DCHECK(allowed_dir.empty())
486         << "Only SANDBOX_TYPE_UTILITY allows a custom directory parameter.";
487   }
489   NSString* sandbox_data = LoadSandboxTemplate(sandbox_type);
490   if (!sandbox_data) {
491     return false;
492   }
494   SandboxVariableSubstitions substitutions;
495   if (!allowed_dir.empty()) {
496     // Add the sandbox commands necessary to access the given directory.
497     // Note: this function must be called before PostProcessSandboxProfile()
498     // since the string it inserts contains variables that need substitution.
499     NSString* allowed_dir_sandbox_command =
500         BuildAllowDirectoryAccessSandboxString(allowed_dir, &substitutions);
502     if (allowed_dir_sandbox_command) {  // May be nil if function fails.
503       sandbox_data = [sandbox_data
504           stringByReplacingOccurrencesOfString:@";ENABLE_DIRECTORY_ACCESS"
505                                     withString:allowed_dir_sandbox_command];
506     }
507   }
509   NSMutableArray* tokens_to_remove = [NSMutableArray array];
511   // Enable verbose logging if enabled on the command line. (See common.sb
512   // for details).
513   const CommandLine* command_line = CommandLine::ForCurrentProcess();
514   bool enable_logging =
515       command_line->HasSwitch(switches::kEnableSandboxLogging);;
516   if (enable_logging) {
517     [tokens_to_remove addObject:@";ENABLE_LOGGING"];
518   }
520   bool lion_or_later = base::mac::IsOSLionOrLater();
522   // Without this, the sandbox will print a message to the system log every
523   // time it denies a request.  This floods the console with useless spew.
524   if (!enable_logging) {
525     substitutions["DISABLE_SANDBOX_DENIAL_LOGGING"] =
526         SandboxSubstring("(with no-log)");
527   } else {
528     substitutions["DISABLE_SANDBOX_DENIAL_LOGGING"] = SandboxSubstring("");
529   }
531   // Splice the path of the user's home directory into the sandbox profile
532   // (see renderer.sb for details).
533   std::string home_dir = [NSHomeDirectory() fileSystemRepresentation];
535   base::FilePath home_dir_canonical =
536       GetCanonicalSandboxPath(base::FilePath(home_dir));
538   substitutions["USER_HOMEDIR_AS_LITERAL"] =
539       SandboxSubstring(home_dir_canonical.value(),
540           SandboxSubstring::LITERAL);
542   if (lion_or_later) {
543     // >=10.7 Sandbox rules.
544     [tokens_to_remove addObject:@";10.7_OR_ABOVE"];
545   }
547   substitutions["COMPONENT_BUILD_WORKAROUND"] = SandboxSubstring("");
548 #if defined(COMPONENT_BUILD)
549   // dlopen() fails without file-read-metadata access if the executable image
550   // contains LC_RPATH load commands. The components build uses those.
551   // See http://crbug.com/127465
552   if (base::mac::IsOSSnowLeopard()) {
553     base::FilePath bundle_executable = base::mac::NSStringToFilePath(
554         [base::mac::MainBundle() executablePath]);
555     NSString* sandbox_command = AllowMetadataForPath(
556         GetCanonicalSandboxPath(bundle_executable));
557     substitutions["COMPONENT_BUILD_WORKAROUND"] =
558         SandboxSubstring(base::SysNSStringToUTF8(sandbox_command));
559   }
560 #endif
562   // All information needed to assemble the final profile has been collected.
563   // Merge it all together.
564   std::string final_sandbox_profile_str;
565   if (!PostProcessSandboxProfile(sandbox_data, tokens_to_remove, substitutions,
566                                  &final_sandbox_profile_str)) {
567     return false;
568   }
570   // Initialize sandbox.
571   char* error_buff = NULL;
572   int error = sandbox_init(final_sandbox_profile_str.c_str(), 0, &error_buff);
573   bool success = (error == 0 && error_buff == NULL);
574   DLOG_IF(FATAL, !success) << "Failed to initialize sandbox: "
575                            << error
576                            << " "
577                            << error_buff;
578   sandbox_free_error(error_buff);
579   gSandboxIsActive = success;
580   return success;
583 // static
584 bool Sandbox::SandboxIsCurrentlyActive() {
585   return gSandboxIsActive;
588 // static
589 base::FilePath Sandbox::GetCanonicalSandboxPath(const base::FilePath& path) {
590   int fd = HANDLE_EINTR(open(path.value().c_str(), O_RDONLY));
591   if (fd < 0) {
592     DPLOG(FATAL) << "GetCanonicalSandboxPath() failed for: "
593                  << path.value();
594     return path;
595   }
596   file_util::ScopedFD file_closer(&fd);
598   base::FilePath::CharType canonical_path[MAXPATHLEN];
599   if (HANDLE_EINTR(fcntl(fd, F_GETPATH, canonical_path)) != 0) {
600     DPLOG(FATAL) << "GetCanonicalSandboxPath() failed for: "
601                  << path.value();
602     return path;
603   }
605   return base::FilePath(canonical_path);
608 }  // namespace content