Home | History | Annotate | Download | only in common
      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 #import <Cocoa/Cocoa.h>
      6 #include <dirent.h>
      7 
      8 extern "C" {
      9 #include <sandbox.h>
     10 }
     11 
     12 #include "base/file_util.h"
     13 #include "base/files/file_path.h"
     14 #include "base/process/kill.h"
     15 #include "base/strings/sys_string_conversions.h"
     16 #include "base/strings/utf_string_conversions.h"
     17 #include "base/test/multiprocess_test.h"
     18 #include "content/common/sandbox_mac.h"
     19 #include "testing/gtest/include/gtest/gtest.h"
     20 #include "testing/multiprocess_func_list.h"
     21 
     22 namespace {
     23 
     24 static const char* kSandboxAccessPathKey = "sandbox_dir";
     25 static const char* kDeniedSuffix = "_denied";
     26 
     27 }  // namespace
     28 
     29 // Tests need to be in the same namespace as the Sandbox class to be useable
     30 // with FRIEND_TEST() declaration.
     31 namespace content {
     32 
     33 class MacDirAccessSandboxTest : public base::MultiProcessTest {
     34  public:
     35   bool CheckSandbox(const std::string& directory_to_try) {
     36     setenv(kSandboxAccessPathKey, directory_to_try.c_str(), 1);
     37     base::ProcessHandle child_process = SpawnChild("mac_sandbox_path_access",
     38                                                    false);
     39     if (child_process == base::kNullProcessHandle) {
     40       LOG(WARNING) << "SpawnChild failed";
     41       return false;
     42     }
     43     int code = -1;
     44     if (!base::WaitForExitCode(child_process, &code)) {
     45       LOG(WARNING) << "base::WaitForExitCode failed";
     46       return false;
     47     }
     48     return code == 0;
     49   }
     50 };
     51 
     52 TEST_F(MacDirAccessSandboxTest, StringEscape) {
     53   const struct string_escape_test_data {
     54   const char* to_escape;
     55   const char* escaped;
     56   } string_escape_cases[] = {
     57     {"", ""},
     58     {"\b\f\n\r\t\\\"", "\\b\\f\\n\\r\\t\\\\\\\""},
     59     {"/'", "/'"},
     60     {"sandwich", "sandwich"},
     61     {"(sandwich)", "(sandwich)"},
     62     {"^\u2135.\u2136$", "^\\u2135.\\u2136$"},
     63   };
     64 
     65   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(string_escape_cases); ++i) {
     66     std::string out;
     67     std::string in(string_escape_cases[i].to_escape);
     68     EXPECT_TRUE(Sandbox::QuotePlainString(in, &out));
     69     EXPECT_EQ(string_escape_cases[i].escaped, out);
     70   }
     71 }
     72 
     73 TEST_F(MacDirAccessSandboxTest, RegexEscape) {
     74   const std::string kSandboxEscapeSuffix("(/|$)");
     75   const struct regex_test_data {
     76     const wchar_t *to_escape;
     77     const char* escaped;
     78   } regex_cases[] = {
     79     {L"", ""},
     80     {L"/'", "/'"},  // / & ' characters don't need escaping.
     81     {L"sandwich", "sandwich"},
     82     {L"(sandwich)", "\\(sandwich\\)"},
     83   };
     84 
     85   // Check that all characters whose values are smaller than 32 [1F] are
     86   // rejected by the regex escaping code.
     87   {
     88     std::string out;
     89     char fail_string[] = {31, 0};
     90     char ok_string[] = {32, 0};
     91     EXPECT_FALSE(Sandbox::QuoteStringForRegex(fail_string, &out));
     92     EXPECT_TRUE(Sandbox::QuoteStringForRegex(ok_string, &out));
     93   }
     94 
     95   // Check that all characters whose values are larger than 126 [7E] are
     96   // rejected by the regex escaping code.
     97   {
     98     std::string out;
     99     EXPECT_TRUE(Sandbox::QuoteStringForRegex("}", &out));   // } == 0x7D == 125
    100     EXPECT_FALSE(Sandbox::QuoteStringForRegex("~", &out));  // ~ == 0x7E == 126
    101     EXPECT_FALSE(
    102         Sandbox::QuoteStringForRegex(WideToUTF8(L"^\u2135.\u2136$"), &out));
    103   }
    104 
    105   {
    106     for (size_t i = 0; i < ARRAYSIZE_UNSAFE(regex_cases); ++i) {
    107       std::string out;
    108       std::string in = WideToUTF8(regex_cases[i].to_escape);
    109       EXPECT_TRUE(Sandbox::QuoteStringForRegex(in, &out));
    110       std::string expected("^");
    111       expected.append(regex_cases[i].escaped);
    112       expected.append(kSandboxEscapeSuffix);
    113       EXPECT_EQ(expected, out);
    114     }
    115   }
    116 
    117   {
    118     std::string in_utf8("\\^.$|()[]*+?{}");
    119     std::string expected;
    120     expected.push_back('^');
    121     for (size_t i = 0; i < in_utf8.length(); ++i) {
    122       expected.push_back('\\');
    123       expected.push_back(in_utf8[i]);
    124     }
    125     expected.append(kSandboxEscapeSuffix);
    126 
    127     std::string out;
    128     EXPECT_TRUE(Sandbox::QuoteStringForRegex(in_utf8, &out));
    129     EXPECT_EQ(expected, out);
    130 
    131   }
    132 }
    133 
    134 // A class to handle auto-deleting a directory.
    135 class ScopedDirectoryDelete {
    136  public:
    137   inline void operator()(base::FilePath* x) const {
    138     if (x) {
    139       base::DeleteFile(*x, true);
    140     }
    141   }
    142 };
    143 
    144 typedef scoped_ptr_malloc<base::FilePath, ScopedDirectoryDelete>
    145     ScopedDirectory;
    146 
    147 TEST_F(MacDirAccessSandboxTest, SandboxAccess) {
    148   using base::CreateDirectory;
    149 
    150   base::FilePath tmp_dir;
    151   ASSERT_TRUE(base::CreateNewTempDirectory(base::FilePath::StringType(),
    152                                            &tmp_dir));
    153   // This step is important on OS X since the sandbox only understands "real"
    154   // paths and the paths CreateNewTempDirectory() returns are empirically in
    155   // /var which is a symlink to /private/var .
    156   tmp_dir = Sandbox::GetCanonicalSandboxPath(tmp_dir);
    157   ScopedDirectory cleanup(&tmp_dir);
    158 
    159   const char* sandbox_dir_cases[] = {
    160     "simple_dir_name",
    161     "^hello++ $",       // Regex.
    162     "\\^.$|()[]*+?{}",  // All regex characters.
    163   };
    164 
    165   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(sandbox_dir_cases); ++i) {
    166     const char* sandbox_dir_name = sandbox_dir_cases[i];
    167     base::FilePath sandbox_dir = tmp_dir.Append(sandbox_dir_name);
    168     ASSERT_TRUE(CreateDirectory(sandbox_dir));
    169     ScopedDirectory cleanup_sandbox(&sandbox_dir);
    170 
    171     // Create a sibling directory of the sandbox dir, whose name has sandbox dir
    172     // as a substring but to which access is denied.
    173     std::string sibling_sandbox_dir_name_denied =
    174         std::string(sandbox_dir_cases[i]) + kDeniedSuffix;
    175     base::FilePath sibling_sandbox_dir = tmp_dir.Append(
    176                                       sibling_sandbox_dir_name_denied.c_str());
    177     ASSERT_TRUE(CreateDirectory(sibling_sandbox_dir));
    178     ScopedDirectory cleanup_sandbox_sibling(&sibling_sandbox_dir);
    179 
    180     EXPECT_TRUE(CheckSandbox(sandbox_dir.value()));
    181   }
    182 }
    183 
    184 MULTIPROCESS_TEST_MAIN(mac_sandbox_path_access) {
    185   char *sandbox_allowed_dir = getenv(kSandboxAccessPathKey);
    186   if (!sandbox_allowed_dir)
    187     return -1;
    188 
    189   // Build up a sandbox profile that only allows access to a single directory.
    190   NSString *sandbox_profile =
    191       @"(version 1)" \
    192       "(deny default)" \
    193       "(allow signal (target self))" \
    194       "(allow sysctl-read)" \
    195       ";ENABLE_DIRECTORY_ACCESS";
    196 
    197   std::string allowed_dir(sandbox_allowed_dir);
    198   Sandbox::SandboxVariableSubstitions substitutions;
    199   NSString* allow_dir_sandbox_code =
    200       Sandbox::BuildAllowDirectoryAccessSandboxString(
    201           base::FilePath(sandbox_allowed_dir),
    202           &substitutions);
    203   sandbox_profile = [sandbox_profile
    204       stringByReplacingOccurrencesOfString:@";ENABLE_DIRECTORY_ACCESS"
    205                                 withString:allow_dir_sandbox_code];
    206 
    207   std::string final_sandbox_profile_str;
    208   if (!Sandbox::PostProcessSandboxProfile(sandbox_profile,
    209                                           [NSArray array],
    210                                           substitutions,
    211                                           &final_sandbox_profile_str)) {
    212     LOG(ERROR) << "Call to PostProcessSandboxProfile() failed";
    213     return -1;
    214   }
    215 
    216   // Enable Sandbox.
    217   char* error_buff = NULL;
    218   int error = sandbox_init(final_sandbox_profile_str.c_str(), 0, &error_buff);
    219   if (error == -1) {
    220     LOG(ERROR) << "Failed to Initialize Sandbox: " << error_buff;
    221     return -1;
    222   }
    223   sandbox_free_error(error_buff);
    224 
    225   // Test Sandbox.
    226 
    227   // We should be able to list the contents of the sandboxed directory.
    228   DIR *file_list = NULL;
    229   file_list = opendir(sandbox_allowed_dir);
    230   if (!file_list) {
    231     PLOG(ERROR) << "Sandbox overly restrictive: call to opendir("
    232                 << sandbox_allowed_dir
    233                 << ") failed";
    234     return -1;
    235   }
    236   closedir(file_list);
    237 
    238   // Test restrictions on accessing files.
    239   base::FilePath allowed_dir_path(sandbox_allowed_dir);
    240   base::FilePath allowed_file = allowed_dir_path.Append("ok_to_write");
    241   base::FilePath denied_file1 =
    242       allowed_dir_path.DirName().Append("cant_access");
    243 
    244   // Try to write a file who's name has the same prefix as the directory we
    245   // allow access to.
    246   base::FilePath basename = allowed_dir_path.BaseName();
    247   base::FilePath allowed_parent_dir = allowed_dir_path.DirName();
    248   std::string tricky_filename = basename.value() + "123";
    249   base::FilePath denied_file2 =  allowed_parent_dir.Append(tricky_filename);
    250 
    251   if (open(allowed_file.value().c_str(), O_WRONLY | O_CREAT) <= 0) {
    252     PLOG(ERROR) << "Sandbox overly restrictive: failed to write ("
    253                 << allowed_file.value()
    254                 << ")";
    255     return -1;
    256   }
    257 
    258   // Test that we deny access to a sibling of the sandboxed directory whose
    259   // name has the sandboxed directory name as a substring. e.g. if the sandbox
    260   // directory is /foo/baz then test /foo/baz_denied.
    261   {
    262     struct stat tmp_stat_info;
    263     std::string denied_sibling =
    264         std::string(sandbox_allowed_dir) + kDeniedSuffix;
    265     if (stat(denied_sibling.c_str(), &tmp_stat_info) > 0) {
    266       PLOG(ERROR) << "Sandbox breach: was able to stat ("
    267                   << denied_sibling.c_str()
    268                   << ")";
    269       return -1;
    270     }
    271   }
    272 
    273   // Test that we can stat parent directories of the "allowed" directory.
    274   {
    275     struct stat tmp_stat_info;
    276     if (stat(allowed_parent_dir.value().c_str(), &tmp_stat_info) != 0) {
    277       PLOG(ERROR) << "Sandbox overly restrictive: unable to stat ("
    278                   << allowed_parent_dir.value()
    279                   << ")";
    280       return -1;
    281     }
    282   }
    283 
    284   // Test that we can't stat files outside the "allowed" directory.
    285   {
    286     struct stat tmp_stat_info;
    287     if (stat(denied_file1.value().c_str(), &tmp_stat_info) > 0) {
    288       PLOG(ERROR) << "Sandbox breach: was able to stat ("
    289                   << denied_file1.value()
    290                   << ")";
    291       return -1;
    292     }
    293   }
    294 
    295   if (open(denied_file1.value().c_str(), O_WRONLY | O_CREAT) > 0) {
    296     PLOG(ERROR) << "Sandbox breach: was able to write ("
    297                 << denied_file1.value()
    298                 << ")";
    299     return -1;
    300   }
    301 
    302   if (open(denied_file2.value().c_str(), O_WRONLY | O_CREAT) > 0) {
    303     PLOG(ERROR) << "Sandbox breach: was able to write ("
    304                 << denied_file2.value()
    305                 << ")";
    306     return -1;
    307   }
    308 
    309   return 0;
    310 }
    311 
    312 }  // namespace content
    313