Make developerPrivate API available in stable.
[chromium-blink-merge.git] / content / common / sandbox_mac_diraccess_unittest.mm
blobc8611dfd45a43f26cc07c859f3ee1c0417db7b5b
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 #import <Cocoa/Cocoa.h>
6 #include <dirent.h>
8 extern "C" {
9 #include <sandbox.h>
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"
22 namespace {
24 static const char* kSandboxAccessPathKey = "sandbox_dir";
25 static const char* kDeniedSuffix = "_denied";
27 }  // namespace
29 // Tests need to be in the same namespace as the Sandbox class to be useable
30 // with FRIEND_TEST() declaration.
31 namespace content {
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   }
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   };
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   }
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   };
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   }
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   }
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   }
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);
127     std::string out;
128     EXPECT_TRUE(Sandbox::QuoteStringForRegex(in_utf8, &out));
129     EXPECT_EQ(expected, out);
131   }
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   }
144 typedef scoped_ptr_malloc<base::FilePath, ScopedDirectoryDelete>
145     ScopedDirectory;
147 TEST_F(MacDirAccessSandboxTest, SandboxAccess) {
148   using file_util::CreateDirectory;
150   base::FilePath tmp_dir;
151   ASSERT_TRUE(file_util::CreateNewTempDirectory("", &tmp_dir));
152   // This step is important on OS X since the sandbox only understands "real"
153   // paths and the paths CreateNewTempDirectory() returns are empirically in
154   // /var which is a symlink to /private/var .
155   tmp_dir = Sandbox::GetCanonicalSandboxPath(tmp_dir);
156   ScopedDirectory cleanup(&tmp_dir);
158   const char* sandbox_dir_cases[] = {
159     "simple_dir_name",
160     "^hello++ $",       // Regex.
161     "\\^.$|()[]*+?{}",  // All regex characters.
162   };
164   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(sandbox_dir_cases); ++i) {
165     const char* sandbox_dir_name = sandbox_dir_cases[i];
166     base::FilePath sandbox_dir = tmp_dir.Append(sandbox_dir_name);
167     ASSERT_TRUE(CreateDirectory(sandbox_dir));
168     ScopedDirectory cleanup_sandbox(&sandbox_dir);
170     // Create a sibling directory of the sandbox dir, whose name has sandbox dir
171     // as a substring but to which access is denied.
172     std::string sibling_sandbox_dir_name_denied =
173         std::string(sandbox_dir_cases[i]) + kDeniedSuffix;
174     base::FilePath sibling_sandbox_dir = tmp_dir.Append(
175                                       sibling_sandbox_dir_name_denied.c_str());
176     ASSERT_TRUE(CreateDirectory(sibling_sandbox_dir));
177     ScopedDirectory cleanup_sandbox_sibling(&sibling_sandbox_dir);
179     EXPECT_TRUE(CheckSandbox(sandbox_dir.value()));
180   }
183 MULTIPROCESS_TEST_MAIN(mac_sandbox_path_access) {
184   char *sandbox_allowed_dir = getenv(kSandboxAccessPathKey);
185   if (!sandbox_allowed_dir)
186     return -1;
188   // Build up a sandbox profile that only allows access to a single directory.
189   NSString *sandbox_profile =
190       @"(version 1)" \
191       "(deny default)" \
192       "(allow signal (target self))" \
193       "(allow sysctl-read)" \
194       ";ENABLE_DIRECTORY_ACCESS";
196   std::string allowed_dir(sandbox_allowed_dir);
197   Sandbox::SandboxVariableSubstitions substitutions;
198   NSString* allow_dir_sandbox_code =
199       Sandbox::BuildAllowDirectoryAccessSandboxString(
200           base::FilePath(sandbox_allowed_dir),
201           &substitutions);
202   sandbox_profile = [sandbox_profile
203       stringByReplacingOccurrencesOfString:@";ENABLE_DIRECTORY_ACCESS"
204                                 withString:allow_dir_sandbox_code];
206   std::string final_sandbox_profile_str;
207   if (!Sandbox::PostProcessSandboxProfile(sandbox_profile,
208                                           [NSArray array],
209                                           substitutions,
210                                           &final_sandbox_profile_str)) {
211     LOG(ERROR) << "Call to PostProcessSandboxProfile() failed";
212     return -1;
213   }
215   // Enable Sandbox.
216   char* error_buff = NULL;
217   int error = sandbox_init(final_sandbox_profile_str.c_str(), 0, &error_buff);
218   if (error == -1) {
219     LOG(ERROR) << "Failed to Initialize Sandbox: " << error_buff;
220     return -1;
221   }
222   sandbox_free_error(error_buff);
224   // Test Sandbox.
226   // We should be able to list the contents of the sandboxed directory.
227   DIR *file_list = NULL;
228   file_list = opendir(sandbox_allowed_dir);
229   if (!file_list) {
230     PLOG(ERROR) << "Sandbox overly restrictive: call to opendir("
231                 << sandbox_allowed_dir
232                 << ") failed";
233     return -1;
234   }
235   closedir(file_list);
237   // Test restrictions on accessing files.
238   base::FilePath allowed_dir_path(sandbox_allowed_dir);
239   base::FilePath allowed_file = allowed_dir_path.Append("ok_to_write");
240   base::FilePath denied_file1 =
241       allowed_dir_path.DirName().Append("cant_access");
243   // Try to write a file who's name has the same prefix as the directory we
244   // allow access to.
245   base::FilePath basename = allowed_dir_path.BaseName();
246   base::FilePath allowed_parent_dir = allowed_dir_path.DirName();
247   std::string tricky_filename = basename.value() + "123";
248   base::FilePath denied_file2 =  allowed_parent_dir.Append(tricky_filename);
250   if (open(allowed_file.value().c_str(), O_WRONLY | O_CREAT) <= 0) {
251     PLOG(ERROR) << "Sandbox overly restrictive: failed to write ("
252                 << allowed_file.value()
253                 << ")";
254     return -1;
255   }
257   // Test that we deny access to a sibling of the sandboxed directory whose
258   // name has the sandboxed directory name as a substring. e.g. if the sandbox
259   // directory is /foo/baz then test /foo/baz_denied.
260   {
261     struct stat tmp_stat_info;
262     std::string denied_sibling =
263         std::string(sandbox_allowed_dir) + kDeniedSuffix;
264     if (stat(denied_sibling.c_str(), &tmp_stat_info) > 0) {
265       PLOG(ERROR) << "Sandbox breach: was able to stat ("
266                   << denied_sibling.c_str()
267                   << ")";
268       return -1;
269     }
270   }
272   // Test that we can stat parent directories of the "allowed" directory.
273   {
274     struct stat tmp_stat_info;
275     if (stat(allowed_parent_dir.value().c_str(), &tmp_stat_info) != 0) {
276       PLOG(ERROR) << "Sandbox overly restrictive: unable to stat ("
277                   << allowed_parent_dir.value()
278                   << ")";
279       return -1;
280     }
281   }
283   // Test that we can't stat files outside the "allowed" directory.
284   {
285     struct stat tmp_stat_info;
286     if (stat(denied_file1.value().c_str(), &tmp_stat_info) > 0) {
287       PLOG(ERROR) << "Sandbox breach: was able to stat ("
288                   << denied_file1.value()
289                   << ")";
290       return -1;
291     }
292   }
294   if (open(denied_file1.value().c_str(), O_WRONLY | O_CREAT) > 0) {
295     PLOG(ERROR) << "Sandbox breach: was able to write ("
296                 << denied_file1.value()
297                 << ")";
298     return -1;
299   }
301   if (open(denied_file2.value().c_str(), O_WRONLY | O_CREAT) > 0) {
302     PLOG(ERROR) << "Sandbox breach: was able to write ("
303                 << denied_file2.value()
304                 << ")";
305     return -1;
306   }
308   return 0;
311 }  // namespace content