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