Add ICU message format support
[chromium-blink-merge.git] / content / common / sandbox_mac.mm
blob4498320b1641e14315ff63002768e669da57ad38
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_split.h"
31 #include "base/strings/string_util.h"
32 #include "base/strings/stringprintf.h"
33 #include "base/strings/sys_string_conversions.h"
34 #include "base/strings/utf_string_conversions.h"
35 #include "base/sys_info.h"
36 #include "content/common/gpu/media/vt_video_decode_accelerator.h"
37 #include "content/grit/content_resources.h"
38 #include "content/public/common/content_client.h"
39 #include "content/public/common/content_switches.h"
40 #include "third_party/icu/source/common/unicode/uchar.h"
41 #include "ui/base/layout.h"
42 #include "ui/gl/gl_surface.h"
44 extern "C" {
45 void CGSSetDenyWindowServerConnections(bool);
46 void CGSShutdownServerConnections();
48 void* sandbox_create_params();
49 int sandbox_set_param(void* params, const char* key, const char* value);
50 void* sandbox_compile_string(const char* profile_str,
51                              void* params,
52                              char** error);
53 int sandbox_apply(void* profile);
54 void sandbox_free_params(void* params);
55 void sandbox_free_profile(void* profile);
58 namespace content {
59 namespace {
61 // Is the sandbox currently active.
62 bool gSandboxIsActive = false;
64 struct SandboxTypeToResourceIDMapping {
65   SandboxType sandbox_type;
66   int sandbox_profile_resource_id;
69 // This is the internal definition of the structure used by sandbox parameters
70 // on OS X 10.6.
71 struct SandboxParams {
72   void* buf;
73   size_t count;
74   size_t size;
77 // Mapping from sandbox process types to resource IDs containing the sandbox
78 // profile for all process types known to content.
79 SandboxTypeToResourceIDMapping kDefaultSandboxTypeToResourceIDMapping[] = {
80   { SANDBOX_TYPE_RENDERER, IDR_RENDERER_SANDBOX_PROFILE },
81   { SANDBOX_TYPE_UTILITY,  IDR_UTILITY_SANDBOX_PROFILE },
82   { SANDBOX_TYPE_GPU,      IDR_GPU_SANDBOX_PROFILE },
83   { SANDBOX_TYPE_PPAPI,    IDR_PPAPI_SANDBOX_PROFILE },
86 static_assert(arraysize(kDefaultSandboxTypeToResourceIDMapping) == \
87               size_t(SANDBOX_TYPE_AFTER_LAST_TYPE), \
88               "sandbox type to resource id mapping incorrect");
90 // Try to escape |c| as a "SingleEscapeCharacter" (\n, etc).  If successful,
91 // returns true and appends the escape sequence to |dst|.
92 bool EscapeSingleChar(char c, std::string* dst) {
93   const char *append = NULL;
94   switch (c) {
95     case '\b':
96       append = "\\b";
97       break;
98     case '\f':
99       append = "\\f";
100       break;
101     case '\n':
102       append = "\\n";
103       break;
104     case '\r':
105       append = "\\r";
106       break;
107     case '\t':
108       append = "\\t";
109       break;
110     case '\\':
111       append = "\\\\";
112       break;
113     case '"':
114       append = "\\\"";
115       break;
116   }
118   if (!append) {
119     return false;
120   }
122   dst->append(append);
123   return true;
126 // Errors quoting strings for the Sandbox profile are always fatal, report them
127 // in a central place.
128 NOINLINE void FatalStringQuoteException(const std::string& str) {
129   // Copy bad string to the stack so it's recorded in the crash dump.
130   char bad_string[256] = {0};
131   base::strlcpy(bad_string, str.c_str(), arraysize(bad_string));
132   DLOG(FATAL) << "String quoting failed " << bad_string;
135 }  // namespace
137 SandboxCompiler::SandboxCompiler(const std::string& profile_str)
138     : params_map_(), profile_str_(profile_str) {
141 SandboxCompiler::~SandboxCompiler() {
144 bool SandboxCompiler::InsertBooleanParam(const std::string& key, bool value) {
145   return params_map_.insert(std::make_pair(key, value ? "TRUE" : "FALSE"))
146       .second;
149 bool SandboxCompiler::InsertStringParam(const std::string& key,
150                                         const std::string& value) {
151   return params_map_.insert(std::make_pair(key, value)).second;
154 void SandboxCompiler::FreeSandboxResources(void* profile,
155                                            void* params,
156                                            char* error) {
157   if (error)
158     sandbox_free_error(error);
159   if (params)
160     sandbox_free_params(params);
161   if (profile)
162     sandbox_free_profile(profile);
165 bool SandboxCompiler::CompileAndApplyProfile(std::string* error) {
166   char* error_internal = nullptr;
167   void* profile = nullptr;
168   void* params = nullptr;
170   if (!params_map_.empty()) {
171     if (base::mac::IsOSSnowLeopard()) {
172       // This is a workaround for 10.6, see crbug.com/509114.
173       // Check that there is no integer overflow.
174       base::CheckedNumeric<size_t> checked_size = params_map_.size();
175       checked_size *= 2;
176       if (!checked_size.IsValid())
177         return false;
179       SandboxParams* internal_params =
180           static_cast<SandboxParams*>(malloc(sizeof(SandboxParams)));
181       internal_params->buf = calloc(checked_size.ValueOrDie(), sizeof(void*));
182       internal_params->count = 0;
183       internal_params->size = checked_size.ValueOrDie();
184       params = internal_params;
185     } else {
186       params = sandbox_create_params();
187       if (!params)
188         return false;
189     }
191     for (const auto& kv : params_map_)
192       sandbox_set_param(params, kv.first.c_str(), kv.second.c_str());
193   }
195   profile =
196       sandbox_compile_string(profile_str_.c_str(), params, &error_internal);
197   if (!profile) {
198     error->assign(error_internal);
199     FreeSandboxResources(profile, params, error_internal);
200     return false;
201   }
203   int result = sandbox_apply(profile);
204   FreeSandboxResources(profile, params, error_internal);
205   return result == 0;
208 // static
209 bool Sandbox::QuotePlainString(const std::string& src_utf8, std::string* dst) {
210   dst->clear();
212   const char* src = src_utf8.c_str();
213   int32_t length = src_utf8.length();
214   int32_t position = 0;
215   while (position < length) {
216     UChar32 c;
217     U8_NEXT(src, position, length, c);  // Macro increments |position|.
218     DCHECK_GE(c, 0);
219     if (c < 0)
220       return false;
222     if (c < 128) {  // EscapeSingleChar only handles ASCII.
223       char as_char = static_cast<char>(c);
224       if (EscapeSingleChar(as_char, dst)) {
225         continue;
226       }
227     }
229     if (c < 32 || c > 126) {
230       // Any characters that aren't printable ASCII get the \u treatment.
231       unsigned int as_uint = static_cast<unsigned int>(c);
232       base::StringAppendF(dst, "\\u%04X", as_uint);
233       continue;
234     }
236     // If we got here we know that the character in question is strictly
237     // in the ASCII range so there's no need to do any kind of encoding
238     // conversion.
239     dst->push_back(static_cast<char>(c));
240   }
241   return true;
244 // static
245 bool Sandbox::QuoteStringForRegex(const std::string& str_utf8,
246                                   std::string* dst) {
247   // Characters with special meanings in sandbox profile syntax.
248   const char regex_special_chars[] = {
249     '\\',
251     // Metacharacters
252     '^',
253     '.',
254     '[',
255     ']',
256     '$',
257     '(',
258     ')',
259     '|',
261     // Quantifiers
262     '*',
263     '+',
264     '?',
265     '{',
266     '}',
267   };
269   // Anchor regex at start of path.
270   dst->assign("^");
272   const char* src = str_utf8.c_str();
273   int32_t length = str_utf8.length();
274   int32_t position = 0;
275   while (position < length) {
276     UChar32 c;
277     U8_NEXT(src, position, length, c);  // Macro increments |position|.
278     DCHECK_GE(c, 0);
279     if (c < 0)
280       return false;
282     // The Mac sandbox regex parser only handles printable ASCII characters.
283     // 33 >= c <= 126
284     if (c < 32 || c > 125) {
285       return false;
286     }
288     for (size_t i = 0; i < arraysize(regex_special_chars); ++i) {
289       if (c == regex_special_chars[i]) {
290         dst->push_back('\\');
291         break;
292       }
293     }
295     dst->push_back(static_cast<char>(c));
296   }
298   // Make sure last element of path is interpreted as a directory. Leaving this
299   // off would allow access to files if they start with the same name as the
300   // directory.
301   dst->append("(/|$)");
303   return true;
306 // Warm up System APIs that empirically need to be accessed before the Sandbox
307 // is turned on.
308 // This method is layed out in blocks, each one containing a separate function
309 // that needs to be warmed up. The OS version on which we found the need to
310 // enable the function is also noted.
311 // This function is tested on the following OS versions:
312 //     10.5.6, 10.6.0
314 // static
315 void Sandbox::SandboxWarmup(int sandbox_type) {
316   base::mac::ScopedNSAutoreleasePool scoped_pool;
318   { // CGColorSpaceCreateWithName(), CGBitmapContextCreate() - 10.5.6
319     base::ScopedCFTypeRef<CGColorSpaceRef> rgb_colorspace(
320         CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB));
322     // Allocate a 1x1 image.
323     char data[4];
324     base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate(
325         data,
326         1,
327         1,
328         8,
329         1 * 4,
330         rgb_colorspace,
331         kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host));
333     // Load in the color profiles we'll need (as a side effect).
334     ignore_result(base::mac::GetSRGBColorSpace());
335     ignore_result(base::mac::GetSystemColorSpace());
337     // CGColorSpaceCreateSystemDefaultCMYK - 10.6
338     base::ScopedCFTypeRef<CGColorSpaceRef> cmyk_colorspace(
339         CGColorSpaceCreateWithName(kCGColorSpaceGenericCMYK));
340   }
342   { // localtime() - 10.5.6
343     time_t tv = {0};
344     localtime(&tv);
345   }
347   { // Gestalt() tries to read /System/Library/CoreServices/SystemVersion.plist
348     // on 10.5.6
349     int32 tmp;
350     base::SysInfo::OperatingSystemVersionNumbers(&tmp, &tmp, &tmp);
351   }
353   {  // CGImageSourceGetStatus() - 10.6
354      // Create a png with just enough data to get everything warmed up...
355     char png_header[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
356     NSData* data = [NSData dataWithBytes:png_header
357                                   length:arraysize(png_header)];
358     base::ScopedCFTypeRef<CGImageSourceRef> img(
359         CGImageSourceCreateWithData((CFDataRef)data, NULL));
360     CGImageSourceGetStatus(img);
361   }
363   {
364     // Allow access to /dev/urandom.
365     base::GetUrandomFD();
366   }
368   { // IOSurfaceLookup() - 10.7
369     // Needed by zero-copy texture update framework - crbug.com/323338
370     base::ScopedCFTypeRef<IOSurfaceRef> io_surface(IOSurfaceLookup(0));
371   }
373   // Process-type dependent warm-up.
374   if (sandbox_type == SANDBOX_TYPE_UTILITY) {
375     // CFTimeZoneCopyZone() tries to read /etc and /private/etc/localtime - 10.8
376     // Needed by Media Galleries API Picasa - crbug.com/151701
377     CFTimeZoneCopySystem();
378   }
380   if (sandbox_type == SANDBOX_TYPE_GPU) {
381     // Preload either the desktop GL or the osmesa so, depending on the
382     // --use-gl flag.
383     gfx::GLSurface::InitializeOneOff();
385     // Preload VideoToolbox.
386     InitializeVideoToolbox();
387   }
389   if (sandbox_type == SANDBOX_TYPE_PPAPI) {
390     // Preload AppKit color spaces used for Flash/ppapi. http://crbug.com/348304
391     NSColor* color = [NSColor controlTextColor];
392     [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
393   }
395   if (sandbox_type == SANDBOX_TYPE_RENDERER &&
396       base::mac::IsOSMountainLionOrLater()) {
397     // Now disconnect from WindowServer, after all objects have been warmed up.
398     // Shutting down the connection requires connecting to WindowServer,
399     // so do this before actually engaging the sandbox. This is only done on
400     // 10.8 and higher because doing it on earlier OSes causes layout tests to
401     // fail <http://crbug.com/397642#c48>. This may cause two log messages to
402     // be printed to the system logger on certain OS versions.
403     CGSSetDenyWindowServerConnections(true);
404     CGSShutdownServerConnections();
405   }
408 // Load the appropriate template for the given sandbox type.
409 // Returns the template as an NSString or nil on error.
410 NSString* LoadSandboxTemplate(int sandbox_type) {
411   // We use a custom sandbox definition to lock things down as tightly as
412   // possible.
413   int sandbox_profile_resource_id = -1;
415   // Find resource id for sandbox profile to use for the specific sandbox type.
416   for (size_t i = 0;
417        i < arraysize(kDefaultSandboxTypeToResourceIDMapping);
418        ++i) {
419     if (kDefaultSandboxTypeToResourceIDMapping[i].sandbox_type ==
420         sandbox_type) {
421       sandbox_profile_resource_id =
422           kDefaultSandboxTypeToResourceIDMapping[i].sandbox_profile_resource_id;
423       break;
424     }
425   }
426   if (sandbox_profile_resource_id == -1) {
427     // Check if the embedder knows about this sandbox process type.
428     bool sandbox_type_found =
429         GetContentClient()->GetSandboxProfileForSandboxType(
430             sandbox_type, &sandbox_profile_resource_id);
431     CHECK(sandbox_type_found) << "Unknown sandbox type " << sandbox_type;
432   }
434   base::StringPiece sandbox_definition =
435       GetContentClient()->GetDataResource(
436           sandbox_profile_resource_id, ui::SCALE_FACTOR_NONE);
437   if (sandbox_definition.empty()) {
438     LOG(FATAL) << "Failed to load the sandbox profile (resource id "
439                << sandbox_profile_resource_id << ")";
440     return nil;
441   }
443   base::StringPiece common_sandbox_definition =
444       GetContentClient()->GetDataResource(
445           IDR_COMMON_SANDBOX_PROFILE, ui::SCALE_FACTOR_NONE);
446   if (common_sandbox_definition.empty()) {
447     LOG(FATAL) << "Failed to load the common sandbox profile";
448     return nil;
449   }
451   base::scoped_nsobject<NSString> common_sandbox_prefix_data(
452       [[NSString alloc] initWithBytes:common_sandbox_definition.data()
453                                length:common_sandbox_definition.length()
454                              encoding:NSUTF8StringEncoding]);
456   base::scoped_nsobject<NSString> sandbox_data(
457       [[NSString alloc] initWithBytes:sandbox_definition.data()
458                                length:sandbox_definition.length()
459                              encoding:NSUTF8StringEncoding]);
461   // Prefix sandbox_data with common_sandbox_prefix_data.
462   return [common_sandbox_prefix_data stringByAppendingString:sandbox_data];
465 // Turns on the OS X sandbox for this process.
467 // static
468 bool Sandbox::EnableSandbox(int sandbox_type,
469                             const base::FilePath& allowed_dir) {
470   // Sanity - currently only SANDBOX_TYPE_UTILITY supports a directory being
471   // passed in.
472   if (sandbox_type < SANDBOX_TYPE_AFTER_LAST_TYPE &&
473       sandbox_type != SANDBOX_TYPE_UTILITY) {
474     DCHECK(allowed_dir.empty())
475         << "Only SANDBOX_TYPE_UTILITY allows a custom directory parameter.";
476   }
478   NSString* sandbox_data = LoadSandboxTemplate(sandbox_type);
479   if (!sandbox_data) {
480     return false;
481   }
483   SandboxCompiler compiler([sandbox_data UTF8String]);
485   if (!allowed_dir.empty()) {
486     // Add the sandbox parameters necessary to access the given directory.
487     base::FilePath allowed_dir_canonical = GetCanonicalSandboxPath(allowed_dir);
488     std::string regex;
489     if (!QuoteStringForRegex(allowed_dir_canonical.value(), &regex)) {
490       FatalStringQuoteException(allowed_dir_canonical.value());
491       return false;
492     }
493     if (!compiler.InsertStringParam("PERMITTED_DIR", regex))
494       return false;
495   }
497   // Enable verbose logging if enabled on the command line. (See common.sb
498   // for details).
499   const base::CommandLine* command_line =
500       base::CommandLine::ForCurrentProcess();
501   bool enable_logging =
502       command_line->HasSwitch(switches::kEnableSandboxLogging);;
503   if (!compiler.InsertBooleanParam("ENABLE_LOGGING", enable_logging))
504     return false;
506   // Without this, the sandbox will print a message to the system log every
507   // time it denies a request.  This floods the console with useless spew.
508   if (!compiler.InsertBooleanParam("DISABLE_SANDBOX_DENIAL_LOGGING",
509                                    !enable_logging))
510     return false;
512   // Splice the path of the user's home directory into the sandbox profile
513   // (see renderer.sb for details).
514   std::string home_dir = [NSHomeDirectory() fileSystemRepresentation];
516   base::FilePath home_dir_canonical =
517       GetCanonicalSandboxPath(base::FilePath(home_dir));
519   std::string quoted_home_dir;
520   if (!QuotePlainString(home_dir_canonical.value(), &quoted_home_dir)) {
521     FatalStringQuoteException(home_dir_canonical.value());
522     return false;
523   }
525   if (!compiler.InsertStringParam("USER_HOMEDIR_AS_LITERAL", quoted_home_dir))
526     return false;
528   bool lion_or_later = base::mac::IsOSLionOrLater();
529   if (!compiler.InsertBooleanParam("LION_OR_LATER", lion_or_later))
530     return false;
532 #if defined(COMPONENT_BUILD)
533   // dlopen() fails without file-read-metadata access if the executable image
534   // contains LC_RPATH load commands. The components build uses those.
535   // See http://crbug.com/127465
536   if (base::mac::IsOSSnowLeopard()) {
537     if (!compiler.InsertBooleanParam("COMPONENT_BUILD_WORKAROUND", true))
538       return false;
539   }
540 #endif
542   // Initialize sandbox.
543   std::string error_str;
544   bool success = compiler.CompileAndApplyProfile(&error_str);
545   DLOG_IF(FATAL, !success) << "Failed to initialize sandbox: " << error_str;
546   gSandboxIsActive = success;
547   return success;
550 // static
551 bool Sandbox::SandboxIsCurrentlyActive() {
552   return gSandboxIsActive;
555 // static
556 base::FilePath Sandbox::GetCanonicalSandboxPath(const base::FilePath& path) {
557   base::ScopedFD fd(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)));
558   if (!fd.is_valid()) {
559     DPLOG(FATAL) << "GetCanonicalSandboxPath() failed for: "
560                  << path.value();
561     return path;
562   }
564   base::FilePath::CharType canonical_path[MAXPATHLEN];
565   if (HANDLE_EINTR(fcntl(fd.get(), F_GETPATH, canonical_path)) != 0) {
566     DPLOG(FATAL) << "GetCanonicalSandboxPath() failed for: "
567                  << path.value();
568     return path;
569   }
571   return base::FilePath(canonical_path);
574 }  // namespace content