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