diff options
author | jeremy@chromium.org <jeremy@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-03 15:36:08 +0000 |
---|---|---|
committer | jeremy@chromium.org <jeremy@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-03 15:36:08 +0000 |
commit | 120be5d1b6455f4a97eaf560d12f0f78c8b1a107 (patch) | |
tree | ee6b8b128d227780501617474e76b73601d01bfe /chrome | |
parent | 780ae94178769a226f9e73f3776412edea60f850 (diff) | |
download | chromium_src-120be5d1b6455f4a97eaf560d12f0f78c8b1a107.zip chromium_src-120be5d1b6455f4a97eaf560d12f0f78c8b1a107.tar.gz chromium_src-120be5d1b6455f4a97eaf560d12f0f78c8b1a107.tar.bz2 |
Add regex escaping code to Mac sandbox implementation and re-enable the utility process on OS X.
Other changes:
* An error initializing the sandbox on OS X is now treated as fatal.
* Improved error reporting for sandbox-related failures.
BUG=26492,23837
TEST=Installing extensions and themes should still work on OS X.
Review URL: http://codereview.chromium.org/434077
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@33682 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/app/chrome_dll_main.cc | 9 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_updater.cc | 5 | ||||
-rw-r--r-- | chrome/browser/extensions/sandboxed_extension_unpacker.cc | 5 | ||||
-rw-r--r-- | chrome/browser/utility.sb | 4 | ||||
-rw-r--r-- | chrome/browser/utility_process_host.cc | 4 | ||||
-rw-r--r-- | chrome/browser/web_resource/web_resource_service.cc | 5 | ||||
-rwxr-xr-x | chrome/chrome_tests.gypi | 1 | ||||
-rw-r--r-- | chrome/common/sandbox_mac.mm | 206 | ||||
-rw-r--r-- | chrome/common/sandbox_mac_unittest.mm | 244 |
9 files changed, 452 insertions, 31 deletions
diff --git a/chrome/app/chrome_dll_main.cc b/chrome/app/chrome_dll_main.cc index 2217c8e..07ccf6d 100644 --- a/chrome/app/chrome_dll_main.cc +++ b/chrome/app/chrome_dll_main.cc @@ -554,8 +554,13 @@ int ChromeMain(int argc, char** argv) { // On OS X the renderer sandbox needs to be initialized later in the startup // sequence in RendererMainPlatformDelegate::PlatformInitialize(). if (process_type != switches::kRendererProcess && - process_type != switches::kExtensionProcess) - sandbox_wrapper.InitializeSandbox(parsed_command_line, process_type); + process_type != switches::kExtensionProcess) { + bool sandbox_initialized_ok = + sandbox_wrapper.InitializeSandbox(parsed_command_line, process_type); + // Die if the sandbox can't be enabled. + CHECK(sandbox_initialized_ok) << "Error initializing sandbox for " + << process_type; + } #endif // OS_MACOSX startup_timer.Stop(); // End of Startup Time Measurement. diff --git a/chrome/browser/extensions/extension_updater.cc b/chrome/browser/extensions/extension_updater.cc index a618de9..f79dc7d 100644 --- a/chrome/browser/extensions/extension_updater.cc +++ b/chrome/browser/extensions/extension_updater.cc @@ -269,9 +269,8 @@ class SafeManifestParser : public UtilityProcessHost::Client { bool use_utility_process = rdh && !CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess); -#if defined(OS_POSIX) - // TODO(port): Don't use a utility process on linux (crbug.com/22703) or - // MacOS (crbug.com/8102) until problems related to autoupdate are fixed. +#if defined(OS_LINUX) + // TODO(port): Don't use a utility process on linux (crbug.com/22703). use_utility_process = false; #endif diff --git a/chrome/browser/extensions/sandboxed_extension_unpacker.cc b/chrome/browser/extensions/sandboxed_extension_unpacker.cc index 46167ad..a3ece10 100644 --- a/chrome/browser/extensions/sandboxed_extension_unpacker.cc +++ b/chrome/browser/extensions/sandboxed_extension_unpacker.cc @@ -65,9 +65,8 @@ void SandboxedExtensionUnpacker::Start() { bool use_utility_process = rdh_ && !CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess); -#if defined(OS_POSIX) - // TODO(port): Don't use a utility process on linux (crbug.com/22703) or - // MacOS (crbug.com/8102) until problems related to autoupdate are fixed. +#if defined(OS_LINUX) + // TODO(port): Don't use a utility process on linux (crbug.com/22703). use_utility_process = false; #endif diff --git a/chrome/browser/utility.sb b/chrome/browser/utility.sb index 291e677..3f88f1cb 100644 --- a/chrome/browser/utility.sb +++ b/chrome/browser/utility.sb @@ -36,5 +36,5 @@ ; Needed for IPC on 10.6 ;10.6_ONLY (allow ipc-posix-shm) -; Enable full access to given directory. -(allow file-read* file-write* (regex #"^DIR_TO_ALLOW_ACCESS")) +; Enable full access to given directory if needed. +;ENABLE_DIRECTORY_ACCESS (allow file-read* file-write* (regex #"DIR_TO_ALLOW_ACCESS")) diff --git a/chrome/browser/utility_process_host.cc b/chrome/browser/utility_process_host.cc index 9f7d647..352425a 100644 --- a/chrome/browser/utility_process_host.cc +++ b/chrome/browser/utility_process_host.cc @@ -55,9 +55,9 @@ FilePath UtilityProcessHost::GetUtilityProcessCmd() { } bool UtilityProcessHost::StartProcess(const FilePath& exposed_dir) { -#if defined(OS_POSIX) +#if defined(OS_LINUX) // TODO(port): We should not reach here on Linux (crbug.com/22703). - // (crbug.com/23837) covers enabling this on Linux/OS X. + // (crbug.com/23837) covers enabling this on Linux. NOTREACHED(); return false; #endif diff --git a/chrome/browser/web_resource/web_resource_service.cc b/chrome/browser/web_resource/web_resource_service.cc index c4ffa47..3d213f1 100644 --- a/chrome/browser/web_resource/web_resource_service.cc +++ b/chrome/browser/web_resource/web_resource_service.cc @@ -111,9 +111,8 @@ class WebResourceService::UnpackerClient web_resource_service_->resource_dispatcher_host_ != NULL && !CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess); -#if defined(OS_POSIX) - // TODO(port): Don't use a utility process on linux (crbug.com/22703) or - // MacOS (crbug.com/8102) until problems related to autoupdate are fixed. +#if defined(OS_LINUX) + // TODO(port): Don't use a utility process on linux (crbug.com/22703). use_utility_process = false; #endif diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 63b7847..46d4a15 100755 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -759,6 +759,7 @@ 'common/pref_service_unittest.cc', 'common/property_bag_unittest.cc', 'common/resource_dispatcher_unittest.cc', + 'common/sandbox_mac_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.mm b/chrome/common/sandbox_mac.mm index 6eac34c..ca8ac6c 100644 --- a/chrome/common/sandbox_mac.mm +++ b/chrome/common/sandbox_mac.mm @@ -13,17 +13,170 @@ extern "C" { #include "base/basictypes.h" #include "base/command_line.h" -#include "base/json/string_escape.h" +#include "base/file_util.h" #include "base/mac_util.h" #include "base/scoped_cftyperef.h" #include "base/scoped_nsautorelease_pool.h" #include "base/string16.h" #include "base/sys_info.h" #include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" #include "chrome/common/chrome_switches.h" +#include "unicode/uchar.h" + +namespace { + +// Try to escape |c| as a "SingleEscapeCharacter" (\n, etc). If successful, +// returns true and appends the escape sequence to |dst|. +bool EscapeSingleChar(char c, std::string* dst) { + const char *append = NULL; + switch (c) { + case '\b': + append = "\\b"; + break; + case '\f': + append = "\\f"; + break; + case '\n': + append = "\\n"; + break; + case '\r': + append = "\\r"; + break; + case '\t': + append = "\\t"; + break; + case '\\': + append = "\\\\"; + break; + case '"': + append = "\\\""; + break; + } + + if (!append) { + return false; + } + + dst->append(append); + return true; +} + +} // namespace namespace sandbox { +// Escape |str_utf8| for use in a plain string variable in a sandbox +// configuraton file. On return |dst| is set to the utf-8 encoded quoted +// output. +// Returns: true on success, false otherwise. +bool QuotePlainString(const std::string& str_utf8, std::string* dst) { + dst->clear(); + + const char* src = str_utf8.c_str(); + int32_t length = str_utf8.length(); + int32_t position = 0; + while (position < length) { + UChar32 c; + U8_NEXT(src, position, length, c); // Macro increments |position|. + DCHECK_GE(c, 0); + if (c < 0) + return false; + + if (c < 128) { // EscapeSingleChar only handles ASCII. + char as_char = static_cast<char>(c); + if (EscapeSingleChar(as_char, dst)) { + continue; + } + } + + if (c < 32 || c > 126) { + // Any characters that aren't printable ASCII get the \u treatment. + unsigned int as_uint = static_cast<unsigned int>(c); + StringAppendF(dst, "\\u%04X", as_uint); + continue; + } + + // If we got here we know that the character in question is strictly + // in the ASCII range so there's no need to do any kind of encoding + // conversion. + dst->push_back(static_cast<char>(c)); + } + return true; +} + +// Escape |str_utf8| for use in a regex literal in a sandbox +// configuraton file. On return |dst| is set to the utf-8 encoded quoted +// output. +// +// The implementation of this function is based on empirical testing of the +// OS X sandbox on 10.5.8 & 10.6.2 which is undocumented and subject to change. +// +// Note: If str_utf8 contains any characters < 32 || >125 then the function +// fails and false is returned. +// +// Returns: true on success, false otherwise. +bool QuoteStringForRegex(const std::string& str_utf8, std::string* dst) { + // List of chars with special meaning to regex. + // This list is derived from http://perldoc.perl.org/perlre.html . + const char regex_special_chars[] = { + '\\', + + // Metacharacters + '^', + '.', + '$', + '|', + '(', + ')', + '[', + ']', + + // Quantifiers + '*', + '+', + '?', + '{', + '}', + }; + + // Anchor regex at start of path. + dst->assign("^"); + + const char* src = str_utf8.c_str(); + int32_t length = str_utf8.length(); + int32_t position = 0; + while (position < length) { + UChar32 c; + U8_NEXT(src, position, length, c); // Macro increments |position|. + DCHECK_GE(c, 0); + if (c < 0) + return false; + + // The Mac sandbox regex parser only handles printable ASCII characters. + // 33 >= c <= 126 + if (c < 32 || c > 125) { + return false; + } + + for (size_t i = 0; i < arraysize(regex_special_chars); ++i) { + if (c == regex_special_chars[i]) { + dst->push_back('\\'); + break; + } + } + + dst->push_back(static_cast<char>(c)); + } + + // Make sure last element of path is interpreted as a directory. Leaving this + // off would allow access to files if they start with the same name as the + // directory. + dst->append("(/|$)"); + + return true; +} + // Warm up System APIs that empirically need to be accessed before the Sandbox // is turned on. // This method is layed out in blocks, each one containing a separate function @@ -91,12 +244,7 @@ bool EnableSandbox(SandboxProcessType sandbox_type, if (sandbox_type != SANDBOX_TYPE_UTILITY) { DCHECK(allowed_dir.empty()) << "Only SANDBOX_TYPE_UTILITY allows a custom directory parameter."; - } else { - DCHECK(!allowed_dir.empty()) - << "SANDBOX_TYPE_UTILITY " - << "needs a custom directory parameter, but an empty one was provided."; } - // We use a custom sandbox definition file to lock things down as // tightly as possible. // TODO(jeremy): Look at using include syntax to unify common parts of sandbox @@ -122,11 +270,12 @@ bool EnableSandbox(SandboxProcessType sandbox_type, ofType:@"sb"]; NSString* sandbox_data = [NSString stringWithContentsOfFile:sandbox_profile_path - encoding:NSUTF8StringEncoding - error:nil]; + encoding:NSUTF8StringEncoding + error:nil]; if (!sandbox_data) { - LOG(ERROR) << "Failed to find the sandbox profile on disk"; + PLOG(ERROR) << "Failed to find the sandbox profile on disk " + << base::SysNSStringToUTF8(sandbox_profile_path); return false; } @@ -140,10 +289,31 @@ bool EnableSandbox(SandboxProcessType sandbox_type, } if (!allowed_dir.empty()) { - NSString* allowed_dir_ns = base::SysUTF8ToNSString(allowed_dir.value()); + // The sandbox only understands "real" paths. This resolving step is + // needed so the caller doesn't need to worry about things like /var + // being a link to /private/var (like in the paths CreateNewTempDirectory() + // returns). + FilePath allowed_dir_absolute(allowed_dir); + if (!file_util::AbsolutePath(&allowed_dir_absolute)) { + PLOG(ERROR) << "Failed to resolve absolute path"; + return false; + } + + std::string allowed_dir_escaped; + if (!QuoteStringForRegex(allowed_dir_absolute.value(), + &allowed_dir_escaped)) { + LOG(ERROR) << "Regex string quoting failed " << allowed_dir.value(); + return false; + } + NSString* allowed_dir_escaped_ns = base::SysUTF8ToNSString( + allowed_dir_escaped.c_str()); + sandbox_data = [sandbox_data + stringByReplacingOccurrencesOfString:@";ENABLE_DIRECTORY_ACCESS" + withString:@""]; sandbox_data = [sandbox_data stringByReplacingOccurrencesOfString:@"DIR_TO_ALLOW_ACCESS" - withString:allowed_dir_ns]; + withString:allowed_dir_escaped_ns]; + } int32 major_version, minor_version, bugfix_version; @@ -161,9 +331,12 @@ bool EnableSandbox(SandboxProcessType sandbox_type, // for this "subdir" is only supported on 10.6. // If we ever need this on pre-10.6 OSs then we'll have to rethink the // surrounding sandbox syntax. - string16 home_dir = base::SysNSStringToUTF16(NSHomeDirectory()); + std::string home_dir = base::SysNSStringToUTF8(NSHomeDirectory()); std::string home_dir_escaped; - base::JsonDoubleQuote(home_dir, false, &home_dir_escaped); + if (!QuotePlainString(home_dir, &home_dir_escaped)) { + LOG(ERROR) << "Sandbox string quoting failed"; + return false; + } NSString* home_dir_escaped_ns = base::SysUTF8ToNSString(home_dir_escaped); sandbox_data = [sandbox_data stringByReplacingOccurrencesOfString:@"USER_HOMEDIR" @@ -173,9 +346,10 @@ bool EnableSandbox(SandboxProcessType sandbox_type, char* error_buff = NULL; int error = sandbox_init([sandbox_data UTF8String], 0, &error_buff); bool success = (error == 0 && error_buff == NULL); - if (error == -1) { - LOG(ERROR) << "Failed to Initialize Sandbox: " << error_buff; - } + LOG_IF(ERROR, !success) << "Failed to initialize sandbox: " + << error + << " " + << error_buff; sandbox_free_error(error_buff); return success; } diff --git a/chrome/common/sandbox_mac_unittest.mm b/chrome/common/sandbox_mac_unittest.mm new file mode 100644 index 0000000..6f469f1 --- /dev/null +++ b/chrome/common/sandbox_mac_unittest.mm @@ -0,0 +1,244 @@ +// Copyright (c) 2009 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 <dirent.h> + +extern "C" { +#include <sandbox.h> +} + +#include "base/file_util.h" +#include "base/file_path.h" +#include "base/multiprocess_test.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "chrome/common/sandbox_mac.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +bool QuotePlainString(const std::string& str_utf8, std::string* dst); +bool QuoteStringForRegex(const std::string& str_utf8, std::string* dst); + +} // namespace sandbox + +static const char* kSandboxAccessPathKey = "sandbox_dir"; + +class MacSandboxTest : public MultiProcessTest { + public: + bool CheckSandbox(std::string directory_to_try) { + setenv(kSandboxAccessPathKey, directory_to_try.c_str(), 1); + base::ProcessHandle child_process = SpawnChild(L"mac_sandbox_path_access"); + int code = -1; + if (!base::WaitForExitCode(child_process, &code)) { + LOG(WARNING) << "base::WaitForExitCode failed"; + return false; + } + return code == 0; + } +}; + +TEST_F(MacSandboxTest, StringEscape) { + using sandbox::QuotePlainString; + + const struct string_escape_test_data { + const char* to_escape; + const char* escaped; + } string_escape_cases[] = { + {"", ""}, + {"\b\f\n\r\t\\\"", "\\b\\f\\n\\r\\t\\\\\\\""}, + {"/'", "/'"}, + {"sandwich", "sandwich"}, + {"(sandwich)", "(sandwich)"}, + {"^\u2135.\u2136$", "^\\u2135.\\u2136$"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(string_escape_cases); ++i) { + std::string out; + std::string in(string_escape_cases[i].to_escape); + EXPECT_TRUE(QuotePlainString(in, &out)); + EXPECT_EQ(string_escape_cases[i].escaped, out); + } +} + +TEST_F(MacSandboxTest, RegexEscape) { + using sandbox::QuoteStringForRegex; + + const std::string kSandboxEscapeSuffix("(/|$)"); + const struct regex_test_data { + const wchar_t *to_escape; + const char* escaped; + } regex_cases[] = { + {L"", ""}, + {L"/'", "/'"}, // / & ' characters don't need escaping. + {L"sandwich", "sandwich"}, + {L"(sandwich)", "\\(sandwich\\)"}, + }; + + // Check that all characters whose values are smaller than 32 [1F] are + // rejected by the regex escaping code. + { + std::string out; + char fail_string[] = {31, 0}; + char ok_string[] = {32, 0}; + EXPECT_FALSE(QuoteStringForRegex(fail_string, &out)); + EXPECT_TRUE(QuoteStringForRegex(ok_string, &out)); + } + + // Check that all characters whose values are larger than 126 [7E] are + // rejected by the regex escaping code. + { + std::string out; + EXPECT_TRUE(QuoteStringForRegex("}", &out)); // } == 0x7D == 125 + EXPECT_FALSE(QuoteStringForRegex("~", &out)); // ~ == 0x7E == 126 + EXPECT_FALSE(QuoteStringForRegex(WideToUTF8(L"^\u2135.\u2136$"), &out)); + } + + { + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(regex_cases); ++i) { + std::string out; + std::string in = WideToUTF8(regex_cases[i].to_escape); + EXPECT_TRUE(QuoteStringForRegex(in, &out)); + std::string expected("^"); + expected.append(regex_cases[i].escaped); + expected.append(kSandboxEscapeSuffix); + EXPECT_EQ(expected, out); + } + } + + { + std::string in_utf8("\\^.$|()[]*+?{}"); + std::string expected; + expected.push_back('^'); + for (size_t i = 0; i < in_utf8.length(); ++i) { + expected.push_back('\\'); + expected.push_back(in_utf8[i]); + } + expected.append(kSandboxEscapeSuffix); + + std::string out; + EXPECT_TRUE(QuoteStringForRegex(in_utf8, &out)); + EXPECT_EQ(expected, out); + + } +} + +// A class to handle auto-deleting a directory. +class ScopedDirectoryDelete { + public: + inline void operator()(FilePath* x) const { + if (x) { + file_util::Delete(*x, true); + } + } +}; + +typedef scoped_ptr_malloc<FilePath, ScopedDirectoryDelete> ScopedDirectory; + +TEST_F(MacSandboxTest, SandboxAccess) { + FilePath tmp_dir; + ASSERT_TRUE(file_util::CreateNewTempDirectory("", &tmp_dir)); + // This step is important on OS X since the sandbox only understands "real" + // paths and the paths CreateNewTempDirectory() returns are empirically in + // /var which is a symlink to /private/var . + ASSERT_TRUE(file_util::AbsolutePath(&tmp_dir)); + ScopedDirectory cleanup(&tmp_dir); + + const char* sandbox_dir_cases[] = { + "simple_dir_name", + "^hello++ $", // Regex. + "\\^.$|()[]*+?{}", // All regex characters. + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(sandbox_dir_cases); ++i) { + const char* sandbox_dir_name = sandbox_dir_cases[i]; + FilePath sandbox_dir = tmp_dir.Append(sandbox_dir_name); + ASSERT_TRUE(file_util::CreateDirectory(sandbox_dir)); + ScopedDirectory cleanup_sandbox(&sandbox_dir); + EXPECT_TRUE(CheckSandbox(sandbox_dir.value())); + } +} + +MULTIPROCESS_TEST_MAIN(mac_sandbox_path_access) { + char *sandbox_allowed_dir = getenv(kSandboxAccessPathKey); + if (!sandbox_allowed_dir) + return -1; + + // Build up a sandbox profile that only allows access to DIR_TO_ALLOW_ACCESS. + NSString *sandbox_profile = + @"(version 1)" \ + "(deny default)" \ + "(allow signal (target self))" \ + "(allow sysctl-read)" \ + "(allow file-read-metadata)" \ + "(allow file-read* file-write* (regex #\"DIR_TO_ALLOW_ACCESS\"))"; + + std::string allowed_dir(sandbox_allowed_dir); + std::string allowed_dir_escaped; + if (!sandbox::QuoteStringForRegex(allowed_dir, &allowed_dir_escaped)) { + LOG(ERROR) << "Regex string quoting failed " << allowed_dir; + return -1; + } + NSString* allowed_dir_escaped_ns = base::SysUTF8ToNSString( + allowed_dir_escaped.c_str()); + sandbox_profile = [sandbox_profile + stringByReplacingOccurrencesOfString:@"DIR_TO_ALLOW_ACCESS" + withString:allowed_dir_escaped_ns]; + // Enable Sandbox. + char* error_buff = NULL; + int error = sandbox_init([sandbox_profile UTF8String], 0, &error_buff); + if (error == -1) { + LOG(ERROR) << "Failed to Initialize Sandbox: " << error_buff; + return -1; + } + sandbox_free_error(error_buff); + + // Test Sandbox. + + // We should be able to list the contents of the sandboxed directory. + DIR *file_list = NULL; + file_list = opendir(sandbox_allowed_dir); + if (!file_list) { + PLOG(ERROR) << "Sandbox overly restrictive: call to opendir(" + << sandbox_allowed_dir + << ") failed"; + return -1; + } + closedir(file_list); + + // Test restrictions on accessing files. + FilePath allowed_dir_path(sandbox_allowed_dir); + FilePath allowed_file = allowed_dir_path.Append("ok_to_write"); + FilePath denied_file1 = allowed_dir_path.DirName().Append("cant_access"); + + // Try to write a file who's name has the same prefix as the directory we + // allow access to. + FilePath basename = allowed_dir_path.BaseName(); + std::string tricky_filename = basename.value() + "123"; + FilePath denied_file2 = allowed_dir_path.DirName().Append(tricky_filename); + + if (open(allowed_file.value().c_str(), O_WRONLY | O_CREAT) <= 0) { + PLOG(ERROR) << "Sandbox overly restrictive: failed to write (" + << allowed_file.value() + << ")"; + return -1; + } + + if (open(denied_file1.value().c_str(), O_WRONLY | O_CREAT) > 0) { + PLOG(ERROR) << "Sandbox breach: was able to write (" + << denied_file1.value() + << ")"; + return -1; + } + + if (open(denied_file2.value().c_str(), O_WRONLY | O_CREAT) > 0) { + PLOG(ERROR) << "Sandbox breach: was able to write (" + << denied_file2.value() + << ")"; + return -1; + } + + return 0; +} |