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