diff options
-rw-r--r-- | chrome/chrome_tests.gypi | 3 | ||||
-rw-r--r-- | chrome/common/sandbox_mac.h | 7 | ||||
-rw-r--r-- | chrome/common/sandbox_mac_system_access_unittest.mm | 96 | ||||
-rw-r--r-- | chrome/common/sandbox_mac_unittest.mm | 8 | ||||
-rw-r--r-- | chrome/common/sandbox_mac_unittest_helper.h | 113 | ||||
-rw-r--r-- | chrome/common/sandbox_mac_unittest_helper.mm | 141 |
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 |