1 // Copyright (C) 2001-2003 Jon A. Maxwell (JAM) 2 // 3 // This library is free software; you can redistribute it and/or 4 // modify it under the terms of the GNU Lesser General Public 5 // License as published by the Free Software Foundation; either 6 // version 2.1 of the License, or (at your option) any later version. 7 // 8 // This library is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 // Lesser General Public License for more details. 12 // 13 // You should have received a copy of the GNU Lesser General Public 14 // License along with this library; if not, write to the Free Software 15 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 16 17 package net.sourceforge.jnlp.runtime; 18 19 import static net.sourceforge.jnlp.runtime.Translator.R; 20 21 import java.awt.Window; 22 import java.net.SocketPermission; 23 import java.security.AccessControlException; 24 import java.security.Permission; 25 26 import javax.swing.JWindow; 27 28 import net.sourceforge.jnlp.security.SecurityDialogs.AccessType; 29 import net.sourceforge.jnlp.services.ServiceUtil; 30 import net.sourceforge.jnlp.util.logging.OutputController; 31 import net.sourceforge.jnlp.util.WeakList; 32 import sun.awt.AWTSecurityManager; 33 import sun.awt.AppContext; 34 35 /** 36 * Security manager for JNLP environment. This security manager 37 * cannot be replaced as it always denies attempts to replace the 38 * security manager or policy. 39 * <p> 40 * The JNLP security manager tracks windows created by an 41 * application, allowing those windows to be disposed when the 42 * application exits but the JVM does not. If security is not 43 * enabled then the first application to call System.exit will 44 * halt the JVM. 45 * </p> 46 * 47 * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author 48 * @version $Revision: 1.17 $ 49 */ 50 class JNLPSecurityManager extends AWTSecurityManager { 51 52 // todo: some apps like JDiskReport can close the VM even when 53 // an exit class is set - fix! 54 55 // todo: create an event dispatch thread for each application, 56 // so that the context classloader doesn't have to be switched 57 // to the foreground application (the currently the approach 58 // since some apps need their classloader as event dispatch 59 // thread's context classloader). 60 61 // todo: use a custom Permission object to identify the current 62 // application in an AccessControlContext by setting a side 63 // effect in its implies method. Use a custom 64 // AllPermissions-like permission to do this for apps granted 65 // all permissions (but investigate whether this will nuke 66 // the all-permission optimizations in the JRE). 67 68 // todo: does not exit app if close button pressed on JFrame 69 // with CLOSE_ON_EXIT (or whatever) set; if doesn't exit, use an 70 // WindowListener to catch WindowClosing event, then if exit is 71 // called immediately afterwards from AWT thread. 72 73 // todo: deny all permissions to applications that should have 74 // already been 'shut down' by closing their resources and 75 // interrupt the threads if operating in a shared-VM (exit class 76 // set). Deny will probably will slow checks down a lot though. 77 78 // todo: weak remember last getProperty application and 79 // re-install properties if another application calls, or find 80 // another way for different apps to have different properties 81 // in java.lang.Sytem with the same names. 82 83 /** only class that can exit the JVM, if set */ 84 private Object exitClass = null; 85 86 /** this exception prevents exiting the JVM */ 87 private SecurityException closeAppEx = // making here prevents huge stack traces 88 new SecurityException(R("RShutdown")); 89 90 /** weak list of windows created */ 91 private WeakList<Window> weakWindows = new WeakList<Window>(); 92 93 /** weak list of applications corresponding to window list */ 94 private WeakList<ApplicationInstance> weakApplications = 95 new WeakList<ApplicationInstance>(); 96 97 /** Sets whether or not exit is allowed (in the context of the plugin, this is always false) */ 98 private boolean exitAllowed = true; 99 100 /** 101 * The AppContext of the main application (netx). We need to store this here 102 * so we can return this when no code from an external application is 103 * running on the thread 104 */ 105 private AppContext mainAppContext; 106 107 /** 108 * Creates a JNLP SecurityManager. 109 */ JNLPSecurityManager()110 JNLPSecurityManager() { 111 // this has the side-effect of creating the Swing shared Frame 112 // owner. Since no application is running at this time, it is 113 // not added to any window list when checkTopLevelWindow is 114 // called for it (and not disposed). 115 116 if (!JNLPRuntime.isHeadless()) { 117 new JWindow().getOwner(); 118 } 119 120 mainAppContext = AppContext.getAppContext(); 121 } 122 123 /** 124 * Returns whether the exit class is present on the stack, or 125 * true if no exit class is set. 126 */ isExitClass()127 public boolean isExitClass() { 128 return isExitClass(getClassContext()); 129 } 130 131 /** 132 * Returns whether the exit class is present on the stack, or 133 * true if no exit class is set. 134 */ isExitClass(Class stack[])135 private boolean isExitClass(Class stack[]) { 136 if (exitClass == null) { 137 return true; 138 } 139 140 for (int i = 0; i < stack.length; i++) { 141 if (stack[i] == exitClass) { 142 return true; 143 } 144 } 145 146 return false; 147 } 148 149 /** 150 * Set the exit class, which is the only class that can exit the 151 * JVM; if not set then any class can exit the JVM. 152 * 153 * @param exitClass the exit class 154 * @throws IllegalStateException if the exit class is already set 155 */ setExitClass(Class<?> exitClass)156 public void setExitClass(Class<?> exitClass) throws IllegalStateException { 157 if (this.exitClass != null) { 158 throw new IllegalStateException(R("RExitTaken")); 159 } 160 161 this.exitClass = exitClass; 162 } 163 164 /** 165 * Return the current Application, or null if none can be 166 * determined. 167 */ getApplication()168 protected ApplicationInstance getApplication() { 169 return getApplication(Thread.currentThread(), getClassContext(), 0); 170 } 171 172 /** 173 * Return the application the opened the specified window (only 174 * call from event dispatch thread). 175 */ getApplication(Window window)176 protected ApplicationInstance getApplication(Window window) { 177 for (int i = weakWindows.size(); i-- > 0;) { 178 Window w = weakWindows.get(i); 179 if (w == null) { 180 weakWindows.remove(i); 181 weakApplications.remove(i); 182 } 183 184 if (w == window) { 185 return weakApplications.get(i); 186 } 187 } 188 189 return null; 190 } 191 192 /** 193 * Return the current Application, or null. 194 */ getApplication(Thread thread, Class<?> stack[], int maxDepth)195 protected ApplicationInstance getApplication(Thread thread, Class<?> stack[], int maxDepth) { 196 ClassLoader cl; 197 JNLPClassLoader jnlpCl; 198 199 cl = thread.getContextClassLoader(); 200 while (cl != null) { 201 jnlpCl = getJnlpClassLoader(cl); 202 if (jnlpCl != null && jnlpCl.getApplication() != null) { 203 return jnlpCl.getApplication(); 204 } 205 cl = cl.getParent(); 206 } 207 208 if (maxDepth <= 0) { 209 maxDepth = stack.length; 210 } 211 212 // this needs to be tightened up 213 for (int i = 0; i < stack.length && i < maxDepth; i++) { 214 cl = stack[i].getClassLoader(); 215 while (cl != null) { 216 jnlpCl = getJnlpClassLoader(cl); 217 if (jnlpCl != null && jnlpCl.getApplication() != null) { 218 return jnlpCl.getApplication(); 219 } 220 cl = cl.getParent(); 221 } 222 } 223 return null; 224 } 225 226 /** 227 * Returns the JNLPClassLoader associated with the given ClassLoader, or 228 * null. 229 * @param cl a ClassLoader 230 * @return JNLPClassLoader or null 231 */ getJnlpClassLoader(ClassLoader cl)232 private JNLPClassLoader getJnlpClassLoader(ClassLoader cl) { 233 // Since we want to deal with JNLPClassLoader, extract it if this 234 // is a codebase loader 235 if (cl instanceof JNLPClassLoader.CodeBaseClassLoader) { 236 cl = ((JNLPClassLoader.CodeBaseClassLoader) cl).getParentJNLPClassLoader(); 237 } 238 239 if (cl instanceof JNLPClassLoader) { 240 JNLPClassLoader loader = (JNLPClassLoader) cl; 241 return loader; 242 } 243 244 return null; 245 } 246 247 /** 248 * Returns the application's thread group if the application can 249 * be determined; otherwise returns super.getThreadGroup() 250 */ 251 @Override getThreadGroup()252 public ThreadGroup getThreadGroup() { 253 ApplicationInstance app = getApplication(); 254 if (app == null) { 255 return super.getThreadGroup(); 256 } 257 258 return app.getThreadGroup(); 259 } 260 261 /** 262 * Throws a SecurityException if the permission is denied, 263 * otherwise return normally. This method always denies 264 * permission to change the security manager or policy. 265 */ 266 @Override checkPermission(Permission perm)267 public void checkPermission(Permission perm) { 268 String name = perm.getName(); 269 270 // Enable this manually -- it'll produce too much output for -verbose 271 // otherwise. 272 // if (true) 273 // OutputController.getLogger().log("Checking permission: " + perm.toString()); 274 275 if (!JNLPRuntime.isWebstartApplication() && 276 ("setPolicy".equals(name) || "setSecurityManager".equals(name))) { 277 throw new SecurityException(R("RCantReplaceSM")); 278 } 279 280 try { 281 // deny all permissions to stopped applications 282 // The call to getApplication() below might not work if an 283 // application hasn't been fully initialized yet. 284 // if (JNLPRuntime.isDebug()) { 285 // if (!"getClassLoader".equals(name)) { 286 // ApplicationInstance app = getApplication(); 287 // if (app != null && !app.isRunning()) 288 // throw new SecurityException(R("RDenyStopped")); 289 // } 290 // } 291 292 super.checkPermission(perm); 293 } catch (SecurityException ex) { 294 OutputController.getLogger().log("Denying permission: " + perm); 295 throw ex; 296 } 297 } 298 299 /** 300 * Asks the user whether or not to grant permission. 301 * @param perm the permission to be granted 302 * @return true if the permission was granted, false otherwise. 303 */ askPermission(Permission perm)304 private boolean askPermission(Permission perm) { 305 306 ApplicationInstance app = getApplication(); 307 if (app != null && !app.isSigned()) { 308 if (perm instanceof SocketPermission 309 && ServiceUtil.checkAccess(AccessType.NETWORK, perm.getName())) { 310 return true; 311 } 312 } 313 314 return false; 315 } 316 317 /** 318 * Adds a permission to the JNLPClassLoader. 319 * @param perm the permission to add to the JNLPClassLoader 320 */ addPermission(Permission perm)321 private void addPermission(Permission perm) { 322 if (JNLPRuntime.getApplication().getClassLoader() instanceof JNLPClassLoader) { 323 324 JNLPClassLoader cl = (JNLPClassLoader) JNLPRuntime.getApplication().getClassLoader(); 325 cl.addPermission(perm); 326 if (JNLPRuntime.isDebug()) { 327 if (cl.getSecurity() == null) { 328 if (cl.getPermissions(null).implies(perm)){ 329 OutputController.getLogger().log(OutputController.Level.ERROR_ALL, "Added permission: " + perm.toString()); 330 } else { 331 OutputController.getLogger().log(OutputController.Level.ERROR_ALL, "Unable to add permission: " + perm.toString()); 332 } 333 } else { 334 OutputController.getLogger().log(OutputController.Level.ERROR_ALL, "Cannot get permissions for null codesource when classloader security is not null"); 335 } 336 } 337 } else { 338 OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "Unable to add permission: " + perm + ", classloader not JNLP."); 339 } 340 } 341 342 /** 343 * Checks whether the window can be displayed without an applet 344 * warning banner, and adds the window to the list of windows to 345 * be disposed when the calling application exits. 346 */ 347 @Override checkTopLevelWindow(Object window)348 public boolean checkTopLevelWindow(Object window) { 349 ApplicationInstance app = getApplication(); 350 351 // remember window -> application mapping for focus, close on exit 352 if (app != null && window instanceof Window) { 353 Window w = (Window) window; 354 355 OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "SM: app: " + app.getTitle() + " is adding a window: " + window + " with appContext " + AppContext.getAppContext()); 356 357 weakWindows.add(w); // for mapping window -> app 358 weakApplications.add(app); 359 360 app.addWindow(w); 361 } 362 363 // todo: set awt.appletWarning to custom message 364 // todo: logo on with glass pane on JFrame/JWindow? 365 366 return super.checkTopLevelWindow(window); 367 } 368 369 /** 370 * Checks whether the caller can exit the system. This method 371 * identifies whether the caller is a real call to Runtime.exec 372 * and has special behavior when returning from this method 373 * would exit the JVM and an exit class is set: if the caller is 374 * not the exit class then the calling application will be 375 * stopped and its resources destroyed (when possible), and an 376 * exception will be thrown to prevent the JVM from shutting 377 * down. 378 * <p> 379 * Calls not from Runtime.exit or with no exit class set will 380 * behave normally, and the exit class can always exit the JVM. 381 * </p> 382 */ 383 @Override checkExit(int status)384 public void checkExit(int status) { 385 386 // applets are not allowed to exit, but the plugin main class (primordial loader) is 387 Class stack[] = getClassContext(); 388 if (!exitAllowed) { 389 for (int i = 0; i < stack.length; i++) { 390 if (stack[i].getClassLoader() != null) { 391 throw new AccessControlException("Applets may not call System.exit()"); 392 } 393 } 394 } 395 396 super.checkExit(status); 397 398 boolean realCall = (stack[1] == Runtime.class); 399 400 if (isExitClass(stack)) { 401 return; 402 } // to Runtime.exit or fake call to see if app has permission 403 404 // not called from Runtime.exit() 405 if (!realCall) { 406 // apps that can't exit should think they can exit normally 407 super.checkExit(status); 408 return; 409 } 410 411 // but when they really call, stop only the app instead of the JVM 412 ApplicationInstance app = getApplication(Thread.currentThread(), stack, 0); 413 if (app == null) { 414 throw new SecurityException(R("RExitNoApp")); 415 } 416 417 app.destroy(); 418 419 throw closeAppEx; 420 } 421 disableExit()422 protected void disableExit() { 423 exitAllowed = false; 424 } 425 426 /** 427 * This returns the appropriate {@link AppContext}. Hooks in AppContext 428 * check if the current {@link SecurityManager} is an instance of 429 * AWTSecurityManager and if so, call this method to give it a chance to 430 * return the appropriate appContext based on the application that is 431 * running. 432 * <p> 433 * This can be called from any thread (possibly a swing thread) to find out 434 * the AppContext for the thread (which may correspond to a particular 435 * applet). 436 * </p> 437 */ 438 @Override getAppContext()439 public AppContext getAppContext() { 440 ApplicationInstance app = getApplication(); 441 if (app == null) { 442 /* 443 * if we cannot find an application based on the code on the stack, 444 * then assume it is the main application 445 */ 446 return mainAppContext; 447 } else { 448 return app.getAppContext(); 449 } 450 451 } 452 453 /** 454 * Tests if a client can get access to the AWT event queue. This version allows 455 * complete access to the EventQueue for its own AppContext-specific EventQueue. 456 * 457 * FIXME there are probably huge security implications for this. Eg: 458 * http://hg.openjdk.java.net/jdk7/awt/jdk/rev/8022709a306d 459 * 460 * @exception SecurityException if the caller does not have 461 * permission to accesss the AWT event queue. 462 */ 463 @Override checkAwtEventQueueAccess()464 public void checkAwtEventQueueAccess() { 465 /* 466 * this is the templace of the code that should allow applets access to 467 * eventqueues 468 */ 469 470 // AppContext appContext = AppContext.getAppContext(); 471 // ApplicationInstance instance = getApplication(); 472 473 // if ((appContext == mainAppContext) && (instance != null)) { 474 // If we're about to allow access to the main EventQueue, 475 // and anything untrusted is on the class context stack, 476 // disallow access. 477 super.checkAwtEventQueueAccess(); 478 // } 479 } 480 481 } 482