diff options
author | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-03-11 21:56:11 +0000 |
---|---|---|
committer | jam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-03-11 21:56:11 +0000 |
commit | 415c2cdea5dfa8bc87bd36a91bdb8473026f943f (patch) | |
tree | 22aefe975f1be77a1bb18cdd96aeb86fbd96e096 /content/common | |
parent | 5471bc3e9d08d1d955c96a8193ee2d9638c1591a (diff) | |
download | chromium_src-415c2cdea5dfa8bc87bd36a91bdb8473026f943f.zip chromium_src-415c2cdea5dfa8bc87bd36a91bdb8473026f943f.tar.gz chromium_src-415c2cdea5dfa8bc87bd36a91bdb8473026f943f.tar.bz2 |
Move some chrome\common code to content in preparation for moving chrome\gpu.
TBR=avi
Review URL: http://codereview.chromium.org/6686002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@77868 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/common')
23 files changed, 2454 insertions, 0 deletions
diff --git a/content/common/chrome_application_mac.h b/content/common/chrome_application_mac.h new file mode 100644 index 0000000..23178ff --- /dev/null +++ b/content/common/chrome_application_mac.h @@ -0,0 +1,71 @@ +// Copyright (c) 2011 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 CONTENT_COMMON_CHROME_APPLICATION_MAC_H_ +#define CONTENT_COMMON_CHROME_APPLICATION_MAC_H_ +#pragma once + +#if defined(__OBJC__) + +#import <AppKit/AppKit.h> + +#include "base/basictypes.h" +#include "base/message_pump_mac.h" +#include "base/scoped_nsobject.h" + +// Event hooks must implement this protocol. +@protocol CrApplicationEventHookProtocol +- (void)hookForEvent:(NSEvent*)theEvent; +@end + + +@interface CrApplication : NSApplication<CrAppProtocol> { + @private + BOOL handlingSendEvent_; + // Array of objects implementing the CrApplicationEventHookProtocol + scoped_nsobject<NSMutableArray> eventHooks_; +} +- (BOOL)isHandlingSendEvent; + +// Add or remove an event hook to be called for every sendEvent: +// that the application receives. These handlers are called before +// the normal [NSApplication sendEvent:] call is made. + +// This is not a good alternative to a nested event loop. It should +// be used only when normal event logic and notification breaks down +// (e.g. when clicking outside a canBecomeKey:NO window to "switch +// context" out of it). +- (void)addEventHook:(id<CrApplicationEventHookProtocol>)hook; +- (void)removeEventHook:(id<CrApplicationEventHookProtocol>)hook; + ++ (NSApplication*)sharedApplication; +@end + +namespace chrome_application_mac { + +// Controls the state of |handlingSendEvent_| in the event loop so that it is +// reset properly. +class ScopedSendingEvent { + public: + ScopedSendingEvent(); + ~ScopedSendingEvent(); + + private: + CrApplication* app_; + BOOL handling_; + DISALLOW_COPY_AND_ASSIGN(ScopedSendingEvent); +}; + +} // namespace chrome_application_mac + +#endif // defined(__OBJC__) + +namespace chrome_application_mac { + +// To be used to instantiate CrApplication from C++ code. +void RegisterCrApp(); + +} // namespace chrome_application_mac + +#endif // CONTENT_COMMON_CHROME_APPLICATION_MAC_H_ diff --git a/content/common/chrome_application_mac.mm b/content/common/chrome_application_mac.mm new file mode 100644 index 0000000..e4adb3a --- /dev/null +++ b/content/common/chrome_application_mac.mm @@ -0,0 +1,76 @@ +// Copyright (c) 2011 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 "content/common/chrome_application_mac.h" + +#include "base/logging.h" + +@interface CrApplication () +- (void)setHandlingSendEvent:(BOOL)handlingSendEvent; +@end + +@implementation CrApplication +// Initialize NSApplication using the custom subclass. Check whether NSApp +// was already initialized using another class, because that would break +// some things. ++ (NSApplication*)sharedApplication { + NSApplication* app = [super sharedApplication]; + if (![NSApp isKindOfClass:self]) { + LOG(ERROR) << "NSApp should be of type " << [[self className] UTF8String] + << ", not " << [[NSApp className] UTF8String]; + DCHECK(false) << "NSApp is of wrong type"; + } + return app; +} + +- (id)init { + if ((self = [super init])) { + eventHooks_.reset([[NSMutableArray alloc] init]); + } + return self; +} + +- (BOOL)isHandlingSendEvent { + return handlingSendEvent_; +} + +- (void)setHandlingSendEvent:(BOOL)handlingSendEvent { + handlingSendEvent_ = handlingSendEvent; +} + +- (void)sendEvent:(NSEvent*)event { + chrome_application_mac::ScopedSendingEvent sendingEventScoper; + for (id<CrApplicationEventHookProtocol> handler in eventHooks_.get()) { + [handler hookForEvent:event]; + } + [super sendEvent:event]; +} + +- (void)addEventHook:(id<CrApplicationEventHookProtocol>)handler { + [eventHooks_ addObject:handler]; +} + +- (void)removeEventHook:(id<CrApplicationEventHookProtocol>)handler { + [eventHooks_ removeObject:handler]; +} + +@end + +namespace chrome_application_mac { + +ScopedSendingEvent::ScopedSendingEvent() + : app_(static_cast<CrApplication*>([CrApplication sharedApplication])), + handling_([app_ isHandlingSendEvent]) { + [app_ setHandlingSendEvent:YES]; +} + +ScopedSendingEvent::~ScopedSendingEvent() { + [app_ setHandlingSendEvent:handling_]; +} + +void RegisterCrApp() { + [CrApplication sharedApplication]; +} + +} // namespace chrome_application_mac diff --git a/content/common/content_switches.cc b/content/common/content_switches.cc index 89efd67..62529a1 100644 --- a/content/common/content_switches.cc +++ b/content/common/content_switches.cc @@ -16,10 +16,19 @@ const char kAllowSandboxDebugging[] = "allow-sandbox-debugging"; // Path to the exe to run for the renderer and plugin subprocesses. const char kBrowserSubprocessPath[] = "browser-subprocess-path"; +// Disables client-visible 3D APIs, in particular WebGL and Pepper 3D. +// This is controlled by policy and is kept separate from the other +// enable/disable switches to avoid accidentally regressing the policy +// support for controlling access to these APIs. +const char kDisable3DAPIs[] = "disable-3d-apis"; + // Disable limits on the number of backing stores. Can prevent blinking for // users with many windows/tabs and lots of memory. const char kDisableBackingStoreLimit[] = "disable-backing-store-limit"; +// Disable experimental WebGL support. +const char kDisableExperimentalWebGL[] = "disable-webgl"; + // Disable FileSystem API. const char kDisableFileSystem[] = "disable-file-system"; @@ -89,6 +98,9 @@ const char kExperimentalLocationFeatures[] = "experimental-location-features"; // Load NPAPI plugins from the specified directory. const char kExtraPluginDir[] = "extra-plugin-dir"; +// Causes the process to run as an extension subprocess. +const char kExtensionProcess[] = "extension"; + // Extra command line options for launching the GPU process (normally used // for debugging). Use like renderer-cmd-prefix. const char kGpuLauncher[] = "gpu-launcher"; @@ -99,6 +111,9 @@ const char kGpuProcess[] = "gpu-process"; // Causes the GPU process to display a dialog on launch. const char kGpuStartupDialog[] = "gpu-startup-dialog"; +// Runs WebGL inside the renderer process. +const char kInProcessWebGL[] = "in-process-webgl"; + // Use LevelDB as back-end for Indexed Database API. const char kLevelDBIndexedDatabase[] = "indexeddb-use-leveldb"; @@ -112,12 +127,18 @@ const char kLoggingLevel[] = "log-level"; // Make plugin processes log their sent and received messages to VLOG(1). const char kLogPluginMessages[] = "log-plugin-messages"; +// Causes the process to run as a NativeClient loader. +const char kNaClLoaderProcess[] = "nacl-loader"; + // Don't Sandbox the GPU process, does not affect other sandboxed processes. const char kNoGpuSandbox[] = "no-gpu-sandbox"; // Don't send HTTP-Referer headers. const char kNoReferrers[] = "no-referrers"; +// Disables the sandbox for all process types that are normally sandboxed. +const char kNoSandbox[] = "no-sandbox"; + // Specifies a command that should be used to launch the plugin process. Useful // for running the plugin process through purify or quantify. Ex: // --plugin-launcher="path\to\purify /Run=yes" @@ -168,6 +189,9 @@ const char kProcessPerTab[] = "process-per-tab"; // renderer or plugin host. If it's empty, it's the browser. const char kProcessType[] = "type"; +// Causes the process to run as a profile import subprocess. +const char kProfileImportProcess[] = "profile-import"; + // Register Pepper plugins (see pepper_plugin_registry.cc for its format). const char kRegisterPepperPlugins[] = "register-pepper-plugins"; @@ -187,6 +211,9 @@ const char kRendererStartupDialog[] = "renderer-startup-dialog"; // Runs the plugin processes inside the sandbox. const char kSafePlugins[] = "safe-plugins"; +// Causes the process to run as a service process. +const char kServiceProcess[] = "service"; + // Runs the renderer and plugins in the same process as the browser const char kSingleProcess[] = "single-process"; @@ -207,6 +234,13 @@ const char kUnlimitedQuotaForIndexedDB[] = "unlimited-quota-for-indexeddb"; // A string used to override the default user agent with a custom one. const char kUserAgent[] = "user-agent"; +// Causes the process to run as a utility subprocess. +const char kUtilityProcess[] = "utility"; + +// The utility process is sandboxed, with access to one directory. This flag +// specifies the directory that can be accessed. +const char kUtilityProcessAllowedDir[] = "utility-allowed-dir"; + // Will add kWaitForDebugger to every child processes. If a value is passed, it // will be used as a filter to determine if the child process should have the // kWaitForDebugger flag passed on or not. diff --git a/content/common/content_switches.h b/content/common/content_switches.h index 1d29799..022f164 100644 --- a/content/common/content_switches.h +++ b/content/common/content_switches.h @@ -13,7 +13,9 @@ namespace switches { extern const char kAllowFileAccessFromFiles[]; extern const char kAllowSandboxDebugging[]; extern const char kBrowserSubprocessPath[]; +extern const char kDisable3DAPIs[]; extern const char kDisableBackingStoreLimit[]; +extern const char kDisableExperimentalWebGL[]; extern const char kDisableFileSystem[]; extern const char kDisableGpuSandbox[]; extern const char kDisableGpuWatchdog[]; @@ -32,16 +34,22 @@ extern const char kEnableSandboxLogging[]; extern const char kEnableSeccompSandbox[]; extern const char kEnableStatsTable[]; extern const char kExperimentalLocationFeatures[]; +// TODO(jam): this doesn't belong in content. +extern const char kExtensionProcess[]; extern const char kExtraPluginDir[]; extern const char kGpuLauncher[]; extern const char kGpuProcess[]; extern const char kGpuStartupDialog[]; +extern const char kInProcessWebGL[]; extern const char kLevelDBIndexedDatabase[]; extern const char kLoadPlugin[]; extern const char kLoggingLevel[]; extern const char kLogPluginMessages[]; +// TODO(jam): this doesn't belong in content. +extern const char kNaClLoaderProcess[]; extern const char kNoGpuSandbox[]; extern const char kNoReferrers[]; +extern const char kNoSandbox[]; extern const char kPluginLauncher[]; extern const char kPluginPath[]; extern const char kPluginProcess[]; @@ -55,17 +63,23 @@ extern const char kPpapiStartupDialog[]; extern const char kProcessPerSite[]; extern const char kProcessPerTab[]; extern const char kProcessType[]; +// TODO(jam): this doesn't belong in content. +extern const char kProfileImportProcess[]; extern const char kRegisterPepperPlugins[]; extern const char kRendererCmdPrefix[]; extern const char kRendererCrashTest[]; extern const char kRendererProcess[]; extern const char kRendererStartupDialog[]; extern const char kSafePlugins[]; +// TODO(jam): this doesn't belong in content. +extern const char kServiceProcess[]; extern const char kSingleProcess[]; extern const char kTestSandbox[]; extern const char kUnlimitedQuotaForFiles[]; extern const char kUnlimitedQuotaForIndexedDB[]; extern const char kUserAgent[]; +extern const char kUtilityProcess[]; +extern const char kUtilityProcessAllowedDir[]; extern const char kWaitForDebuggerChildren[]; extern const char kWebCoreLogChannels[]; extern const char kWebWorkerProcessPerCore[]; diff --git a/content/common/debug_flags.cc b/content/common/debug_flags.cc new file mode 100644 index 0000000..9fcf2f0 --- /dev/null +++ b/content/common/debug_flags.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2011 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 "content/common/debug_flags.h" + +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/utf_string_conversions.h" +#include "chrome/common/chrome_switches.h" +#include "ipc/ipc_switches.h" + +bool DebugFlags::ProcessDebugFlags(CommandLine* command_line, + ChildProcessInfo::ProcessType type, + bool is_in_sandbox) { + bool should_help_child = false; + const CommandLine& current_cmd_line = *CommandLine::ForCurrentProcess(); + if (current_cmd_line.HasSwitch(switches::kDebugChildren)) { + // Look to pass-on the kDebugOnStart flag. + std::string value = current_cmd_line.GetSwitchValueASCII( + switches::kDebugChildren); + if (value.empty() || + (type == ChildProcessInfo::WORKER_PROCESS && + value == switches::kWorkerProcess) || + (type == ChildProcessInfo::RENDER_PROCESS && + value == switches::kRendererProcess) || + (type == ChildProcessInfo::PLUGIN_PROCESS && + value == switches::kPluginProcess)) { + command_line->AppendSwitch(switches::kDebugOnStart); + should_help_child = true; + } + command_line->AppendSwitchASCII(switches::kDebugChildren, value); + } else if (current_cmd_line.HasSwitch(switches::kWaitForDebuggerChildren)) { + // Look to pass-on the kWaitForDebugger flag. + std::string value = current_cmd_line.GetSwitchValueASCII( + switches::kWaitForDebuggerChildren); + if (value.empty() || + (type == ChildProcessInfo::WORKER_PROCESS && + value == switches::kWorkerProcess) || + (type == ChildProcessInfo::RENDER_PROCESS && + value == switches::kRendererProcess) || + (type == ChildProcessInfo::PLUGIN_PROCESS && + value == switches::kPluginProcess)) { + command_line->AppendSwitch(switches::kWaitForDebugger); + } + command_line->AppendSwitchASCII(switches::kWaitForDebuggerChildren, value); + } + return should_help_child; +} diff --git a/content/common/debug_flags.h b/content/common/debug_flags.h new file mode 100644 index 0000000..0699e4d --- /dev/null +++ b/content/common/debug_flags.h @@ -0,0 +1,29 @@ +// Copyright (c) 2011 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 CONTENT_COMMON_DEBUG_FLAGS_H_ +#define CONTENT_COMMON_DEBUG_FLAGS_H_ +#pragma once + +#include "content/common/child_process_info.h" + +class CommandLine; + +class DebugFlags { + public: + + // Updates the command line arguments with debug-related flags. If + // debug flags have been used with this process, they will be + // filtered and added to command_line as needed. is_in_sandbox must + // be true if the child process will be in a sandbox. + // + // Returns true if the caller should "help" the child process by + // calling the JIT debugger on it. It may only happen if + // is_in_sandbox is true. + static bool ProcessDebugFlags(CommandLine* command_line, + ChildProcessInfo::ProcessType type, + bool is_in_sandbox); +}; + +#endif // CONTENT_COMMON_DEBUG_FLAGS_H_ diff --git a/content/common/font_descriptor_mac.h b/content/common/font_descriptor_mac.h new file mode 100644 index 0000000..91bb338 --- /dev/null +++ b/content/common/font_descriptor_mac.h @@ -0,0 +1,33 @@ +// Copyright (c) 2011 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 CONTENT_COMMON_FONT_DESCRIPTOR_MAC_H_ +#define CONTENT_COMMON_FONT_DESCRIPTOR_MAC_H_ +#pragma once + +#include "base/string16.h" + +#ifdef __OBJC__ +@class NSFont; +#else +class NSFont; +#endif + +// Container to allow serializing an NSFont over IPC. +struct FontDescriptor { + explicit FontDescriptor(NSFont* font); + + FontDescriptor() : font_point_size(0) {} + + // Return an autoreleased NSFont corresponding to the font description. + NSFont* nsFont() const; + + // Name of the font. + string16 font_name; + + // Size in points. + float font_point_size; +}; + +#endif // CONTENT_COMMON_FONT_DESCRIPTOR_MAC_H_ diff --git a/content/common/font_descriptor_mac.mm b/content/common/font_descriptor_mac.mm new file mode 100644 index 0000000..90c7986 --- /dev/null +++ b/content/common/font_descriptor_mac.mm @@ -0,0 +1,20 @@ +// Copyright (c) 2011 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 "content/common/font_descriptor_mac.h" + +#include <Cocoa/Cocoa.h> + +#include "base/sys_string_conversions.h" + +FontDescriptor::FontDescriptor(NSFont* font) { + font_name = base::SysNSStringToUTF16([font fontName]); + font_point_size = [font pointSize]; +} + +NSFont* FontDescriptor::nsFont() const { + NSString* font_name_ns = base::SysUTF16ToNSString(font_name); + NSFont* font = [NSFont fontWithName:font_name_ns size:font_point_size]; + return font; +} diff --git a/content/common/font_descriptor_mac_unittest.mm b/content/common/font_descriptor_mac_unittest.mm new file mode 100644 index 0000000..afa905b --- /dev/null +++ b/content/common/font_descriptor_mac_unittest.mm @@ -0,0 +1,91 @@ +// Copyright (c) 2011 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 "content/common/font_descriptor_mac.h" + +#include <Cocoa/Cocoa.h> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +namespace { + +class FontSerializationTest : public PlatformTest {}; + + +// Compare 2 fonts, make sure they point at the same font definition and have +// the same style. Only Bold & Italic style attributes are tested since those +// are the only ones we care about at the moment. +bool CompareFonts(NSFont* font1, NSFont* font2) { + ATSFontRef id1 = CTFontGetPlatformFont(reinterpret_cast<CTFontRef>(font1), 0); + ATSFontRef id2 = CTFontGetPlatformFont(reinterpret_cast<CTFontRef>(font2), 0); + + if (id1 != id2) { + LOG(ERROR) << "ATSFontRefs for " + << [[font1 fontName] UTF8String] + << " and " + << [[font2 fontName] UTF8String] + << " are different"; + return false; + } + + CGFloat size1 = [font1 pointSize]; + CGFloat size2 = [font2 pointSize]; + if (size1 != size2) { + LOG(ERROR) << "font sizes for " + << [[font1 fontName] UTF8String] << " (" << size1 << ")" + << "and" + << [[font2 fontName] UTF8String] << " (" << size2 << ")" + << " are different"; + return false; + } + + NSFontTraitMask traits1 = [[NSFontManager sharedFontManager] + traitsOfFont:font1]; + NSFontTraitMask traits2 = [[NSFontManager sharedFontManager] + traitsOfFont:font2]; + + bool is_bold1 = traits1 & NSBoldFontMask; + bool is_bold2 = traits2 & NSBoldFontMask; + bool is_italic1 = traits1 & NSItalicFontMask; + bool is_italic2 = traits2 & NSItalicFontMask; + + if (is_bold1 != is_bold2 || is_italic1 != is_italic2) { + LOG(ERROR) << "Style information for " + << [[font1 fontName] UTF8String] + << " and " + << [[font2 fontName] UTF8String] + << " are different"; + return false; + } + + return true; +} + +// Verify that serialization and deserialization of fonts with various styles +// is performed correctly by FontDescriptor. +TEST_F(FontSerializationTest, StyledFonts) { + NSFont* plain_font = [NSFont systemFontOfSize:12.0]; + ASSERT_TRUE(plain_font != nil); + FontDescriptor desc_plain(plain_font); + EXPECT_TRUE(CompareFonts(plain_font, desc_plain.nsFont())); + + NSFont* bold_font = [NSFont boldSystemFontOfSize:30.0]; + ASSERT_TRUE(bold_font != nil); + FontDescriptor desc_bold(bold_font); + EXPECT_TRUE(CompareFonts(bold_font, desc_bold.nsFont())); + + NSFont* italic_bold_font = + [[NSFontManager sharedFontManager] + fontWithFamily:@"Courier" + traits:(NSBoldFontMask | NSItalicFontMask) + weight:5 + size:18.0]; + ASSERT_TRUE(italic_bold_font != nil); + FontDescriptor desc_italic_bold(italic_bold_font); + EXPECT_TRUE(CompareFonts(italic_bold_font, desc_italic_bold.nsFont())); +} + +} // namsepace diff --git a/content/common/font_loader_mac.h b/content/common/font_loader_mac.h new file mode 100644 index 0000000..532a09c --- /dev/null +++ b/content/common/font_loader_mac.h @@ -0,0 +1,55 @@ +// Copyright (c) 2011 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 CONTENT_COMMON_FONT_LOADER_MAC_H_ +#define CONTENT_COMMON_FONT_LOADER_MAC_H_ +#pragma once + +#include <ApplicationServices/ApplicationServices.h> + +#include "base/shared_memory.h" + +#ifdef __OBJC__ +@class NSFont; +#else +class NSFont; +#endif + +// Provides functionality to transmit fonts over IPC. +// +// Note about font formats: .dfont (datafork suitcase) fonts are currently not +// supported by this code since ATSFontActivateFromMemory() can't handle them +// directly. + +class FontLoader { + public: + // Load a font specified by |font_to_encode| into a shared memory buffer + // suitable for sending over IPC. + // + // On return: + // returns true on success, false on failure. + // |font_data| - shared memory buffer containing the raw data for the font + // file. + // |font_data_size| - size of data contained in |font_data|. + static bool LoadFontIntoBuffer(NSFont* font_to_encode, + base::SharedMemory* font_data, + uint32* font_data_size); + + // Given a shared memory buffer containing the raw data for a font file, load + // the font and return a container ref. + // + // |data| - A shared memory handle pointing to the raw data from a font file. + // |data_size| - Size of |data|. + // + // On return: + // returns true on success, false on failure. + // |font_container| - A font container corresponding to the designated font. + // The caller is responsible for releasing this value via ATSFontDeactivate() + // when done + static bool ATSFontContainerFromBuffer(base::SharedMemoryHandle font_data, + uint32 font_data_size, + ATSFontContainerRef* font_container); +}; + +#endif // CONTENT_COMMON_FONT_LOADER_MAC_H_ diff --git a/content/common/font_loader_mac.mm b/content/common/font_loader_mac.mm new file mode 100644 index 0000000..124708b --- /dev/null +++ b/content/common/font_loader_mac.mm @@ -0,0 +1,110 @@ +// Copyright (c) 2011 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 "content/common/font_loader_mac.h" + +#import <Cocoa/Cocoa.h> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/mac/mac_util.h" +#include "base/sys_string_conversions.h" + +// static +bool FontLoader::LoadFontIntoBuffer(NSFont* font_to_encode, + base::SharedMemory* font_data, + uint32* font_data_size) { + CHECK(font_data && font_data_size); + *font_data_size = 0; + + // Used only for logging. + std::string font_name([[font_to_encode fontName] UTF8String]); + + // Load appropriate NSFont. + if (!font_to_encode) { + LOG(ERROR) << "Failed to load font " << font_name; + return false; + } + + // NSFont -> ATSFontRef. + ATSFontRef ats_font = + CTFontGetPlatformFont(reinterpret_cast<CTFontRef>(font_to_encode), NULL); + if (!ats_font) { + LOG(ERROR) << "Conversion to ATSFontRef failed for " << font_name; + return false; + } + + // ATSFontRef -> File path. + // Warning: Calling this function on a font activated from memory will result + // in failure with a -50 - paramErr. This may occur if + // CreateCGFontFromBuffer() is called in the same process as this function + // e.g. when writing a unit test that exercises these two functions together. + // If said unit test were to load a system font and activate it from memory + // it becomes impossible for the system to the find the original file ref + // since the font now lives in memory as far as it's concerned. + FSRef font_fsref; + if (ATSFontGetFileReference(ats_font, &font_fsref) != noErr) { + LOG(ERROR) << "Failed to find font file for " << font_name; + return false; + } + FilePath font_path = FilePath(base::mac::PathFromFSRef(font_fsref)); + + // Load file into shared memory buffer. + int64 font_file_size_64 = -1; + if (!file_util::GetFileSize(font_path, &font_file_size_64)) { + LOG(ERROR) << "Couldn't get font file size for " << font_path.value(); + return false; + } + + if (font_file_size_64 <= 0 || font_file_size_64 >= kint32max) { + LOG(ERROR) << "Bad size for font file " << font_path.value(); + return false; + } + + int32 font_file_size_32 = static_cast<int32>(font_file_size_64); + if (!font_data->CreateAndMapAnonymous(font_file_size_32)) { + LOG(ERROR) << "Failed to create shmem area for " << font_name; + return false; + } + + int32 amt_read = file_util::ReadFile(font_path, + reinterpret_cast<char*>(font_data->memory()), + font_file_size_32); + if (amt_read != font_file_size_32) { + LOG(ERROR) << "Failed to read font data for " << font_path.value(); + return false; + } + + *font_data_size = font_file_size_32; + return true; +} + +// static +bool FontLoader::ATSFontContainerFromBuffer(base::SharedMemoryHandle font_data, + uint32 font_data_size, + ATSFontContainerRef* font_container) +{ + CHECK(font_container); + + using base::SharedMemory; + DCHECK(SharedMemory::IsHandleValid(font_data)); + DCHECK_GT(font_data_size, 0U); + + SharedMemory shm(font_data, true); + if (!shm.Map(font_data_size)) + return false; + + // A value of 3 means the font is private and can't be seen by anyone else. + // This is the value used by WebKit when activating remote fonts. + const ATSFontContext kFontContextPrivate = 3; + OSStatus err = ATSFontActivateFromMemory(shm.memory(), font_data_size, + kFontContextPrivate, kATSFontFormatUnspecified, NULL, + kATSOptionFlagsDefault, font_container); + if (err != noErr || !font_container) + return false; + + return true; +} diff --git a/content/common/main_function_params.h b/content/common/main_function_params.h new file mode 100644 index 0000000..1c53c5f --- /dev/null +++ b/content/common/main_function_params.h @@ -0,0 +1,37 @@ +// Copyright (c) 2011 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. + +// Wrapper to the parameter list for the "main" entry points (browser, renderer, +// plugin) to shield the call sites from the differences between platforms +// (e.g., POSIX doesn't need to pass any sandbox information). + +#ifndef CONTENT_COMMON_MAIN_FUNCTION_PARAMS_H_ +#define CONTENT_COMMON_MAIN_FUNCTION_PARAMS_H_ +#pragma once + +#include "base/command_line.h" +#include "content/common/sandbox_init_wrapper.h" + +namespace base { +namespace mac { +class ScopedNSAutoreleasePool; +} +} + +class Task; + +struct MainFunctionParams { + MainFunctionParams(const CommandLine& cl, const SandboxInitWrapper& sb, + base::mac::ScopedNSAutoreleasePool* pool) + : command_line_(cl), sandbox_info_(sb), autorelease_pool_(pool), + ui_task(NULL) { } + const CommandLine& command_line_; + const SandboxInitWrapper& sandbox_info_; + base::mac::ScopedNSAutoreleasePool* autorelease_pool_; + // Used by InProcessBrowserTest. If non-null BrowserMain schedules this + // task to run on the MessageLoop and BrowserInit is not invoked. + Task* ui_task; +}; + +#endif // CONTENT_COMMON_MAIN_FUNCTION_PARAMS_H_ diff --git a/content/common/sandbox_init_wrapper.h b/content/common/sandbox_init_wrapper.h new file mode 100644 index 0000000..c144992 --- /dev/null +++ b/content/common/sandbox_init_wrapper.h @@ -0,0 +1,70 @@ +// Copyright (c) 2011 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 CONTENT_COMMON_SANDBOX_INIT_WRAPPER_H_ +#define CONTENT_COMMON_SANDBOX_INIT_WRAPPER_H_ +#pragma once + +// Wraps the sandbox initialization and platform variables to consolodate +// the code and reduce the number of platform ifdefs elsewhere. The POSIX +// version of this wrapper is basically empty. + +#include "build/build_config.h" + +#include <string> + +#include "base/basictypes.h" +#if defined(OS_WIN) +#include "sandbox/src/sandbox.h" +#endif + +class CommandLine; + +#if defined(OS_WIN) + +class SandboxInitWrapper { + public: + SandboxInitWrapper() : broker_services_(), target_services_() { } + // SetServices() needs to be called before InitializeSandbox() on Win32 with + // the info received from the chrome exe main. + void SetServices(sandbox::SandboxInterfaceInfo* sandbox_info); + sandbox::BrokerServices* BrokerServices() const { return broker_services_; } + sandbox::TargetServices* TargetServices() const { return target_services_; } + + // Initialize the sandbox for renderer, gpu, utility, worker, nacl, and + // plug-in processes, depending on the command line flags. The browser + // process is not sandboxed. + // Returns true if the sandbox was initialized succesfully, false if an error + // occurred. If process_type isn't one that needs sandboxing true is always + // returned. + bool InitializeSandbox(const CommandLine& parsed_command_line, + const std::string& process_type); + private: + sandbox::BrokerServices* broker_services_; + sandbox::TargetServices* target_services_; + + DISALLOW_COPY_AND_ASSIGN(SandboxInitWrapper); +}; + +#elif defined(OS_POSIX) + +class SandboxInitWrapper { + public: + SandboxInitWrapper() { } + + // Initialize the sandbox for renderer and plug-in processes, depending on + // the command line flags. The browser process is not sandboxed. + // Returns true if the sandbox was initialized succesfully, false if an error + // occurred. If process_type isn't one that needs sandboxing true is always + // returned. + bool InitializeSandbox(const CommandLine& parsed_command_line, + const std::string& process_type); + + private: + DISALLOW_COPY_AND_ASSIGN(SandboxInitWrapper); +}; + +#endif + +#endif // CONTENT_COMMON_SANDBOX_INIT_WRAPPER_H_ diff --git a/content/common/sandbox_init_wrapper_linux.cc b/content/common/sandbox_init_wrapper_linux.cc new file mode 100644 index 0000000..450bd2d --- /dev/null +++ b/content/common/sandbox_init_wrapper_linux.cc @@ -0,0 +1,13 @@ +// Copyright (c) 2011 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 "content/common/sandbox_init_wrapper.h" + +#include "base/command_line.h" + +bool SandboxInitWrapper::InitializeSandbox(const CommandLine& command_line, + const std::string& process_type) { + // TODO(port): Does Linux need to do anything here? + return true; +} diff --git a/content/common/sandbox_init_wrapper_mac.cc b/content/common/sandbox_init_wrapper_mac.cc new file mode 100644 index 0000000..997ba43 --- /dev/null +++ b/content/common/sandbox_init_wrapper_mac.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2011 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 "content/common/sandbox_init_wrapper.h" + +#include "base/command_line.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "content/common/content_switches.h" +#include "content/common/sandbox_mac.h" + +bool SandboxInitWrapper::InitializeSandbox(const CommandLine& command_line, + const std::string& process_type) { + using sandbox::Sandbox; + + if (command_line.HasSwitch(switches::kNoSandbox)) + return true; + + Sandbox::SandboxProcessType sandbox_process_type; + FilePath allowed_dir; // Empty by default. + + if (process_type.empty()) { + // Browser process isn't sandboxed. + return true; + } else if (process_type == switches::kRendererProcess) { + if (!command_line.HasSwitch(switches::kDisable3DAPIs) && + !command_line.HasSwitch(switches::kDisableExperimentalWebGL) && + command_line.HasSwitch(switches::kInProcessWebGL)) { + // TODO(kbr): this check seems to be necessary only on this + // platform because the sandbox is initialized later. Remove + // this once this flag is removed. + return true; + } else { + sandbox_process_type = Sandbox::SANDBOX_TYPE_RENDERER; + } + } else if (process_type == switches::kExtensionProcess) { + // Extension processes are just renderers [they use RenderMain()] with a + // different set of command line flags. + // If we ever get here it means something has changed in regards + // to the extension process mechanics and we should probably reexamine + // how we sandbox extension processes since they are no longer identical + // to renderers. + NOTREACHED(); + return true; + } else if (process_type == switches::kUtilityProcess) { + // Utility process sandbox. + sandbox_process_type = Sandbox::SANDBOX_TYPE_UTILITY; + allowed_dir = + command_line.GetSwitchValuePath(switches::kUtilityProcessAllowedDir); + } else if (process_type == switches::kWorkerProcess) { + // Worker process sandbox. + sandbox_process_type = Sandbox::SANDBOX_TYPE_WORKER; + } else if (process_type == switches::kNaClLoaderProcess) { + // Native Client sel_ldr (user untrusted code) sandbox. + sandbox_process_type = Sandbox::SANDBOX_TYPE_NACL_LOADER; + } else if (process_type == switches::kGpuProcess) { + sandbox_process_type = Sandbox::SANDBOX_TYPE_GPU; + } else if ((process_type == switches::kPluginProcess) || + (process_type == switches::kProfileImportProcess) || + (process_type == switches::kServiceProcess)) { + return true; + } else { + // Failsafe: If you hit an unreached here, is your new process type in need + // of sandboxing? + NOTREACHED(); + return true; + } + + // Warm up APIs before turning on the sandbox. + Sandbox::SandboxWarmup(sandbox_process_type); + + // Actually sandbox the process. + return Sandbox::EnableSandbox(sandbox_process_type, allowed_dir); +} diff --git a/content/common/sandbox_init_wrapper_win.cc b/content/common/sandbox_init_wrapper_win.cc new file mode 100644 index 0000000..d322130 --- /dev/null +++ b/content/common/sandbox_init_wrapper_win.cc @@ -0,0 +1,52 @@ +// Copyright (c) 2011 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 "content/common/sandbox_init_wrapper.h" + +#include "base/command_line.h" +#include "base/logging.h" + +#include "chrome/common/chrome_switches.h" + +void SandboxInitWrapper::SetServices(sandbox::SandboxInterfaceInfo* info) { + if (!info) + return; + if (info->legacy) { + // Looks like we are in the case when the new chrome.dll is being launched + // by the old chrome.exe, the old chrome exe has SandboxInterfaceInfo as a + // union, while now we have a struct. + // TODO(cpu): Remove this nasty hack after M10 release. + broker_services_ = reinterpret_cast<sandbox::BrokerServices*>(info->legacy); + target_services_ = reinterpret_cast<sandbox::TargetServices*>(info->legacy); + } else { + // Normal case, both the exe and the dll are the same version. Both + // interface pointers cannot be non-zero. A process can either be a target + // or a broker but not both. + broker_services_ = info->broker_services; + target_services_ = info->target_services; + DCHECK(!(target_services_ && broker_services_)); + } +} + +bool SandboxInitWrapper::InitializeSandbox(const CommandLine& command_line, + const std::string& process_type) { + if (command_line.HasSwitch(switches::kNoSandbox)) + return true; + if ((process_type == switches::kRendererProcess) || + (process_type == switches::kExtensionProcess) || + (process_type == switches::kWorkerProcess) || + (process_type == switches::kNaClLoaderProcess) || + (process_type == switches::kUtilityProcess)) { + // The above five process types must be sandboxed unless --no-sandbox + // is present in the command line. + if (!target_services_) + return false; + } else { + // Other process types might or might not be sandboxed. + // TODO(cpu): clean this mess. + if (!target_services_) + return true; + } + return (sandbox::SBOX_ALL_OK == target_services_->Init()); +} diff --git a/content/common/sandbox_mac.h b/content/common/sandbox_mac.h new file mode 100644 index 0000000..50c989b --- /dev/null +++ b/content/common/sandbox_mac.h @@ -0,0 +1,180 @@ +// Copyright (c) 2011 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 CONTENT_COMMON_SANDBOX_MAC_H_ +#define CONTENT_COMMON_SANDBOX_MAC_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/hash_tables.h" +#include "base/gtest_prod_util.h" + +class FilePath; + +#if __OBJC__ +@class NSArray; +@class NSString; +#else +class NSArray; +class NSString; +#endif + +namespace sandbox { + +// Class representing a substring of the sandbox profile tagged with its type. +class SandboxSubstring { + public: + enum SandboxSubstringType { + PLAIN, // Just a plain string, no escaping necessary. + LITERAL, // Escape for use in (literal ...) expression. + REGEX, // Escape for use in (regex ...) expression. + }; + + SandboxSubstring() {} + + explicit SandboxSubstring(const std::string& value) + : value_(value), + type_(PLAIN) {} + + SandboxSubstring(const std::string& value, SandboxSubstringType type) + : value_(value), + type_(type) {} + + const std::string& value() { return value_; } + SandboxSubstringType type() { return type_; } + + private: + std::string value_; + SandboxSubstringType type_; +}; + +class Sandbox { + public: + // A map of variable name -> string to substitute in its place. + typedef base::hash_map<std::string, SandboxSubstring> + SandboxVariableSubstitions; + + enum SandboxProcessType { + SANDBOX_TYPE_FIRST_TYPE, // Placeholder to ease iteration. + + SANDBOX_TYPE_RENDERER = SANDBOX_TYPE_FIRST_TYPE, + + // The worker process uses the most restrictive sandbox which has almost + // *everything* locked down. Only a couple of /System/Library/ paths and + // some other very basic operations (e.g., reading metadata to allow + // following symlinks) are permitted. + SANDBOX_TYPE_WORKER, + + // Utility process is as restrictive as the worker process except full + // access is allowed to one configurable directory. + SANDBOX_TYPE_UTILITY, + + // Native Client sandbox for the user's untrusted code. + SANDBOX_TYPE_NACL_LOADER, + + // GPU process. + SANDBOX_TYPE_GPU, + + SANDBOX_AFTER_TYPE_LAST_TYPE, // Placeholder to ease iteration. + }; + + // Warm up System APIs that empirically need to be accessed before the Sandbox + // is turned on. |sandbox_type| is the type of sandbox to warm up. + static void SandboxWarmup(SandboxProcessType sandbox_type); + + // Turns on the OS X sandbox for this process. + // |sandbox_type| - type of Sandbox to use. + // |allowed_dir| - directory to allow access to, currently the only sandbox + // profile that supports this is SANDBOX_TYPE_UTILITY . + // + // Returns true on success, false if an error occurred enabling the sandbox. + static bool EnableSandbox(SandboxProcessType sandbox_type, + const FilePath& allowed_dir); + + + // Exposed for testing purposes, used by an accessory function of our tests + // so we can't use FRIEND_TEST. + + // Build the Sandbox command necessary to allow access to a named directory + // indicated by |allowed_dir|. + // Returns a string containing the sandbox profile commands necessary to allow + // access to that directory or nil if an error occured. + + // The header comment for PostProcessSandboxProfile() explains how variable + // substition works in sandbox templates. + // The returned string contains embedded variables. The function fills in + // |substitutions| to contain the values for these variables. + static NSString* BuildAllowDirectoryAccessSandboxString( + const FilePath& allowed_dir, + SandboxVariableSubstitions* substitutions); + + // Assemble the final sandbox profile from a template by removing comments + // and substituting variables. + // + // |sandbox_template| is a string which contains 2 entitites to operate on: + // + // - Comments - The sandbox comment syntax is used to make the OS sandbox + // optionally ignore commands it doesn't support. e.g. + // ;10.6_ONLY (foo) + // Where (foo) is some command that is only supported on OS X 10.6. + // The ;10.6_ONLY comment can then be removed from the template to enable + // (foo) as appropriate. + // + // - Variables - denoted by @variable_name@ . These are defined in the + // sandbox template in cases where another string needs to be substituted at + // runtime. e.g. @HOMEDIR_AS_LITERAL@ is substituted at runtime for the user's + // home directory escaped appropriately for a (literal ...) expression. + // + // |comments_to_remove| is a list of NSStrings containing the comments to + // remove. + // |substitutions| is a hash of "variable name" -> "string to substitute". + // Where the replacement string is tagged with information on how it is to be + // escaped e.g. used as part of a regex string or a literal. + // + // On output |final_sandbox_profile_str| contains the final sandbox profile. + // Returns true on success, false otherwise. + static bool PostProcessSandboxProfile( + NSString* in_sandbox_data, + NSArray* comments_to_remove, + SandboxVariableSubstitions& substitutions, + std::string *final_sandbox_profile_str); + + private: + // Escape |src_utf8| for use in a plain string variable in a sandbox + // configuraton file. On return |dst| is set to the quoted output. + // Returns: true on success, false otherwise. + static bool QuotePlainString(const std::string& src_utf8, std::string* dst); + + // 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. + static bool QuoteStringForRegex(const std::string& str_utf8, + std::string* dst); + + // Convert provided path into a "canonical" path matching what the Sandbox + // expects i.e. one without symlinks. + // This path is not necessarily unique e.g. in the face of hardlinks. + static void GetCanonicalSandboxPath(FilePath* path); + + FRIEND_TEST(MacDirAccessSandboxTest, StringEscape); + FRIEND_TEST(MacDirAccessSandboxTest, RegexEscape); + FRIEND_TEST(MacDirAccessSandboxTest, SandboxAccess); + + DISALLOW_IMPLICIT_CONSTRUCTORS(Sandbox); +}; + +} // namespace sandbox + +#endif // CONTENT_COMMON_SANDBOX_MAC_H_ diff --git a/content/common/sandbox_mac.mm b/content/common/sandbox_mac.mm new file mode 100644 index 0000000..1d1d36f --- /dev/null +++ b/content/common/sandbox_mac.mm @@ -0,0 +1,579 @@ +// Copyright (c) 2011 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 "content/common/sandbox_mac.h" + +#import <Cocoa/Cocoa.h> +#import <OpenGL/OpenGL.h> + +extern "C" { +#include <sandbox.h> +} +#include <signal.h> +#include <sys/param.h> + +#include "app/gfx/gl/gl_context.h" +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/mac/mac_util.h" +#include "base/rand_util_c.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/string16.h" +#include "base/string_util.h" +#include "base/sys_info.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "content/common/chrome_application_mac.h" +#include "content/common/content_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 { + + +// static +bool Sandbox::QuotePlainString(const std::string& src_utf8, std::string* dst) { + dst->clear(); + + const char* src = src_utf8.c_str(); + int32_t length = src_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); + base::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; +} + +// static +bool Sandbox::QuoteStringForRegex(const std::string& str_utf8, + std::string* dst) { + // Characters with special meanings in sandbox profile syntax. + 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 +// that needs to be warmed up. The OS version on which we found the need to +// enable the function is also noted. +// This function is tested on the following OS versions: +// 10.5.6, 10.6.0 + +// static +void Sandbox::SandboxWarmup(SandboxProcessType sandbox_type) { + base::mac::ScopedNSAutoreleasePool scoped_pool; + + { // CGColorSpaceCreateWithName(), CGBitmapContextCreate() - 10.5.6 + base::mac::ScopedCFTypeRef<CGColorSpaceRef> rgb_colorspace( + CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)); + + // Allocate a 1x1 image. + char data[4]; + base::mac::ScopedCFTypeRef<CGContextRef> context( + CGBitmapContextCreate(data, 1, 1, 8, 1 * 4, + rgb_colorspace, + kCGImageAlphaPremultipliedFirst | + kCGBitmapByteOrder32Host)); + + // Load in the color profiles we'll need (as a side effect). + (void) base::mac::GetSRGBColorSpace(); + (void) base::mac::GetSystemColorSpace(); + + // CGColorSpaceCreateSystemDefaultCMYK - 10.6 + base::mac::ScopedCFTypeRef<CGColorSpaceRef> cmyk_colorspace( + CGColorSpaceCreateWithName(kCGColorSpaceGenericCMYK)); + } + + { // [-NSColor colorUsingColorSpaceName] - 10.5.6 + NSColor* color = [NSColor controlTextColor]; + [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + } + + { // localtime() - 10.5.6 + time_t tv = {0}; + localtime(&tv); + } + + { // Gestalt() tries to read /System/Library/CoreServices/SystemVersion.plist + // on 10.5.6 + int32 tmp; + base::SysInfo::OperatingSystemVersionNumbers(&tmp, &tmp, &tmp); + } + + { // CGImageSourceGetStatus() - 10.6 + // Create a png with just enough data to get everything warmed up... + char png_header[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; + NSData* data = [NSData dataWithBytes:png_header + length:arraysize(png_header)]; + base::mac::ScopedCFTypeRef<CGImageSourceRef> img( + CGImageSourceCreateWithData((CFDataRef)data, + NULL)); + CGImageSourceGetStatus(img); + } + + { + // Allow access to /dev/urandom. + GetUrandomFD(); + } + + // Process-type dependent warm-up. + switch (sandbox_type) { + case SANDBOX_TYPE_GPU: + { // GPU-related stuff is very slow without this, probably because + // the sandbox prevents loading graphics drivers or some such. + CGLPixelFormatAttribute attribs[] = { (CGLPixelFormatAttribute)0 }; + CGLPixelFormatObj format; + GLint n; + CGLChoosePixelFormat(attribs, &format, &n); + if (format) + CGLReleasePixelFormat(format); + } + + { + // Preload either the desktop GL or the osmesa so, depending on the + // --use-gl flag. + gfx::GLContext::InitializeOneOff(); + } + break; + + default: + // To shut up a gcc warning. + break; + } +} + +// static +NSString* Sandbox::BuildAllowDirectoryAccessSandboxString( + const FilePath& allowed_dir, + SandboxVariableSubstitions* substitutions) { + // A whitelist is used to determine which directories can be statted + // This means that in the case of an /a/b/c/d/ directory, we may be able to + // stat the leaf directory, but not it's parent. + // The extension code in Chrome calls realpath() which fails if it can't call + // stat() on one of the parent directories in the path. + // The solution to this is to allow statting the parent directories themselves + // but not their contents. We need to add a separate rule for each parent + // directory. + + // 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_canonical(allowed_dir); + GetCanonicalSandboxPath(&allowed_dir_canonical); + + // Collect a list of all parent directories. + FilePath last_path = allowed_dir_canonical; + std::vector<FilePath> subpaths; + for (FilePath path = allowed_dir_canonical.DirName(); + path.value() != last_path.value(); + path = path.DirName()) { + subpaths.push_back(path); + last_path = path; + } + + // Iterate through all parents and allow stat() on them explicitly. + NSString* sandbox_command = @"(allow file-read-metadata "; + for (std::vector<FilePath>::reverse_iterator i = subpaths.rbegin(); + i != subpaths.rend(); + ++i) { + std::string subdir_escaped; + if (!QuotePlainString(i->value(), &subdir_escaped)) { + LOG(FATAL) << "String quoting failed " << i->value(); + return nil; + } + + NSString* subdir_escaped_ns = + base::SysUTF8ToNSString(subdir_escaped.c_str()); + sandbox_command = + [sandbox_command stringByAppendingFormat:@"(literal \"%@\")", + subdir_escaped_ns]; + } + + // Finally append the leaf directory. Unlike it's parents (for which only + // stat() should be allowed), the leaf directory needs full access. + (*substitutions)["ALLOWED_DIR"] = + SandboxSubstring(allowed_dir_canonical.value(), + SandboxSubstring::REGEX); + sandbox_command = + [sandbox_command + stringByAppendingString:@") (allow file-read* file-write*" + " (regex #\"@ALLOWED_DIR@\") )"]; + return sandbox_command; +} + +// Load the appropriate template for the given sandbox type. +// Returns the template as an NSString or nil on error. +NSString* LoadSandboxTemplate(Sandbox::SandboxProcessType sandbox_type) { + // We use a custom sandbox definition file to lock things down as + // tightly as possible. + NSString* sandbox_config_filename = nil; + switch (sandbox_type) { + case Sandbox::SANDBOX_TYPE_RENDERER: + sandbox_config_filename = @"renderer"; + break; + case Sandbox::SANDBOX_TYPE_WORKER: + sandbox_config_filename = @"worker"; + break; + case Sandbox::SANDBOX_TYPE_UTILITY: + sandbox_config_filename = @"utility"; + break; + case Sandbox::SANDBOX_TYPE_NACL_LOADER: + // The Native Client loader is used for safeguarding the user's + // untrusted code within Native Client. + sandbox_config_filename = @"nacl_loader"; + break; + case Sandbox::SANDBOX_TYPE_GPU: + sandbox_config_filename = @"gpu"; + break; + default: + NOTREACHED(); + return nil; + } + + // Read in the sandbox profile and the common prefix file. + NSString* common_sandbox_prefix_path = + [base::mac::MainAppBundle() pathForResource:@"common" + ofType:@"sb"]; + NSString* common_sandbox_prefix_data = + [NSString stringWithContentsOfFile:common_sandbox_prefix_path + encoding:NSUTF8StringEncoding + error:NULL]; + + if (!common_sandbox_prefix_data) { + LOG(FATAL) << "Failed to find the sandbox profile on disk " + << [common_sandbox_prefix_path fileSystemRepresentation]; + return nil; + } + + NSString* sandbox_profile_path = + [base::mac::MainAppBundle() pathForResource:sandbox_config_filename + ofType:@"sb"]; + NSString* sandbox_data = + [NSString stringWithContentsOfFile:sandbox_profile_path + encoding:NSUTF8StringEncoding + error:NULL]; + + if (!sandbox_data) { + LOG(FATAL) << "Failed to find the sandbox profile on disk " + << [sandbox_profile_path fileSystemRepresentation]; + return nil; + } + + // Prefix sandbox_data with common_sandbox_prefix_data. + return [common_sandbox_prefix_data stringByAppendingString:sandbox_data]; +} + +// Retrieve OS X version, output parameters are self explanatory. +void GetOSVersion(bool* snow_leopard_or_higher) { + int32 major_version, minor_version, bugfix_version; + base::SysInfo::OperatingSystemVersionNumbers(&major_version, + &minor_version, + &bugfix_version); + *snow_leopard_or_higher = + (major_version > 10 || (major_version == 10 && minor_version >= 6)); +} + +// static +bool Sandbox::PostProcessSandboxProfile( + NSString* sandbox_template, + NSArray* comments_to_remove, + SandboxVariableSubstitions& substitutions, + std::string *final_sandbox_profile_str) { + NSString* sandbox_data = [[sandbox_template copy] autorelease]; + + // Remove comments, e.g. ;10.6_ONLY . + for (NSString* to_remove in comments_to_remove) { + sandbox_data = [sandbox_data stringByReplacingOccurrencesOfString:to_remove + withString:@""]; + } + + // Split string on "@" characters. + std::vector<std::string> raw_sandbox_pieces; + if (Tokenize([sandbox_data UTF8String], "@", &raw_sandbox_pieces) == 0) { + LOG(FATAL) << "Bad Sandbox profile, should contain at least one token (" + << [sandbox_data UTF8String] + << ")"; + return false; + } + + // Iterate over string pieces and substitute variables, escaping as necessary. + size_t output_string_length = 0; + std::vector<std::string> processed_sandbox_pieces(raw_sandbox_pieces.size()); + for (std::vector<std::string>::iterator it = raw_sandbox_pieces.begin(); + it != raw_sandbox_pieces.end(); + ++it) { + std::string new_piece; + SandboxVariableSubstitions::iterator replacement_it = + substitutions.find(*it); + if (replacement_it == substitutions.end()) { + new_piece = *it; + } else { + // Found something to substitute. + SandboxSubstring& replacement = replacement_it->second; + switch (replacement.type()) { + case SandboxSubstring::PLAIN: + new_piece = replacement.value(); + break; + + case SandboxSubstring::LITERAL: + QuotePlainString(replacement.value(), &new_piece); + break; + + case SandboxSubstring::REGEX: + QuoteStringForRegex(replacement.value(), &new_piece); + break; + } + } + output_string_length += new_piece.size(); + processed_sandbox_pieces.push_back(new_piece); + } + + // Build final output string. + final_sandbox_profile_str->reserve(output_string_length); + + for (std::vector<std::string>::iterator it = processed_sandbox_pieces.begin(); + it != processed_sandbox_pieces.end(); + ++it) { + final_sandbox_profile_str->append(*it); + } + return true; +} + + +// Turns on the OS X sandbox for this process. + +// static +bool Sandbox::EnableSandbox(SandboxProcessType sandbox_type, + const FilePath& allowed_dir) { + // Sanity - currently only SANDBOX_TYPE_UTILITY supports a directory being + // passed in. + if (sandbox_type != SANDBOX_TYPE_UTILITY) { + DCHECK(allowed_dir.empty()) + << "Only SANDBOX_TYPE_UTILITY allows a custom directory parameter."; + } + + NSString* sandbox_data = LoadSandboxTemplate(sandbox_type); + if (!sandbox_data) { + return false; + } + + SandboxVariableSubstitions substitutions; + if (!allowed_dir.empty()) { + // Add the sandbox commands necessary to access the given directory. + // Note: this function must be called before PostProcessSandboxProfile() + // since the string it inserts contains variables that need substitution. + NSString* allowed_dir_sandbox_command = + BuildAllowDirectoryAccessSandboxString(allowed_dir, &substitutions); + + if (allowed_dir_sandbox_command) { // May be nil if function fails. + sandbox_data = [sandbox_data + stringByReplacingOccurrencesOfString:@";ENABLE_DIRECTORY_ACCESS" + withString:allowed_dir_sandbox_command]; + } + } + + NSMutableArray* tokens_to_remove = [NSMutableArray array]; + + // Enable verbose logging if enabled on the command line. (See common.sb + // for details). + const CommandLine* command_line = CommandLine::ForCurrentProcess(); + bool enable_logging = + command_line->HasSwitch(switches::kEnableSandboxLogging);; + if (enable_logging) { + [tokens_to_remove addObject:@";ENABLE_LOGGING"]; + } + + bool snow_leopard_or_higher; + GetOSVersion(&snow_leopard_or_higher); + + // Without this, the sandbox will print a message to the system log every + // time it denies a request. This floods the console with useless spew. The + // (with no-log) syntax is only supported on 10.6+ + if (snow_leopard_or_higher && !enable_logging) { + substitutions["DISABLE_SANDBOX_DENIAL_LOGGING"] = + SandboxSubstring("(with no-log)"); + } else { + substitutions["DISABLE_SANDBOX_DENIAL_LOGGING"] = SandboxSubstring(""); + } + + // Splice the path of the user's home directory into the sandbox profile + // (see renderer.sb for details). + std::string home_dir = base::SysNSStringToUTF8(NSHomeDirectory()); + + FilePath home_dir_canonical(home_dir); + GetCanonicalSandboxPath(&home_dir_canonical); + + substitutions["USER_HOMEDIR_AS_LITERAL"] = + SandboxSubstring(home_dir_canonical.value(), + SandboxSubstring::LITERAL); + + if (snow_leopard_or_higher) { + // 10.6-only Sandbox rules. + [tokens_to_remove addObject:@";10.6_ONLY"]; + } else { + // Sandbox rules only for versions before 10.6. + [tokens_to_remove addObject:@";BEFORE_10.6"]; + } + + // All information needed to assemble the final profile has been collected. + // Merge it all together. + std::string final_sandbox_profile_str; + if (!PostProcessSandboxProfile(sandbox_data, tokens_to_remove, substitutions, + &final_sandbox_profile_str)) { + return false; + } + + // Initialize sandbox. + char* error_buff = NULL; + int error = sandbox_init(final_sandbox_profile_str.c_str(), 0, &error_buff); + bool success = (error == 0 && error_buff == NULL); + LOG_IF(FATAL, !success) << "Failed to initialize sandbox: " + << error + << " " + << error_buff; + sandbox_free_error(error_buff); + return success; +} + +// static +void Sandbox::GetCanonicalSandboxPath(FilePath* path) { + int fd = HANDLE_EINTR(open(path->value().c_str(), O_RDONLY)); + if (fd < 0) { + PLOG(FATAL) << "GetCanonicalSandboxPath() failed for: " + << path->value(); + return; + } + file_util::ScopedFD file_closer(&fd); + + FilePath::CharType canonical_path[MAXPATHLEN]; + if (HANDLE_EINTR(fcntl(fd, F_GETPATH, canonical_path)) != 0) { + PLOG(FATAL) << "GetCanonicalSandboxPath() failed for: " + << path->value(); + return; + } + + *path = FilePath(canonical_path); +} + +} // namespace sandbox diff --git a/content/common/sandbox_mac_diraccess_unittest.mm b/content/common/sandbox_mac_diraccess_unittest.mm new file mode 100644 index 0000000..8aad63b --- /dev/null +++ b/content/common/sandbox_mac_diraccess_unittest.mm @@ -0,0 +1,304 @@ +// Copyright (c) 2011 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/test/multiprocess_test.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "content/common/sandbox_mac.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + +namespace { + +static const char* kSandboxAccessPathKey = "sandbox_dir"; +static const char* kDeniedSuffix = "_denied"; + +} // namespace + +// Tests need to be in the same namespace as the sandbox::Sandbox class to be +// useable with FRIEND_TEST() declaration. +namespace sandbox { + +class MacDirAccessSandboxTest : public base::MultiProcessTest { + public: + bool CheckSandbox(const std::string& directory_to_try) { + setenv(kSandboxAccessPathKey, directory_to_try.c_str(), 1); + base::ProcessHandle child_process = SpawnChild("mac_sandbox_path_access", + false); + int code = -1; + if (!base::WaitForExitCode(child_process, &code)) { + LOG(WARNING) << "base::WaitForExitCode failed"; + return false; + } + return code == 0; + } +}; + +TEST_F(MacDirAccessSandboxTest, StringEscape) { + 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(Sandbox::QuotePlainString(in, &out)); + EXPECT_EQ(string_escape_cases[i].escaped, out); + } +} + +TEST_F(MacDirAccessSandboxTest, RegexEscape) { + 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(Sandbox::QuoteStringForRegex(fail_string, &out)); + EXPECT_TRUE(Sandbox::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(Sandbox::QuoteStringForRegex("}", &out)); // } == 0x7D == 125 + EXPECT_FALSE(Sandbox::QuoteStringForRegex("~", &out)); // ~ == 0x7E == 126 + EXPECT_FALSE( + Sandbox::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(Sandbox::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(Sandbox::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(MacDirAccessSandboxTest, SandboxAccess) { + using file_util::CreateDirectory; + + 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 . + Sandbox::GetCanonicalSandboxPath(&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(CreateDirectory(sandbox_dir)); + ScopedDirectory cleanup_sandbox(&sandbox_dir); + + // Create a sibling directory of the sandbox dir, whose name has sandbox dir + // as a substring but to which access is denied. + std::string sibling_sandbox_dir_name_denied = + std::string(sandbox_dir_cases[i]) + kDeniedSuffix; + FilePath sibling_sandbox_dir = tmp_dir.Append( + sibling_sandbox_dir_name_denied.c_str()); + ASSERT_TRUE(CreateDirectory(sibling_sandbox_dir)); + ScopedDirectory cleanup_sandbox_sibling(&sibling_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 a single directory. + NSString *sandbox_profile = + @"(version 1)" \ + "(deny default)" \ + "(allow signal (target self))" \ + "(allow sysctl-read)" \ + ";ENABLE_DIRECTORY_ACCESS"; + + std::string allowed_dir(sandbox_allowed_dir); + Sandbox::SandboxVariableSubstitions substitutions; + NSString* allow_dir_sandbox_code = + Sandbox::BuildAllowDirectoryAccessSandboxString( + FilePath(sandbox_allowed_dir), + &substitutions); + sandbox_profile = [sandbox_profile + stringByReplacingOccurrencesOfString:@";ENABLE_DIRECTORY_ACCESS" + withString:allow_dir_sandbox_code]; + + std::string final_sandbox_profile_str; + if (!Sandbox::PostProcessSandboxProfile(sandbox_profile, + [NSArray array], + substitutions, + &final_sandbox_profile_str)) { + LOG(ERROR) << "Call to PostProcessSandboxProfile() failed"; + return -1; + } + + // Enable Sandbox. + char* error_buff = NULL; + int error = sandbox_init(final_sandbox_profile_str.c_str(), 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(); + FilePath allowed_parent_dir = allowed_dir_path.DirName(); + std::string tricky_filename = basename.value() + "123"; + FilePath denied_file2 = allowed_parent_dir.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; + } + + // Test that we deny access to a sibling of the sandboxed directory whose + // name has the sandboxed directory name as a substring. e.g. if the sandbox + // directory is /foo/baz then test /foo/baz_denied. + { + struct stat tmp_stat_info; + std::string denied_sibling = + std::string(sandbox_allowed_dir) + kDeniedSuffix; + if (stat(denied_sibling.c_str(), &tmp_stat_info) > 0) { + PLOG(ERROR) << "Sandbox breach: was able to stat (" + << denied_sibling.c_str() + << ")"; + return -1; + } + } + + // Test that we can stat parent directories of the "allowed" directory. + { + struct stat tmp_stat_info; + if (stat(allowed_parent_dir.value().c_str(), &tmp_stat_info) != 0) { + PLOG(ERROR) << "Sandbox overly restrictive: unable to stat (" + << allowed_parent_dir.value() + << ")"; + return -1; + } + } + + // Test that we can't stat files outside the "allowed" directory. + { + struct stat tmp_stat_info; + if (stat(denied_file1.value().c_str(), &tmp_stat_info) > 0) { + PLOG(ERROR) << "Sandbox breach: was able to stat (" + << denied_file1.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; +} + +} // namespace sandbox diff --git a/content/common/sandbox_mac_fontloading_unittest.mm b/content/common/sandbox_mac_fontloading_unittest.mm new file mode 100644 index 0000000..7df1bfe --- /dev/null +++ b/content/common/sandbox_mac_fontloading_unittest.mm @@ -0,0 +1,179 @@ +// Copyright (c) 2011 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/file_util.h" +#include "base/logging.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/scoped_ptr.h" +#include "base/shared_memory.h" +#include "content/common/font_loader_mac.h" +#include "content/common/sandbox_mac_unittest_helper.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using sandboxtest::MacSandboxTest; +using sandbox::Sandbox; + +bool CGFontFromFontContainer(ATSFontContainerRef container, CGFontRef* out) { + // Count the number of fonts that were loaded. + ItemCount fontCount = 0; + OSStatus err = ATSFontFindFromContainer(container, kATSOptionFlagsDefault, 0, + NULL, &fontCount); + + if (err != noErr || fontCount < 1) { + return false; + } + + // Load font from container. + ATSFontRef font_ref_ats = 0; + ATSFontFindFromContainer(container, kATSOptionFlagsDefault, 1, + &font_ref_ats, NULL); + + if (!font_ref_ats) { + return false; + } + + // Convert to cgFont. + CGFontRef font_ref_cg = CGFontCreateWithPlatformFont(&font_ref_ats); + + if (!font_ref_cg) { + return false; + } + + *out = font_ref_cg; + return true; +} + +class ScopedFontContainer { + public: + explicit ScopedFontContainer(ATSFontContainerRef ref) + : container_ref(ref) {} + + ~ScopedFontContainer() { + ATSFontDeactivate(container_ref, NULL, kATSOptionFlagsDefault); + } + + ATSFontContainerRef container_ref; +}; + +class FontLoadingTestCase : public sandboxtest::MacSandboxTestCase { + public: + FontLoadingTestCase() : font_data_length_(-1) {} + virtual bool BeforeSandboxInit(); + virtual bool SandboxedTest(); + private: + scoped_ptr<base::SharedMemory> font_shmem_; + size_t font_data_length_; +}; +REGISTER_SANDBOX_TEST_CASE(FontLoadingTestCase); + + +// Load raw font data into shared memory object. +bool FontLoadingTestCase::BeforeSandboxInit() { + std::string font_data; + if (!file_util::ReadFileToString(FilePath(test_data_.c_str()), &font_data)) { + LOG(ERROR) << "Failed to read font data from file (" << test_data_ << ")"; + return false; + } + + font_data_length_ = font_data.length(); + if (font_data_length_ <= 0) { + LOG(ERROR) << "No font data: " << font_data_length_; + return false; + } + + font_shmem_.reset(new base::SharedMemory); + if (!font_shmem_.get()) { + LOG(ERROR) << "Failed to create shared memory object."; + return false; + } + + if (!font_shmem_->CreateAndMapAnonymous(font_data_length_)) { + LOG(ERROR) << "SharedMemory::Create failed"; + return false; + } + + memcpy(font_shmem_->memory(), font_data.c_str(), font_data_length_); + if (!font_shmem_->Unmap()) { + LOG(ERROR) << "SharedMemory::Unmap failed"; + return false; + } + return true; +} + +bool FontLoadingTestCase::SandboxedTest() { + base::SharedMemoryHandle shmem_handle; + if (!font_shmem_->ShareToProcess(NULL, &shmem_handle)) { + LOG(ERROR) << "SharedMemory::ShareToProcess failed"; + return false; + } + + ATSFontContainerRef font_container; + if (!FontLoader::ATSFontContainerFromBuffer(shmem_handle, font_data_length_, + &font_container)) { + LOG(ERROR) << "Call to CreateCGFontFromBuffer() failed"; + return false; + } + + // Unload the font container when done. + ScopedFontContainer scoped_unloader(font_container); + + CGFontRef cg_font_ref; + if (!CGFontFromFontContainer(font_container, &cg_font_ref)) { + LOG(ERROR) << "CGFontFromFontContainer failed"; + return false; + } + + if (!cg_font_ref) { + LOG(ERROR) << "Got NULL CGFontRef"; + return false; + } + base::mac::ScopedCFTypeRef<CGFontRef> cgfont(cg_font_ref); + + CTFontRef ct_font_ref = + CTFontCreateWithGraphicsFont(cgfont.get(), 16.0, NULL, NULL); + base::mac::ScopedCFTypeRef<CTFontRef> ctfont(ct_font_ref); + + if (!ct_font_ref) { + LOG(ERROR) << "CTFontCreateWithGraphicsFont() failed"; + return false; + } + + // Do something with the font to make sure it's loaded. + CGFloat cap_height = CTFontGetCapHeight(ct_font_ref); + + if (cap_height <= 0.0) { + LOG(ERROR) << "Got bad value for CTFontGetCapHeight " << cap_height; + return false; + } + + return true; +} + +TEST_F(MacSandboxTest, FontLoadingTest) { + FilePath temp_file_path; + FILE* temp_file = file_util::CreateAndOpenTemporaryFile(&temp_file_path); + ASSERT_TRUE(temp_file); + file_util::ScopedFILE temp_file_closer(temp_file); + + base::SharedMemory font_data; + uint32 font_data_size; + NSFont* srcFont = [NSFont fontWithName:@"Geeza Pro" size:16.0]; + EXPECT_TRUE(FontLoader::LoadFontIntoBuffer(srcFont, + &font_data, &font_data_size)); + EXPECT_GT(font_data_size, 0U); + + file_util::WriteFileDescriptor(fileno(temp_file), + static_cast<const char *>(font_data.memory()), font_data_size); + + ASSERT_TRUE(RunTestInSandbox(Sandbox::SANDBOX_TYPE_RENDERER, + "FontLoadingTestCase", temp_file_path.value().c_str())); + temp_file_closer.reset(); + ASSERT_TRUE(file_util::Delete(temp_file_path, false)); +} + +} // namespace diff --git a/content/common/sandbox_mac_system_access_unittest.mm b/content/common/sandbox_mac_system_access_unittest.mm new file mode 100644 index 0000000..46d7596 --- /dev/null +++ b/content/common/sandbox_mac_system_access_unittest.mm @@ -0,0 +1,96 @@ +// Copyright (c) 2011 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 "content/common/sandbox_mac.h" +#include "content/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/content/common/sandbox_mac_unittest_helper.h b/content/common/sandbox_mac_unittest_helper.h new file mode 100644 index 0000000..f228678 --- /dev/null +++ b/content/common/sandbox_mac_unittest_helper.h @@ -0,0 +1,117 @@ +// Copyright (c) 2011 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 CONTENT_COMMON_SANDBOX_MAC_UNITTEST_HELPER_H_ +#define CONTENT_COMMON_SANDBOX_MAC_UNITTEST_HELPER_H_ +#pragma once + +#include "base/test/multiprocess_test.h" +#include "content/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 +// its 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 its 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 base::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::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: + virtual ~MacSandboxTestCase() {} + + // 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(); + + // 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); + + protected: + std::string test_data_; +}; + +// Plumbing to support the REGISTER_SANDBOX_TEST_CASE macro. +namespace internal { + +// Register a test case with a given name. +void AddSandboxTestCase(const char* test_name, MacSandboxTestCase* test_class); + +// 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) { + AddSandboxTestCase(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 // CONTENT_COMMON_SANDBOX_MAC_UNITTEST_HELPER_H_ diff --git a/content/common/sandbox_mac_unittest_helper.mm b/content/common/sandbox_mac_unittest_helper.mm new file mode 100644 index 0000000..6b485de --- /dev/null +++ b/content/common/sandbox_mac_unittest_helper.mm @@ -0,0 +1,170 @@ +// Copyright (c) 2011 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 "content/common/sandbox_mac_unittest_helper.h" + +extern "C" { +#include <sandbox.h> +} + +#include <map> + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "content/common/sandbox_mac.h" +#include "testing/multiprocess_func_list.h" + +using sandbox::Sandbox; + +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 { + +typedef std::map<std::string,MacSandboxTestCase*> SandboxTestMap; + +// A function that returns a common map from string -> test case class. +SandboxTestMap& GetSandboxTestMap() { + static SandboxTestMap test_map; + return test_map; +} + +void AddSandboxTestCase(const char* test_name, MacSandboxTestCase* test_class) { + SandboxTestMap& test_map = GetSandboxTestMap(); + if (test_map.find(test_name) != test_map.end()) { + LOG(ERROR) << "Trying to register duplicate test" << test_name; + NOTREACHED(); + } + test_map[test_name] = test_class; +} + +} // 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("mac_sandbox_test_runner", + false); + int code = -1; + if (!base::WaitForExitCode(child_process, &code)) { + LOG(WARNING) << "base::WaitForExitCode failed"; + return false; + } + return code == 0; +} + +bool MacSandboxTestCase::BeforeSandboxInit() { + return true; +} + +void MacSandboxTestCase::SetTestData(const char* test_data) { + test_data_ = test_data; +} + +// 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(sandbox_type); + + 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 |