View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.logging.security;
19  
20  import java.io.FilePermission;
21  import java.security.Permission;
22  import java.security.Permissions;
23  
24  /**
25   * Custom implementation of a security manager, so we can control the
26   * security environment for tests in this package.
27   */
28  public class MockSecurityManager extends SecurityManager {
29  
30      private static final Permission setSecurityManagerPerm =
31          new RuntimePermission("setSecurityManager");
32      private final Permissions permissions = new Permissions();
33  
34      private int untrustedCodeCount;
35  
36      public MockSecurityManager() {
37          permissions.add(setSecurityManagerPerm);
38      }
39  
40      /**
41       * Define the set of permissions to be granted to classes in the o.a.c.l package,
42       * but NOT to unit-test classes in o.a.c.l.security package.
43       */
44      public void addPermission(final Permission p) {
45          permissions.add(p);
46      }
47  
48      @Override
49      public void checkPermission(final Permission p) throws SecurityException {
50          if (setSecurityManagerPerm.implies(p)) {
51              // ok, allow this; we don't want to block any calls to setSecurityManager
52              // otherwise this custom security manager cannot be reset to the original.
53              // System.out.println("setSecurityManager: granted");
54              return;
55          }
56  
57          // Allow read-only access to files, as this is needed to load classes!
58          // Ideally, we would limit this to just .class and .jar files.
59          if (p instanceof FilePermission) {
60            final FilePermission fp = (FilePermission) p;
61            if (fp.getActions().equals("read")) {
62              // System.out.println("Permit read of files");
63              return;
64            }
65          }
66  
67          System.out.println("\n\ntesting permission:" + p.getClass() + ":"+ p);
68  
69          final Exception e = new Exception();
70          e.fillInStackTrace();
71          final StackTraceElement[] stack = e.getStackTrace();
72  
73          // scan the call stack from most recent to oldest.
74          // start at 1 to skip the entry in the stack for this method
75          for(int i=1; i<stack.length; ++i) {
76              final String cname = stack[i].getClassName();
77              System.out.println("" + i + ":" + stack[i].getClassName() +
78                "." + stack[i].getMethodName() + ":" + stack[i].getLineNumber());
79  
80              if (cname.equals("java.util.logging.Handler") && stack[i].getMethodName().equals("setLevel")) {
81                  // LOGGING CODE CAUSES ACCESSCONTROLEXCEPTION
82                  // http://www-01.ibm.com/support/docview.wss?uid=swg1IZ51152
83                  return;
84              }
85  
86              if (cname.equals("java.util.logging.Level") && stack[i].getMethodName().equals("getLocalizedLevelName")) {
87                  // LOGGING-156: OpenJDK 1.7 JULI code (java.util.logging.Level#getLocalizedLevelName)
88                  // calls ResourceBundle#getBundle() without using AccessController#doPrivileged()
89                  // requiring RuntimePermission: "accessClassInPackage.sun.util.logging.resources"
90                  return;
91              }
92  
93              if (cname.equals("java.security.AccessController")) {
94                  // Presumably method name equals "doPrivileged"
95                  //
96                  // The previous iteration of this loop verified that the
97                  // PrivilegedAction.run method associated with this
98                  // doPrivileged method call had the right permissions,
99                  // so we just return here. Effectively, the method invoking
100                 // doPrivileged asserted that it checked the input params
101                 // and found them safe, and that code is trusted, so we
102                 // don't need to check the trust level of code higher in
103                 // the call stack.
104                 System.out.println("Access controller found: returning");
105                 return;
106             }
107             if (cname.startsWith("java.")
108                 || cname.startsWith("javax.")
109                 || cname.startsWith("junit.")
110                 || cname.startsWith("org.apache.tools.ant.")
111                 || cname.startsWith("sun.")) {
112                 // Code in these packages is trusted if the caller is trusted.
113                 //
114                 // TODO: maybe check class is loaded via system loader or similar rather
115                 // than checking name? Trusted domains may be different in alternative
116                 // jvms..
117             } else if (cname.startsWith("org.apache.commons.logging.security")) {
118                 // this is the unit test code; treat this like an untrusted client
119                 // app that is using JCL
120                 ++untrustedCodeCount;
121                 System.out.println("Untrusted code [test] found");
122                 throw new SecurityException("Untrusted code [test] found");
123             } else if (cname.startsWith("org.apache.commons.logging.")) {
124                 if (!permissions.implies(p)) {
125                     System.out.println("Permission refused:" + p.getClass() + ":" + p);
126                     throw new SecurityException("Permission refused:" + p.getClass() + ":" + p);
127                 }
128                 // Code here is trusted if the caller is trusted
129                 System.out.println("Permission in allowed set for JCL class");
130             } else {
131                 // we found some code that is not trusted to perform this operation.
132                 System.out.println("Unexpected code: permission refused:" + p.getClass() + ":" + p);
133                 throw new SecurityException("Unexpected code: permission refused:" + p.getClass() + ":" + p);
134             }
135         }
136     }
137 
138     /**
139      * This returns the number of times that a check of a permission failed
140      * due to stack-walking tracing up into untrusted code. Any non-zero
141      * value indicates a bug in JCL, that is, a situation where code was not
142      * correctly wrapped in an AccessController block. The result of such a
143      * bug is that signing JCL is not sufficient to allow JCL to perform
144      * the operation; the caller would need to be signed too.
145      */
146     public int getUntrustedCodeCount() {
147         return untrustedCodeCount;
148     }
149 }