1/* 2 * Copyright (C) 2010 Apple Inc. All rights reserved. 3 * Copyright (C) 2011 Google Inc. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 16 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 18 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 24 * THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#import "config.h" 28#import "PluginProcess.h" 29 30#if ENABLE(NETSCAPE_PLUGIN_API) 31 32#import "ArgumentCoders.h" 33#import "NetscapePlugin.h" 34#import "PluginProcessCreationParameters.h" 35#import "PluginProcessProxyMessages.h" 36#import "PluginProcessShim.h" 37#import "PluginSandboxProfile.h" 38#import "SandboxInitializationParameters.h" 39#import "SandboxUtilities.h" 40#import <CoreAudio/AudioHardware.h> 41#import <WebCore/LocalizedStrings.h> 42#import <WebKitSystemInterface.h> 43#import <dlfcn.h> 44#import <objc/runtime.h> 45#import <sysexits.h> 46#import <wtf/HashSet.h> 47#import <wtf/NeverDestroyed.h> 48 49using namespace WebCore; 50 51const CFStringRef kLSPlugInBundleIdentifierKey = CFSTR("LSPlugInBundleIdentifierKey"); 52 53// These values were chosen to match default NSURLCache sizes at the time of this writing. 54const NSUInteger pluginMemoryCacheSize = 512000; 55const NSUInteger pluginDiskCacheSize = 20000000; 56 57namespace WebKit { 58 59class FullscreenWindowTracker { 60 WTF_MAKE_NONCOPYABLE(FullscreenWindowTracker); 61 62public: 63 FullscreenWindowTracker() { } 64 65 template<typename T> void windowShown(T window); 66 template<typename T> void windowHidden(T window); 67 68private: 69 typedef HashSet<void*> WindowSet; 70 WindowSet m_windows; 71}; 72 73static bool rectCoversAnyScreen(NSRect rect) 74{ 75 for (NSScreen *screen in [NSScreen screens]) { 76 if (NSContainsRect(rect, [screen frame])) 77 return YES; 78 } 79 return NO; 80} 81 82#ifndef NP_NO_CARBON 83static bool windowCoversAnyScreen(WindowRef window) 84{ 85 HIRect bounds; 86 HIWindowGetBounds(window, kWindowStructureRgn, kHICoordSpaceScreenPixel, &bounds); 87 88 // Convert to Cocoa-style screen coordinates that use a Y offset relative to the zeroth screen's origin. 89 bounds.origin.y = NSHeight([(NSScreen *)[[NSScreen screens] objectAtIndex:0] frame]) - CGRectGetMaxY(bounds); 90 91 return rectCoversAnyScreen(NSRectFromCGRect(bounds)); 92} 93#endif 94 95static bool windowCoversAnyScreen(NSWindow* window) 96{ 97 return rectCoversAnyScreen([window frame]); 98} 99 100template<typename T> void FullscreenWindowTracker::windowShown(T window) 101{ 102 // If this window is already visible then there is nothing to do. 103 WindowSet::iterator it = m_windows.find(window); 104 if (it != m_windows.end()) 105 return; 106 107 // If the window is not full-screen then we're not interested in it. 108 if (!windowCoversAnyScreen(window)) 109 return; 110 111 bool windowSetWasEmpty = m_windows.isEmpty(); 112 113 m_windows.add(window); 114 115 // If this is the first full screen window to be shown, notify the UI process. 116 if (windowSetWasEmpty) 117 PluginProcess::shared().setFullscreenWindowIsShowing(true); 118} 119 120template<typename T> void FullscreenWindowTracker::windowHidden(T window) 121{ 122 // If this is not a window that we're tracking then there is nothing to do. 123 WindowSet::iterator it = m_windows.find(window); 124 if (it == m_windows.end()) 125 return; 126 127 m_windows.remove(it); 128 129 // If this was the last full screen window that was visible, notify the UI process. 130 if (m_windows.isEmpty()) 131 PluginProcess::shared().setFullscreenWindowIsShowing(false); 132} 133 134static FullscreenWindowTracker& fullscreenWindowTracker() 135{ 136 static NeverDestroyed<FullscreenWindowTracker> fullscreenWindowTracker; 137 return fullscreenWindowTracker; 138} 139 140#if defined(__i386__) 141 142static pthread_once_t shouldCallRealDebuggerOnce = PTHREAD_ONCE_INIT; 143static bool isUserbreakSet = false; 144 145static void initShouldCallRealDebugger() 146{ 147 char* var = getenv("USERBREAK"); 148 149 if (var) 150 isUserbreakSet = atoi(var); 151} 152 153static bool shouldCallRealDebugger() 154{ 155 pthread_once(&shouldCallRealDebuggerOnce, initShouldCallRealDebugger); 156 157 return isUserbreakSet; 158} 159 160static bool isWindowActive(WindowRef windowRef, bool& result) 161{ 162#ifndef NP_NO_CARBON 163 if (NetscapePlugin* plugin = NetscapePlugin::netscapePluginFromWindow(windowRef)) { 164 result = plugin->isWindowActive(); 165 return true; 166 } 167#endif 168 return false; 169} 170 171static UInt32 getCurrentEventButtonState() 172{ 173#ifndef NP_NO_CARBON 174 return NetscapePlugin::buttonState(); 175#else 176 ASSERT_NOT_REACHED(); 177 return 0; 178#endif 179} 180 181static void carbonWindowShown(WindowRef window) 182{ 183#ifndef NP_NO_CARBON 184 fullscreenWindowTracker().windowShown(window); 185#endif 186} 187 188static void carbonWindowHidden(WindowRef window) 189{ 190#ifndef NP_NO_CARBON 191 fullscreenWindowTracker().windowHidden(window); 192#endif 193} 194 195static bool openCFURLRef(CFURLRef url, int32_t& status, CFURLRef* launchedURL) 196{ 197 String launchedURLString; 198 if (!PluginProcess::shared().openURL(URL(url).string(), status, launchedURLString)) 199 return false; 200 201 if (!launchedURLString.isNull() && launchedURL) 202 *launchedURL = URL(ParsedURLString, launchedURLString).createCFURL().leakRef(); 203 return true; 204} 205 206#endif 207 208static void setModal(bool modalWindowIsShowing) 209{ 210 PluginProcess::shared().setModalWindowIsShowing(modalWindowIsShowing); 211} 212 213static unsigned modalCount = 0; 214 215static void beginModal() 216{ 217#pragma clang diagnostic push 218#pragma clang diagnostic ignored "-Wdeprecated-declarations" 219 // Make sure to make ourselves the front process 220 ProcessSerialNumber psn; 221 GetCurrentProcess(&psn); 222 SetFrontProcess(&psn); 223#pragma clang diagnostic pop 224 225 if (!modalCount++) 226 setModal(true); 227} 228 229static void endModal() 230{ 231 if (!--modalCount) 232 setModal(false); 233} 234 235static IMP NSApplication_RunModalForWindow; 236 237static NSInteger replacedRunModalForWindow(id self, SEL _cmd, NSWindow* window) 238{ 239 beginModal(); 240 NSInteger result = ((NSInteger (*)(id, SEL, NSWindow *))NSApplication_RunModalForWindow)(self, _cmd, window); 241 endModal(); 242 243 return result; 244} 245 246#if defined(__i386__) 247static void initializeShim() 248{ 249 // Initialize the shim for 32-bit only. 250 const PluginProcessShimCallbacks callbacks = { 251 shouldCallRealDebugger, 252 isWindowActive, 253 getCurrentEventButtonState, 254 beginModal, 255 endModal, 256 carbonWindowShown, 257 carbonWindowHidden, 258 setModal, 259 openCFURLRef, 260 }; 261 262 PluginProcessShimInitializeFunc initFunc = reinterpret_cast<PluginProcessShimInitializeFunc>(dlsym(RTLD_DEFAULT, "WebKitPluginProcessShimInitialize")); 263 initFunc(callbacks); 264} 265#endif 266 267static IMP NSConcreteTask_launch; 268 269static void replacedNSConcreteTask_launch(NSTask *self, SEL _cmd) 270{ 271 String launchPath = self.launchPath; 272 273 Vector<String> arguments; 274 arguments.reserveInitialCapacity(self.arguments.count); 275 for (NSString *argument in self.arguments) 276 arguments.uncheckedAppend(argument); 277 278 if (PluginProcess::shared().launchProcess(launchPath, arguments)) 279 return; 280 281 NSConcreteTask_launch(self, _cmd); 282} 283 284static NSRunningApplication *(*NSWorkspace_launchApplicationAtURL_options_configuration_error)(NSWorkspace *, SEL, NSURL *, NSWorkspaceLaunchOptions, NSDictionary *, NSError **); 285 286static NSRunningApplication *replacedNSWorkspace_launchApplicationAtURL_options_configuration_error(NSWorkspace *self, SEL _cmd, NSURL *url, NSWorkspaceLaunchOptions options, NSDictionary *configuration, NSError **error) 287{ 288 Vector<String> arguments; 289 if (NSArray *argumentsArray = [configuration objectForKey:NSWorkspaceLaunchConfigurationArguments]) { 290 if ([argumentsArray isKindOfClass:[NSArray array]]) { 291 for (NSString *argument in argumentsArray) { 292 if ([argument isKindOfClass:[NSString class]]) 293 arguments.append(argument); 294 } 295 } 296 } 297 298 if (PluginProcess::shared().launchApplicationAtURL(URL(url).string(), arguments)) { 299 if (error) 300 *error = nil; 301 return nil; 302 } 303 304 return NSWorkspace_launchApplicationAtURL_options_configuration_error(self, _cmd, url, options, configuration, error); 305} 306 307static BOOL (*NSWorkspace_openFile)(NSWorkspace *, SEL, NSString *); 308 309static BOOL replacedNSWorkspace_openFile(NSWorkspace *self, SEL _cmd, NSString *fullPath) 310{ 311 if (PluginProcess::shared().openFile(fullPath)) 312 return true; 313 314 return NSWorkspace_openFile(self, _cmd, fullPath); 315} 316 317static void initializeCocoaOverrides() 318{ 319 // Override -[NSConcreteTask launch:] 320 Method launchMethod = class_getInstanceMethod(objc_getClass("NSConcreteTask"), @selector(launch)); 321 NSConcreteTask_launch = method_setImplementation(launchMethod, reinterpret_cast<IMP>(replacedNSConcreteTask_launch)); 322 323 // Override -[NSWorkspace launchApplicationAtURL:options:configuration:error:] 324 Method launchApplicationAtURLOptionsConfigurationErrorMethod = class_getInstanceMethod(objc_getClass("NSWorkspace"), @selector(launchApplicationAtURL:options:configuration:error:)); 325 NSWorkspace_launchApplicationAtURL_options_configuration_error = reinterpret_cast<NSRunningApplication *(*)(NSWorkspace *, SEL, NSURL *, NSWorkspaceLaunchOptions, NSDictionary *, NSError **)>(method_setImplementation(launchApplicationAtURLOptionsConfigurationErrorMethod, reinterpret_cast<IMP>(replacedNSWorkspace_launchApplicationAtURL_options_configuration_error))); 326 327 // Override -[NSWorkspace openFile:] 328 Method openFileMethod = class_getInstanceMethod(objc_getClass("NSWorkspace"), @selector(openFile:)); 329 NSWorkspace_openFile = reinterpret_cast<BOOL (*)(NSWorkspace *, SEL, NSString *)>(method_setImplementation(openFileMethod, reinterpret_cast<IMP>(replacedNSWorkspace_openFile))); 330 331 // Override -[NSApplication runModalForWindow:] 332 Method runModalForWindowMethod = class_getInstanceMethod(objc_getClass("NSApplication"), @selector(runModalForWindow:)); 333 NSApplication_RunModalForWindow = method_setImplementation(runModalForWindowMethod, reinterpret_cast<IMP>(replacedRunModalForWindow)); 334 335 NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; 336 337 // Track when any Cocoa window is about to be be shown. 338 id orderOnScreenObserver = [defaultCenter addObserverForName:WKWindowWillOrderOnScreenNotification() 339 object:nil 340 queue:nil 341 usingBlock:^(NSNotification *notification) { fullscreenWindowTracker().windowShown([notification object]); }]; 342 // Track when any Cocoa window is about to be hidden. 343 id orderOffScreenObserver = [defaultCenter addObserverForName:WKWindowWillOrderOffScreenNotification() 344 object:nil 345 queue:nil 346 usingBlock:^(NSNotification *notification) { fullscreenWindowTracker().windowHidden([notification object]); }]; 347 348 // Leak the two observers so that they observe notifications for the lifetime of the process. 349 CFRetain(orderOnScreenObserver); 350 CFRetain(orderOffScreenObserver); 351} 352 353void PluginProcess::setModalWindowIsShowing(bool modalWindowIsShowing) 354{ 355 parentProcessConnection()->send(Messages::PluginProcessProxy::SetModalWindowIsShowing(modalWindowIsShowing), 0); 356} 357 358void PluginProcess::setFullscreenWindowIsShowing(bool fullscreenWindowIsShowing) 359{ 360 parentProcessConnection()->send(Messages::PluginProcessProxy::SetFullscreenWindowIsShowing(fullscreenWindowIsShowing), 0); 361} 362 363bool PluginProcess::launchProcess(const String& launchPath, const Vector<String>& arguments) 364{ 365 bool result; 366 if (!parentProcessConnection()->sendSync(Messages::PluginProcessProxy::LaunchProcess(launchPath, arguments), Messages::PluginProcessProxy::LaunchProcess::Reply(result), 0)) 367 return false; 368 369 return result; 370} 371 372bool PluginProcess::launchApplicationAtURL(const String& urlString, const Vector<String>& arguments) 373{ 374 bool result = false; 375 if (!parentProcessConnection()->sendSync(Messages::PluginProcessProxy::LaunchApplicationAtURL(urlString, arguments), Messages::PluginProcessProxy::LaunchProcess::Reply(result), 0)) 376 return false; 377 378 return result; 379} 380 381bool PluginProcess::openURL(const String& urlString, int32_t& status, String& launchedURLString) 382{ 383 bool result; 384 if (!parentProcessConnection()->sendSync(Messages::PluginProcessProxy::OpenURL(urlString), Messages::PluginProcessProxy::OpenURL::Reply(result, status, launchedURLString), 0)) 385 return false; 386 387 return result; 388} 389 390bool PluginProcess::openFile(const String& fullPath) 391{ 392 bool result; 393 if (!parentProcessConnection()->sendSync(Messages::PluginProcessProxy::OpenFile(fullPath), Messages::PluginProcessProxy::OpenFile::Reply(result), 0)) 394 return false; 395 396 return result; 397} 398 399static void muteAudio(void) 400{ 401 AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyProcessIsAudible, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; 402 UInt32 propertyData = 0; 403 OSStatus result = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, 0, sizeof(UInt32), &propertyData); 404 ASSERT_UNUSED(result, result == noErr); 405} 406 407void PluginProcess::platformInitializePluginProcess(const PluginProcessCreationParameters& parameters) 408{ 409 m_compositingRenderServerPort = parameters.acceleratedCompositingPort.port(); 410 if (parameters.processType == PluginProcessTypeSnapshot) 411 muteAudio(); 412 413 [NSURLCache setSharedURLCache:adoptNS([[NSURLCache alloc] 414 initWithMemoryCapacity:pluginMemoryCacheSize 415 diskCapacity:pluginDiskCacheSize 416 diskPath:m_nsurlCacheDirectory]).get()]; 417} 418 419void PluginProcess::platformInitializeProcess(const ChildProcessInitializationParameters& parameters) 420{ 421#if defined(__i386__) 422 // Initialize the shim. 423 initializeShim(); 424#endif 425 426 // Initialize Cocoa overrides. 427 initializeCocoaOverrides(); 428 429 // FIXME: It would be better to proxy SetCursor calls over to the UI process instead of 430 // allowing plug-ins to change the mouse cursor at any time. 431 WKEnableSettingCursorWhenInBackground(); 432 433 RetainPtr<CFURLRef> pluginURL = adoptCF(CFURLCreateWithFileSystemPath(0, m_pluginPath.createCFString().get(), kCFURLPOSIXPathStyle, false)); 434 if (!pluginURL) 435 return; 436 437 RetainPtr<CFBundleRef> pluginBundle = adoptCF(CFBundleCreate(kCFAllocatorDefault, pluginURL.get())); 438 if (!pluginBundle) 439 return; 440 441 m_pluginBundleIdentifier = CFBundleGetIdentifier(pluginBundle.get()); 442 443 // FIXME: Workaround for Java not liking its plugin process to be supressed - <rdar://problem/14267843> 444 if (m_pluginBundleIdentifier == "com.oracle.java.JavaAppletPlugin") 445 (new UserActivity("com.oracle.java.JavaAppletPlugin"))->start(); 446} 447 448void PluginProcess::initializeProcessName(const ChildProcessInitializationParameters& parameters) 449{ 450 NSString *applicationName = [NSString stringWithFormat:WEB_UI_STRING("%@ (%@ Internet plug-in)", "visible name of the plug-in host process. The first argument is the plug-in name and the second argument is the application name."), [[(NSString *)m_pluginPath lastPathComponent] stringByDeletingPathExtension], (NSString *)parameters.uiProcessName]; 451 WKSetVisibleApplicationName((CFStringRef)applicationName); 452#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 453 if (!m_pluginBundleIdentifier.isEmpty()) 454 WKSetApplicationInformationItem(kLSPlugInBundleIdentifierKey, m_pluginBundleIdentifier.createCFString().get()); 455#endif 456} 457 458void PluginProcess::initializeSandbox(const ChildProcessInitializationParameters& parameters, SandboxInitializationParameters& sandboxParameters) 459{ 460 // PluginProcess may already be sandboxed if its parent process was sandboxed, and launched a child process instead of an XPC service. 461 // This is generally not expected, however we currently always spawn a child process to create a MIME type preferences file. 462 if (processIsSandboxed(getpid())) { 463 RELEASE_ASSERT(!parameters.connectionIdentifier.xpcConnection); 464 RELEASE_ASSERT(processIsSandboxed(getppid())); 465 return; 466 } 467 468 bool parentIsSandboxed = parameters.connectionIdentifier.xpcConnection && processIsSandboxed(xpc_connection_get_pid(parameters.connectionIdentifier.xpcConnection.get())); 469 470 if (parameters.extraInitializationData.get("disable-sandbox") == "1") { 471 if (parentIsSandboxed) { 472 WTFLogAlways("Sandboxed processes may not disable plug-in sandbox, terminating %s.", parameters.clientIdentifier.utf8().data()); 473 exit(EX_OSERR); 474 } 475 return; 476 } 477 478 String sandboxProfile = pluginSandboxProfile(m_pluginBundleIdentifier); 479 if (sandboxProfile.isEmpty()) { 480 if (parentIsSandboxed) { 481 WTFLogAlways("Sandboxed processes may only use sandboxed plug-ins, terminating %s.", parameters.clientIdentifier.utf8().data()); 482 exit(EX_OSERR); 483 } 484 return; 485 } 486 487 sandboxParameters.setSandboxProfile(sandboxProfile); 488 489 char temporaryDirectory[PATH_MAX]; 490 if (!confstr(_CS_DARWIN_USER_TEMP_DIR, temporaryDirectory, sizeof(temporaryDirectory))) { 491 WTFLogAlways("PluginProcess: couldn't retrieve system temporary directory path: %d\n", errno); 492 exit(EX_OSERR); 493 } 494 495 char cacheDirectory[PATH_MAX]; 496 if (!confstr(_CS_DARWIN_USER_CACHE_DIR, cacheDirectory, sizeof(cacheDirectory))) { 497 WTFLogAlways("PluginProcess: couldn't retrieve system cache directory path: %d\n", errno); 498 exit(EX_OSERR); 499 } 500 501 m_nsurlCacheDirectory = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:cacheDirectory length:strlen(temporaryDirectory)] stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]]; 502 if (![[NSFileManager defaultManager] createDirectoryAtURL:[NSURL fileURLWithPath:m_nsurlCacheDirectory isDirectory:YES] withIntermediateDirectories:YES attributes:nil error:nil]) { 503 WTFLogAlways("PluginProcess: couldn't create NSURL cache directory '%s'\n", temporaryDirectory); 504 exit(EX_OSERR); 505 } 506 507 if (strlcpy(temporaryDirectory, [[[[NSFileManager defaultManager] stringWithFileSystemRepresentation:temporaryDirectory length:strlen(temporaryDirectory)] stringByAppendingPathComponent:@"WebKitPlugin-XXXXXX"] fileSystemRepresentation], sizeof(temporaryDirectory)) >= sizeof(temporaryDirectory) 508 || !mkdtemp(temporaryDirectory)) { 509 WTFLogAlways("PluginProcess: couldn't create private temporary directory '%s'\n", temporaryDirectory); 510 exit(EX_OSERR); 511 } 512 513 sandboxParameters.setSystemDirectorySuffix([[[[NSFileManager defaultManager] stringWithFileSystemRepresentation:temporaryDirectory length:strlen(temporaryDirectory)] lastPathComponent] fileSystemRepresentation]); 514 515 sandboxParameters.addPathParameter("PLUGIN_PATH", m_pluginPath); 516 sandboxParameters.addPathParameter("NSURL_CACHE_DIR", m_nsurlCacheDirectory); 517 518 [[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSUseRemoteSavePanel" : @YES }]; 519 520 ChildProcess::initializeSandbox(parameters, sandboxParameters); 521} 522 523 524void PluginProcess::stopRunLoop() 525{ 526 ChildProcess::stopNSAppRunLoop(); 527} 528 529} // namespace WebKit 530 531#endif // ENABLE(NETSCAPE_PLUGIN_API) 532