summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/chrome_tests.gypi3
-rw-r--r--chrome/common/sandbox_mac.h7
-rw-r--r--chrome/common/sandbox_mac_system_access_unittest.mm96
-rw-r--r--chrome/common/sandbox_mac_unittest.mm8
-rw-r--r--chrome/common/sandbox_mac_unittest_helper.h113
-rw-r--r--chrome/common/sandbox_mac_unittest_helper.mm141
6 files changed, 363 insertions, 5 deletions
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index e22f0d5..836dbcf 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -992,6 +992,9 @@
'common/render_messages_unittest.cc',
'common/resource_dispatcher_unittest.cc',
'common/sandbox_mac_unittest.mm',
+ 'common/sandbox_mac_unittest_helper.h',
+ 'common/sandbox_mac_unittest_helper.mm',
+ 'common/sandbox_mac_system_access_unittest.mm',
'common/thumbnail_score_unittest.cc',
'common/time_format_unittest.cc',
'common/worker_thread_ticker_unittest.cc',
diff --git a/chrome/common/sandbox_mac.h b/chrome/common/sandbox_mac.h
index c8ef4c3..8065298 100644
--- a/chrome/common/sandbox_mac.h
+++ b/chrome/common/sandbox_mac.h
@@ -10,7 +10,10 @@
namespace sandbox {
enum SandboxProcessType {
- SANDBOX_TYPE_RENDERER,
+
+ SANDBOX_TYPE_FIRST_TYPE, // Placeholder to ease iteration.
+
+ SANDBOX_TYPE_RENDERER = SANDBOX_TYPE_FIRST_TYPE,
// The worker processes uses the most restrictive sandbox which has almost
// *everything* locked down. Only a couple of /System/Library/ paths and
@@ -26,6 +29,8 @@ enum SandboxProcessType {
// loader contains the user's untrusted code.
SANDBOX_TYPE_NACL_PLUGIN,
SANDBOX_TYPE_NACL_LOADER,
+
+ SANDBOX_AFTER_TYPE_LAST_TYPE, // Placeholder to ease iteration.
};
// Warm up System APIs that empirically need to be accessed before the Sandbox
diff --git a/chrome/common/sandbox_mac_system_access_unittest.mm b/chrome/common/sandbox_mac_system_access_unittest.mm
new file mode 100644
index 0000000..3ae41d7
--- /dev/null
+++ b/chrome/common/sandbox_mac_system_access_unittest.mm
@@ -0,0 +1,96 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/logging.h"
+#include "base/sys_string_conversions.h"
+#include "chrome/common/sandbox_mac.h"
+#include "chrome/common/sandbox_mac_unittest_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using sandboxtest::MacSandboxTest;
+
+//--------------------- Clipboard Sandboxing ----------------------
+// Test case for checking sandboxing of clipboard access.
+class MacSandboxedClipboardTestCase : public sandboxtest::MacSandboxTestCase {
+ public:
+ MacSandboxedClipboardTestCase();
+ virtual ~MacSandboxedClipboardTestCase();
+
+ virtual bool SandboxedTest();
+
+ virtual void SetTestData(const char* test_data);
+ private:
+ NSString* clipboard_name_;
+};
+
+REGISTER_SANDBOX_TEST_CASE(MacSandboxedClipboardTestCase);
+
+MacSandboxedClipboardTestCase::MacSandboxedClipboardTestCase() :
+ clipboard_name_(nil) {}
+
+MacSandboxedClipboardTestCase::~MacSandboxedClipboardTestCase() {
+ [clipboard_name_ release];
+}
+
+bool MacSandboxedClipboardTestCase::SandboxedTest() {
+ // Shouldn't be able to open the pasteboard in the sandbox.
+
+ if ([clipboard_name_ length] == 0) {
+ LOG(ERROR) << "Clipboard name is empty";
+ return false;
+ }
+
+ NSPasteboard* pb = [NSPasteboard pasteboardWithName:clipboard_name_];
+ if (pb != nil) {
+ LOG(ERROR) << "Was able to access named clipboard";
+ return false;
+ }
+
+ pb = [NSPasteboard generalPasteboard];
+ if (pb != nil) {
+ LOG(ERROR) << "Was able to access system clipboard";
+ return false;
+ }
+
+ return true;
+}
+
+void MacSandboxedClipboardTestCase::SetTestData(const char* test_data) {
+ clipboard_name_ = [base::SysUTF8ToNSString(test_data) retain];
+}
+
+TEST_F(MacSandboxTest, ClipboardAccess) {
+ NSPasteboard* pb = [NSPasteboard pasteboardWithUniqueName];
+ EXPECT_EQ([[pb types] count], 0U);
+
+ std::string pasteboard_name = base::SysNSStringToUTF8([pb name]);
+ EXPECT_TRUE(RunTestInAllSandboxTypes("MacSandboxedClipboardTestCase",
+ pasteboard_name.c_str()));
+
+ // After executing the test, the clipboard should still be empty.
+ EXPECT_EQ([[pb types] count], 0U);
+}
+
+//--------------------- File Access Sandboxing ----------------------
+// Test case for checking sandboxing of filesystem apis.
+class MacSandboxedFileAccessTestCase : public sandboxtest::MacSandboxTestCase {
+ public:
+ virtual bool SandboxedTest();
+};
+
+REGISTER_SANDBOX_TEST_CASE(MacSandboxedFileAccessTestCase);
+
+bool MacSandboxedFileAccessTestCase::SandboxedTest() {
+ return open("/etc/passwd", O_RDONLY) == -1;
+}
+
+TEST_F(MacSandboxTest, FileAccess) {
+ EXPECT_TRUE(RunTestInAllSandboxTypes("MacSandboxedFileAccessTestCase", NULL));
+}
+
+} // namespace
diff --git a/chrome/common/sandbox_mac_unittest.mm b/chrome/common/sandbox_mac_unittest.mm
index 0adcc6d..dcd9814 100644
--- a/chrome/common/sandbox_mac_unittest.mm
+++ b/chrome/common/sandbox_mac_unittest.mm
@@ -26,7 +26,7 @@ bool QuoteStringForRegex(const std::string& str_utf8, std::string* dst);
static const char* kSandboxAccessPathKey = "sandbox_dir";
-class MacSandboxTest : public MultiProcessTest {
+class MacDirAccessSandboxTest : public MultiProcessTest {
public:
bool CheckSandbox(std::string directory_to_try) {
setenv(kSandboxAccessPathKey, directory_to_try.c_str(), 1);
@@ -40,7 +40,7 @@ class MacSandboxTest : public MultiProcessTest {
}
};
-TEST_F(MacSandboxTest, StringEscape) {
+TEST_F(MacDirAccessSandboxTest, StringEscape) {
using sandbox::QuotePlainString;
const struct string_escape_test_data {
@@ -63,7 +63,7 @@ TEST_F(MacSandboxTest, StringEscape) {
}
}
-TEST_F(MacSandboxTest, RegexEscape) {
+TEST_F(MacDirAccessSandboxTest, RegexEscape) {
using sandbox::QuoteStringForRegex;
const std::string kSandboxEscapeSuffix("(/|$)");
@@ -137,7 +137,7 @@ class ScopedDirectoryDelete {
typedef scoped_ptr_malloc<FilePath, ScopedDirectoryDelete> ScopedDirectory;
-TEST_F(MacSandboxTest, SandboxAccess) {
+TEST_F(MacDirAccessSandboxTest, SandboxAccess) {
FilePath tmp_dir;
ASSERT_TRUE(file_util::CreateNewTempDirectory("", &tmp_dir));
// This step is important on OS X since the sandbox only understands "real"
diff --git a/chrome/common/sandbox_mac_unittest_helper.h b/chrome/common/sandbox_mac_unittest_helper.h
new file mode 100644
index 0000000..533b132
--- /dev/null
+++ b/chrome/common/sandbox_mac_unittest_helper.h
@@ -0,0 +1,113 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_SANDBOX_MAC_UNITTEST_RUNNER_H_
+#define CHROME_COMMON_SANDBOX_MAC_UNITTEST_RUNNER_H_
+
+#include "base/multiprocess_test.h"
+#include "chrome/common/sandbox_mac.h"
+
+namespace sandboxtest {
+
+// Helpers for writing unit tests that runs in the context of the Mac sandbox.
+//
+// How to write a sandboxed test:
+// 1. Create a class that inherits from MacSandboxTestCase and overrides
+// it's functions to run code before or after the sandbox is initialised in a
+// subprocess.
+// 2. Register the class you just created with the REGISTER_SANDBOX_TEST_CASE()
+// macro.
+// 3. Write a test [using TEST_F()] that inherits from MacSandboxTest and call
+// one of it's helper functions to launch the test.
+//
+// Example:
+// class TestCaseThatRunsInSandboxedSubprocess :
+// public sandboxtest::MacSandboxTestCase {
+// public:
+// virtual bool SandboxedTest() {
+// .. test code that runs in sandbox goes here ..
+// return true; // always succeed.
+// }
+// };
+//
+// // Register the test case you just created.
+// REGISTER_SANDBOX_TEST_CASE(TestCaseThatRunsInSandboxedSubprocess);
+//
+// TEST_F(MacSandboxTest, ATest) {
+// EXPECT_TRUE(RunTestInAllSandboxTypes(
+// "TestCaseThatRunsInSandboxedSubprocess",
+// NULL));
+// }
+
+// Base test type with helper functions to spawn a subprocess that exercises
+// a given test in the sandbox.
+class MacSandboxTest : public MultiProcessTest {
+ public:
+ // Runs a test specified by |test_name| in a sandbox of the type specified
+ // by |sandbox_type|. |test_data| is a custom string that a test can pass
+ // to the child process runing in the sandbox.
+ // Returns true if the test passes, false if either of the functions in
+ // the corresponding MacSandboxTestCase return false.
+ bool RunTestInSandbox(sandbox::SandboxProcessType sandbox_type,
+ const char* test_name,
+ const char* test_data);
+
+ // Runs the test specified by |test_name| in all the different sandbox types,
+ // one by one.
+ // Returns true if the test passes, false if either of the functions in
+ // the corresponding MacSandboxTestCase return false in any of the spawned
+ // processes.
+ bool RunTestInAllSandboxTypes(const char* test_name,
+ const char* test_data);
+};
+
+// Class to ease writing test cases that run inside the OS X sandbox.
+// This class is instantiated in a subprocess, and allows you to run test code
+// at various stages of execution.
+// Note that you must register the subclass you create with the
+// REGISTER_SANDBOX_TEST_CASE so it's visible to the test driver.
+class MacSandboxTestCase {
+ public:
+ // Code that runs in the sandboxed subprocess before the sandbox is
+ // initialized.
+ // Returning false from this function will cause the entire test case to fail.
+ virtual bool BeforeSandboxInit() { return true; };
+
+ // Code that runs in the sandboxed subprocess when the sandbox has been
+ // enabled.
+ // Returning false from this function will cause the entire test case to fail.
+ virtual bool SandboxedTest() = 0;
+
+ // The data that's passed in the |user_data| parameter of
+ // RunTest[s]InSandbox() is passed to this function.
+ virtual void SetTestData(const char* test_data) {}
+};
+
+// Plumbing to support the REGISTER_SANDBOX_TEST_CASE macro.
+namespace internal {
+
+typedef std::map<std::string,MacSandboxTestCase*> SandboxTestMap;
+
+// A function that returns a common map from string -> test case class.
+SandboxTestMap& GetSandboxTestMap();
+
+// Construction of this class causes a new entry to be placed in a global
+// map.
+template <class T> struct RegisterSandboxTest {
+ RegisterSandboxTest(const char* test_name) {
+ GetSandboxTestMap()[test_name] = new T;
+ }
+};
+
+#define REGISTER_SANDBOX_TEST_CASE(class_name) \
+ namespace { \
+ sandboxtest::internal::RegisterSandboxTest<class_name> \
+ register_test##class_name(#class_name); \
+ } // namespace
+
+} // namespace internal
+
+} // namespace sandboxtest
+
+#endif // CHROME_COMMON_SANDBOX_MAC_UNITTEST_RUNNER_H_
diff --git a/chrome/common/sandbox_mac_unittest_helper.mm b/chrome/common/sandbox_mac_unittest_helper.mm
new file mode 100644
index 0000000..749bcc1
--- /dev/null
+++ b/chrome/common/sandbox_mac_unittest_helper.mm
@@ -0,0 +1,141 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/sandbox_mac_unittest_helper.h"
+
+extern "C" {
+#include <sandbox.h>
+}
+
+#include "base/scoped_ptr.h"
+#include "chrome/common/sandbox_mac.h"
+
+namespace {
+
+const char* kSandboxTypeKey = "CHROMIUM_SANDBOX_SANDBOX_TYPE";
+const char* kSandboxTestNameKey = "CHROMIUM_SANDBOX_TEST_NAME";
+const char* kTestDataKey = "CHROMIUM_SANDBOX_USER_DATA";
+
+} // namespace
+
+namespace sandboxtest {
+
+// Support infrastructure for REGISTER_SANDBOX_TEST_CASE macro.
+namespace internal {
+
+SandboxTestMap& GetSandboxTestMap() {
+ static SandboxTestMap test_map;
+ return test_map;
+}
+
+} // namespace internal
+
+bool MacSandboxTest:: RunTestInAllSandboxTypes(const char* test_name,
+ const char* test_data) {
+ // Go through all the sandbox types, and run the test case in each of them
+ // if one fails, abort.
+ for(int i = static_cast<int>(sandbox::SANDBOX_TYPE_FIRST_TYPE);
+ i < sandbox::SANDBOX_AFTER_TYPE_LAST_TYPE;
+ ++i) {
+ if (!RunTestInSandbox(static_cast<sandbox::SandboxProcessType>(i),
+ test_name, test_data)) {
+ LOG(ERROR) << "Sandboxed test (" << test_name << ")" <<
+ "Failed in sandbox type " << i <<
+ "user data: (" << test_data << ")";
+ return false;
+ }
+ }
+ return true;
+}
+
+bool MacSandboxTest::RunTestInSandbox(sandbox::SandboxProcessType sandbox_type,
+ const char* test_name,
+ const char* test_data) {
+ std::stringstream s;
+ s << static_cast<int>(static_cast<int>(sandbox_type));
+ setenv(kSandboxTypeKey, s.str().c_str(), 1);
+ setenv(kSandboxTestNameKey, test_name, 1);
+ if (test_data)
+ setenv(kTestDataKey, test_data, 1);
+
+ base::ProcessHandle child_process = SpawnChild(L"mac_sandbox_test_runner");
+ int code = -1;
+ if (!base::WaitForExitCode(child_process, &code)) {
+ LOG(WARNING) << "base::WaitForExitCode failed";
+ return false;
+ }
+ return code == 0;
+}
+
+// Given a test name specified by |name| return that test case.
+// If no test case is found for the given name, return NULL.
+MacSandboxTestCase *SandboxTestForName(const char* name) {
+ using internal::SandboxTestMap;
+ using internal::GetSandboxTestMap;
+
+ SandboxTestMap all_tests = GetSandboxTestMap();
+
+ SandboxTestMap::iterator it = all_tests.find(name);
+ if (it == all_tests.end()) {
+ LOG(ERROR) << "Couldn't find sandbox test case(" << name << ")";
+ return NULL;
+ }
+
+ return it->second;
+}
+
+} // namespace sandboxtest
+
+namespace {
+
+// Main function for driver process that enables the sandbox and runs test
+// code.
+MULTIPROCESS_TEST_MAIN(mac_sandbox_test_runner) {
+ // Extract parameters.
+ char* sandbox_type_str = getenv(kSandboxTypeKey);
+ if (!sandbox_type_str) {
+ LOG(ERROR) << "Sandbox type not specified";
+ return -1;
+ }
+ sandbox::SandboxProcessType sandbox_type =
+ static_cast<sandbox::SandboxProcessType>(atoi(sandbox_type_str));
+ char* sandbox_test_name = getenv(kSandboxTestNameKey);
+ if (!sandbox_test_name) {
+ LOG(ERROR) << "Sandbox test name not specified";
+ return -1;
+ }
+
+ const char* test_data = getenv(kTestDataKey);
+
+ // Find Test Function to run;
+ scoped_ptr<sandboxtest::MacSandboxTestCase>
+ test_case(sandboxtest::SandboxTestForName(sandbox_test_name));
+ if (!test_case.get()) {
+ LOG(ERROR) << "Invalid sandbox test name (" << sandbox_test_name << ")";
+ return -1;
+ }
+ test_case->SetTestData(test_data);
+
+ // Run Test.
+ if (!test_case->BeforeSandboxInit()) {
+ LOG(ERROR) << sandbox_test_name << "Failed test before sandbox init";
+ return -1;
+ }
+
+ sandbox::SandboxWarmup();
+
+ if (!sandbox::EnableSandbox(sandbox_type, FilePath())) {
+ LOG(ERROR) << "Failed to initialize sandbox " << sandbox_type;
+ return -1;
+ }
+
+ if (!test_case->SandboxedTest()) {
+ LOG(ERROR) << sandbox_test_name << "Failed sandboxed test";
+ return -1;
+ }
+
+ return 0;
+}
+
+} // namespace