1 /* 2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.internal.module; 27 28 import java.lang.invoke.MethodHandles; 29 import java.lang.reflect.Module; 30 import java.net.URL; 31 import java.security.AccessController; 32 import java.security.CodeSource; 33 import java.security.PrivilegedAction; 34 import java.security.ProtectionDomain; 35 import java.util.HashMap; 36 import java.util.HashSet; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.Objects; 40 import java.util.Set; 41 import java.util.WeakHashMap; 42 import java.util.function.Supplier; 43 import java.util.stream.Collectors; 44 45 /** 46 * Supports logging of access to members of API packages that are exported or 47 * opened via backdoor mechanisms to code in unnamed modules. 48 */ 49 50 public final class IllegalAccessLogger { 51 52 // true to print stack trace 53 private static final boolean PRINT_STACK_TRACE; 54 static { 55 String s = System.getProperty("sun.reflect.debugModuleAccessChecks"); 56 PRINT_STACK_TRACE = "access".equals(s); 57 } 58 59 private static final StackWalker STACK_WALKER 60 = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); 61 62 // the maximum number of frames to capture 63 private static final int MAX_STACK_FRAMES = 32; 64 65 // lock to avoid interference when printing stack traces 66 private static final Object OUTPUT_LOCK = new Object(); 67 68 // caller -> usages 69 private final Map<Class<?>, Set<Usage>> callerToUsages = new WeakHashMap<>(); 70 71 // module -> (package name -> CLI option) 72 private final Map<Module, Map<String, String>> exported; 73 private final Map<Module, Map<String, String>> opened; 74 75 private IllegalAccessLogger(Map<Module, Map<String, String>> exported, 76 Map<Module, Map<String, String>> opened) { 77 this.exported = deepCopy(exported); 78 this.opened = deepCopy(opened); 79 } 80 81 /** 82 * Returns that a Builder that is seeded with the packages known to this logger. 83 */ 84 public Builder toBuilder() { 85 return new Builder(exported, opened); 86 } 87 88 /** 89 * Logs access to the member of a target class by a caller class if the class 90 * is in a package that is exported via a backdoor mechanism. 91 * 92 * The {@code whatSupplier} supplies the message that describes the member. 93 */ 94 public void logIfExportedByBackdoor(Class<?> caller, 95 Class<?> target, 96 Supplier<String> whatSupplier) { 97 Map<String, String> packages = exported.get(target.getModule()); 98 if (packages != null) { 99 String how = packages.get(target.getPackageName()); 100 if (how != null) { 101 log(caller, whatSupplier.get(), how); 102 } 103 } 104 } 105 106 /** 107 * Logs access to the member of a target class by a caller class if the class 108 * is in a package that is opened via a backdoor mechanism. 109 * 110 * The {@code what} parameter supplies the message that describes the member. 111 */ 112 public void logIfOpenedByBackdoor(Class<?> caller, 113 Class<?> target, 114 Supplier<String> whatSupplier) { 115 Map<String, String> packages = opened.get(target.getModule()); 116 if (packages != null) { 117 String how = packages.get(target.getPackageName()); 118 if (how != null) { 119 log(caller, whatSupplier.get(), how); 120 } 121 } 122 } 123 124 /** 125 * Logs access by a caller class. The {@code what} parameter describes 126 * the member is accessed, the {@code how} parameter is the means by which 127 * access is allocated (CLI option for example). 128 */ 129 private void log(Class<?> caller, String what, String how) { 130 log(caller, what, () -> { 131 PrivilegedAction<ProtectionDomain> pa = caller::getProtectionDomain; 132 CodeSource cs = AccessController.doPrivileged(pa).getCodeSource(); 133 URL url = (cs != null) ? cs.getLocation() : null; 134 String source = caller.getName(); 135 if (url != null) 136 source += " (" + url + ")"; 137 return "WARNING: Illegal access by " + source + " to " + what 138 + " (permitted by " + how + ")"; 139 }); 140 } 141 142 143 /** 144 * Logs access to caller class if the class is in a package that is opened via 145 * a backdoor mechanism. 146 */ 147 public void logIfOpenedByBackdoor(MethodHandles.Lookup caller, Class<?> target) { 148 Map<String, String> packages = opened.get(target.getModule()); 149 if (packages != null) { 150 String how = packages.get(target.getPackageName()); 151 if (how != null) { 152 log(caller.lookupClass(), target.getName(), () -> 153 "WARNING: Illegal access using Lookup on " + caller.lookupClass() 154 + " to " + target + " (permitted by " + how + ")"); 155 } 156 } 157 } 158 159 /** 160 * Log access by a caller. The {@code what} parameter describes the class or 161 * member that is being accessed. The {@code msgSupplier} supplies the log 162 * message. 163 * 164 * To reduce output, this method only logs the access if it hasn't been seen 165 * previously. "Seen previously" is implemented as a map of caller class -> Usage, 166 * where a Usage is the "what" and a hash of the stack trace. The map has weak 167 * keys so it can be expunged when the caller is GC'ed/unloaded. 168 */ 169 private void log(Class<?> caller, String what, Supplier<String> msgSupplier) { 170 // stack trace without the top-most frames in java.base 171 List<StackWalker.StackFrame> stack = STACK_WALKER.walk(s -> 172 s.dropWhile(this::isJavaBase) 173 .limit(MAX_STACK_FRAMES) 174 .collect(Collectors.toList()) 175 ); 176 177 // check if the access has already been recorded 178 Usage u = new Usage(what, hash(stack)); 179 boolean firstUsage; 180 synchronized (this) { 181 firstUsage = callerToUsages.computeIfAbsent(caller, k -> new HashSet<>()).add(u); 182 } 183 184 // log message if first usage 185 if (firstUsage) { 186 String msg = msgSupplier.get(); 187 if (PRINT_STACK_TRACE) { 188 synchronized (OUTPUT_LOCK) { 189 System.err.println(msg); 190 stack.forEach(f -> System.err.println("\tat " + f)); 191 } 192 } else { 193 System.err.println(msg); 194 } 195 } 196 } 197 198 private static class Usage { 199 private final String what; 200 private final int stack; 201 Usage(String what, int stack) { 202 this.what = what; 203 this.stack = stack; 204 } 205 @Override 206 public int hashCode() { 207 return what.hashCode() ^ stack; 208 } 209 @Override 210 public boolean equals(Object ob) { 211 if (ob instanceof Usage) { 212 Usage that = (Usage)ob; 213 return what.equals(that.what) && stack == (that.stack); 214 } else { 215 return false; 216 } 217 } 218 } 219 220 /** 221 * Returns true if the stack frame is for a class in java.base. 222 */ 223 private boolean isJavaBase(StackWalker.StackFrame frame) { 224 Module caller = frame.getDeclaringClass().getModule(); 225 return "java.base".equals(caller.getName()); 226 } 227 228 /** 229 * Computes a hash code for the give stack frames. The hash code is based 230 * on the class, method name, and BCI. 231 */ 232 private int hash(List<StackWalker.StackFrame> stack) { 233 int hash = 0; 234 for (StackWalker.StackFrame frame : stack) { 235 hash = (31 * hash) + Objects.hash(frame.getDeclaringClass(), 236 frame.getMethodName(), 237 frame.getByteCodeIndex()); 238 } 239 return hash; 240 } 241 242 // system-wide IllegalAccessLogger 243 private static volatile IllegalAccessLogger logger; 244 245 /** 246 * Sets the system-wide IllegalAccessLogger 247 */ 248 public static void setIllegalAccessLogger(IllegalAccessLogger l) { 249 if (l.exported.isEmpty() && l.opened.isEmpty()) { 250 logger = null; 251 } else { 252 logger = l; 253 } 254 } 255 256 /** 257 * Returns the system-wide IllegalAccessLogger or {@code null} if there is 258 * no logger. 259 */ 260 public static IllegalAccessLogger illegalAccessLogger() { 261 return logger; 262 } 263 264 /** 265 * A builder for IllegalAccessLogger objects. 266 */ 267 public static class Builder { 268 private Map<Module, Map<String, String>> exported; 269 private Map<Module, Map<String, String>> opened; 270 271 public Builder() { } 272 273 public Builder(Map<Module, Map<String, String>> exported, 274 Map<Module, Map<String, String>> opened) { 275 this.exported = deepCopy(exported); 276 this.opened = deepCopy(opened); 277 } 278 279 public void logAccessToExportedPackage(Module m, String pn, String how) { 280 if (!m.isExported(pn)) { 281 if (exported == null) 282 exported = new HashMap<>(); 283 exported.computeIfAbsent(m, k -> new HashMap<>()).putIfAbsent(pn, how); 284 } 285 } 286 287 public void logAccessToOpenPackage(Module m, String pn, String how) { 288 // opens implies exported at run-time. 289 logAccessToExportedPackage(m, pn, how); 290 291 if (!m.isOpen(pn)) { 292 if (opened == null) 293 opened = new HashMap<>(); 294 opened.computeIfAbsent(m, k -> new HashMap<>()).putIfAbsent(pn, how); 295 } 296 } 297 298 /** 299 * Builds the logger. 300 */ 301 public IllegalAccessLogger build() { 302 return new IllegalAccessLogger(exported, opened); 303 } 304 } 305 306 307 static Map<Module, Map<String, String>> deepCopy(Map<Module, Map<String, String>> map) { 308 if (map == null || map.isEmpty()) { 309 return new HashMap<>(); 310 } else { 311 Map<Module, Map<String, String>> newMap = new HashMap<>(); 312 for (Map.Entry<Module, Map<String, String>> e : map.entrySet()) { 313 newMap.put(e.getKey(), new HashMap<>(e.getValue())); 314 } 315 return newMap; 316 } 317 } 318 }