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