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