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