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>
12 #include "base/file_util.h"
13 #include "base/file_path.h"
14 #include "base/test/multiprocess_test.h"
15 #include "base/sys_string_conversions.h"
16 #include "base/utf_string_conversions.h"
17 #include "content/common/sandbox_mac.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 #include "testing/multiprocess_func_list.h"
23 static const char* kSandboxAccessPathKey = "sandbox_dir";
24 static const char* kDeniedSuffix = "_denied";
28 // Tests need to be in the same namespace as the Sandbox class to be useable
29 // with FRIEND_TEST() declaration.
32 class MacDirAccessSandboxTest : public base::MultiProcessTest {
34 bool CheckSandbox(const std::string& directory_to_try) {
35 setenv(kSandboxAccessPathKey, directory_to_try.c_str(), 1);
36 base::ProcessHandle child_process = SpawnChild("mac_sandbox_path_access",
38 if (child_process == base::kNullProcessHandle) {
39 LOG(WARNING) << "SpawnChild failed";
43 if (!base::WaitForExitCode(child_process, &code)) {
44 LOG(WARNING) << "base::WaitForExitCode failed";
51 TEST_F(MacDirAccessSandboxTest, StringEscape) {
52 const struct string_escape_test_data {
53 const char* to_escape;
55 } string_escape_cases[] = {
57 {"\b\f\n\r\t\\\"", "\\b\\f\\n\\r\\t\\\\\\\""},
59 {"sandwich", "sandwich"},
60 {"(sandwich)", "(sandwich)"},
61 {"^\u2135.\u2136$", "^\\u2135.\\u2136$"},
64 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(string_escape_cases); ++i) {
66 std::string in(string_escape_cases[i].to_escape);
67 EXPECT_TRUE(Sandbox::QuotePlainString(in, &out));
68 EXPECT_EQ(string_escape_cases[i].escaped, out);
72 TEST_F(MacDirAccessSandboxTest, RegexEscape) {
73 const std::string kSandboxEscapeSuffix("(/|$)");
74 const struct regex_test_data {
75 const wchar_t *to_escape;
79 {L"/'", "/'"}, // / & ' characters don't need escaping.
80 {L"sandwich", "sandwich"},
81 {L"(sandwich)", "\\(sandwich\\)"},
84 // Check that all characters whose values are smaller than 32 [1F] are
85 // rejected by the regex escaping code.
88 char fail_string[] = {31, 0};
89 char ok_string[] = {32, 0};
90 EXPECT_FALSE(Sandbox::QuoteStringForRegex(fail_string, &out));
91 EXPECT_TRUE(Sandbox::QuoteStringForRegex(ok_string, &out));
94 // Check that all characters whose values are larger than 126 [7E] are
95 // rejected by the regex escaping code.
98 EXPECT_TRUE(Sandbox::QuoteStringForRegex("}", &out)); // } == 0x7D == 125
99 EXPECT_FALSE(Sandbox::QuoteStringForRegex("~", &out)); // ~ == 0x7E == 126
101 Sandbox::QuoteStringForRegex(WideToUTF8(L"^\u2135.\u2136$"), &out));
105 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(regex_cases); ++i) {
107 std::string in = WideToUTF8(regex_cases[i].to_escape);
108 EXPECT_TRUE(Sandbox::QuoteStringForRegex(in, &out));
109 std::string expected("^");
110 expected.append(regex_cases[i].escaped);
111 expected.append(kSandboxEscapeSuffix);
112 EXPECT_EQ(expected, out);
117 std::string in_utf8("\\^.$|()[]*+?{}");
118 std::string expected;
119 expected.push_back('^');
120 for (size_t i = 0; i < in_utf8.length(); ++i) {
121 expected.push_back('\\');
122 expected.push_back(in_utf8[i]);
124 expected.append(kSandboxEscapeSuffix);
127 EXPECT_TRUE(Sandbox::QuoteStringForRegex(in_utf8, &out));
128 EXPECT_EQ(expected, out);
133 // A class to handle auto-deleting a directory.
134 class ScopedDirectoryDelete {
136 inline void operator()(FilePath* x) const {
138 file_util::Delete(*x, true);
143 typedef scoped_ptr_malloc<FilePath, ScopedDirectoryDelete> ScopedDirectory;
145 TEST_F(MacDirAccessSandboxTest, SandboxAccess) {
146 using file_util::CreateDirectory;
149 ASSERT_TRUE(file_util::CreateNewTempDirectory("", &tmp_dir));
150 // This step is important on OS X since the sandbox only understands "real"
151 // paths and the paths CreateNewTempDirectory() returns are empirically in
152 // /var which is a symlink to /private/var .
153 tmp_dir = Sandbox::GetCanonicalSandboxPath(tmp_dir);
154 ScopedDirectory cleanup(&tmp_dir);
156 const char* sandbox_dir_cases[] = {
158 "^hello++ $", // Regex.
159 "\\^.$|()[]*+?{}", // All regex characters.
162 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(sandbox_dir_cases); ++i) {
163 const char* sandbox_dir_name = sandbox_dir_cases[i];
164 FilePath sandbox_dir = tmp_dir.Append(sandbox_dir_name);
165 ASSERT_TRUE(CreateDirectory(sandbox_dir));
166 ScopedDirectory cleanup_sandbox(&sandbox_dir);
168 // Create a sibling directory of the sandbox dir, whose name has sandbox dir
169 // as a substring but to which access is denied.
170 std::string sibling_sandbox_dir_name_denied =
171 std::string(sandbox_dir_cases[i]) + kDeniedSuffix;
172 FilePath sibling_sandbox_dir = tmp_dir.Append(
173 sibling_sandbox_dir_name_denied.c_str());
174 ASSERT_TRUE(CreateDirectory(sibling_sandbox_dir));
175 ScopedDirectory cleanup_sandbox_sibling(&sibling_sandbox_dir);
177 EXPECT_TRUE(CheckSandbox(sandbox_dir.value()));
181 MULTIPROCESS_TEST_MAIN(mac_sandbox_path_access) {
182 char *sandbox_allowed_dir = getenv(kSandboxAccessPathKey);
183 if (!sandbox_allowed_dir)
186 // Build up a sandbox profile that only allows access to a single directory.
187 NSString *sandbox_profile =
190 "(allow signal (target self))" \
191 "(allow sysctl-read)" \
192 ";ENABLE_DIRECTORY_ACCESS";
194 std::string allowed_dir(sandbox_allowed_dir);
195 Sandbox::SandboxVariableSubstitions substitutions;
196 NSString* allow_dir_sandbox_code =
197 Sandbox::BuildAllowDirectoryAccessSandboxString(
198 FilePath(sandbox_allowed_dir),
200 sandbox_profile = [sandbox_profile
201 stringByReplacingOccurrencesOfString:@";ENABLE_DIRECTORY_ACCESS"
202 withString:allow_dir_sandbox_code];
204 std::string final_sandbox_profile_str;
205 if (!Sandbox::PostProcessSandboxProfile(sandbox_profile,
208 &final_sandbox_profile_str)) {
209 LOG(ERROR) << "Call to PostProcessSandboxProfile() failed";
214 char* error_buff = NULL;
215 int error = sandbox_init(final_sandbox_profile_str.c_str(), 0, &error_buff);
217 LOG(ERROR) << "Failed to Initialize Sandbox: " << error_buff;
220 sandbox_free_error(error_buff);
224 // We should be able to list the contents of the sandboxed directory.
225 DIR *file_list = NULL;
226 file_list = opendir(sandbox_allowed_dir);
228 PLOG(ERROR) << "Sandbox overly restrictive: call to opendir("
229 << sandbox_allowed_dir
235 // Test restrictions on accessing files.
236 FilePath allowed_dir_path(sandbox_allowed_dir);
237 FilePath allowed_file = allowed_dir_path.Append("ok_to_write");
238 FilePath denied_file1 = allowed_dir_path.DirName().Append("cant_access");
240 // Try to write a file who's name has the same prefix as the directory we
242 FilePath basename = allowed_dir_path.BaseName();
243 FilePath allowed_parent_dir = allowed_dir_path.DirName();
244 std::string tricky_filename = basename.value() + "123";
245 FilePath denied_file2 = allowed_parent_dir.Append(tricky_filename);
247 if (open(allowed_file.value().c_str(), O_WRONLY | O_CREAT) <= 0) {
248 PLOG(ERROR) << "Sandbox overly restrictive: failed to write ("
249 << allowed_file.value()
254 // Test that we deny access to a sibling of the sandboxed directory whose
255 // name has the sandboxed directory name as a substring. e.g. if the sandbox
256 // directory is /foo/baz then test /foo/baz_denied.
258 struct stat tmp_stat_info;
259 std::string denied_sibling =
260 std::string(sandbox_allowed_dir) + kDeniedSuffix;
261 if (stat(denied_sibling.c_str(), &tmp_stat_info) > 0) {
262 PLOG(ERROR) << "Sandbox breach: was able to stat ("
263 << denied_sibling.c_str()
269 // Test that we can stat parent directories of the "allowed" directory.
271 struct stat tmp_stat_info;
272 if (stat(allowed_parent_dir.value().c_str(), &tmp_stat_info) != 0) {
273 PLOG(ERROR) << "Sandbox overly restrictive: unable to stat ("
274 << allowed_parent_dir.value()
280 // Test that we can't stat files outside the "allowed" directory.
282 struct stat tmp_stat_info;
283 if (stat(denied_file1.value().c_str(), &tmp_stat_info) > 0) {
284 PLOG(ERROR) << "Sandbox breach: was able to stat ("
285 << denied_file1.value()
291 if (open(denied_file1.value().c_str(), O_WRONLY | O_CREAT) > 0) {
292 PLOG(ERROR) << "Sandbox breach: was able to write ("
293 << denied_file1.value()
298 if (open(denied_file2.value().c_str(), O_WRONLY | O_CREAT) > 0) {
299 PLOG(ERROR) << "Sandbox breach: was able to write ("
300 << denied_file2.value()
308 } // namespace content