1 /* 2 * Copyright (c) 2016, 2018, 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.jfr.internal; 27 28 import java.io.File; 29 import java.io.FileNotFoundException; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.io.RandomAccessFile; 33 import java.io.Reader; 34 import java.lang.reflect.Constructor; 35 import java.lang.reflect.Field; 36 import java.lang.reflect.Method; 37 import java.lang.reflect.ReflectPermission; 38 import java.nio.channels.FileChannel; 39 import java.nio.channels.ReadableByteChannel; 40 import java.nio.file.FileVisitResult; 41 import java.nio.file.Files; 42 import java.nio.file.Path; 43 import java.nio.file.Paths; 44 import java.nio.file.SimpleFileVisitor; 45 import java.nio.file.StandardOpenOption; 46 import java.nio.file.attribute.BasicFileAttributes; 47 import java.security.AccessControlContext; 48 import java.security.AccessController; 49 import java.security.Permission; 50 import java.security.PrivilegedAction; 51 import java.security.PrivilegedActionException; 52 import java.security.PrivilegedExceptionAction; 53 import java.util.ArrayList; 54 import java.util.Iterator; 55 import java.util.List; 56 import java.util.Objects; 57 import java.util.PropertyPermission; 58 import java.util.concurrent.Callable; 59 60 import jdk.internal.misc.Unsafe; 61 import jdk.internal.module.Modules; 62 import jdk.jfr.Event; 63 import jdk.jfr.FlightRecorder; 64 import jdk.jfr.FlightRecorderListener; 65 import jdk.jfr.FlightRecorderPermission; 66 import jdk.jfr.Recording; 67 68 /** 69 * Contains JFR code that does 70 * {@link AccessController#doPrivileged(PrivilegedAction)} 71 */ 72 public final class SecuritySupport { 73 private final static Unsafe unsafe = Unsafe.getUnsafe(); 74 private final static Module JFR_MODULE = Event.class.getModule(); 75 public final static SafePath JFC_DIRECTORY = getPathInProperty("java.home", "lib/jfr"); 76 77 static final SafePath USER_HOME = getPathInProperty("user.home", null); 78 static final SafePath JAVA_IO_TMPDIR = getPathInProperty("java.io.tmpdir", null); 79 80 final static class SecureRecorderListener implements FlightRecorderListener { 81 82 private final AccessControlContext context; 83 private final FlightRecorderListener changeListener; 84 85 SecureRecorderListener(AccessControlContext context, FlightRecorderListener changeListener) { 86 this.context = Objects.requireNonNull(context); 87 this.changeListener = Objects.requireNonNull(changeListener); 88 } 89 90 @Override 91 public void recordingStateChanged(Recording recording) { 92 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 93 try { 94 changeListener.recordingStateChanged(recording); 95 } catch (Throwable t) { 96 // Prevent malicious user to propagate exception callback in the wrong context 97 Logger.log(LogTag.JFR, LogLevel.WARN, "Unexpected exception in listener " + changeListener.getClass()+ " at recording state change"); 98 } 99 return null; 100 }, context); 101 } 102 103 @Override 104 public void recorderInitialized(FlightRecorder recorder) { 105 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 106 try { 107 changeListener.recorderInitialized(recorder); 108 } catch (Throwable t) { 109 // Prevent malicious user to propagate exception callback in the wrong context 110 Logger.log(LogTag.JFR, LogLevel.WARN, "Unexpected exception in listener " + changeListener.getClass()+ " when initializing FlightRecorder"); 111 } 112 return null; 113 }, context); 114 } 115 116 public FlightRecorderListener getChangeListener() { 117 return changeListener; 118 } 119 } 120 121 private static final class DirectoryCleaner extends SimpleFileVisitor<Path> { 122 @Override 123 public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException { 124 Files.delete(path); 125 return FileVisitResult.CONTINUE; 126 } 127 128 @Override 129 public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 130 if (exc != null) { 131 throw exc; 132 } 133 Files.delete(dir); 134 return FileVisitResult.CONTINUE; 135 } 136 } 137 138 /** 139 * Path created by the default file provider,and not 140 * a malicious provider. 141 * 142 */ 143 public static final class SafePath { 144 private final Path path; 145 private final String text; 146 147 public SafePath(Path p) { 148 // sanitize 149 text = p.toString(); 150 path = Paths.get(text); 151 } 152 153 public SafePath(String path) { 154 this(Paths.get(path)); 155 } 156 157 public Path toPath() { 158 return path; 159 } 160 161 public String toString() { 162 return text; 163 } 164 } 165 166 private interface RunnableWithCheckedException { 167 public void run() throws Exception; 168 } 169 170 private interface CallableWithoutCheckException<T> { 171 public T call(); 172 } 173 174 private static <U> U doPrivilegedIOWithReturn(Callable<U> function) throws IOException { 175 try { 176 return AccessController.doPrivileged(new PrivilegedExceptionAction<U>() { 177 @Override 178 public U run() throws Exception { 179 return function.call(); 180 } 181 }, null); 182 } catch (PrivilegedActionException e) { 183 Throwable t = e.getCause(); 184 if (t instanceof IOException) { 185 throw (IOException) t; 186 } 187 throw new IOException("Unexpected error during I/O operation. " + t.getMessage(), t); 188 } 189 } 190 191 private static void doPriviligedIO(RunnableWithCheckedException function) throws IOException { 192 doPrivilegedIOWithReturn(() -> { 193 function.run(); 194 return null; 195 }); 196 } 197 198 private static void doPrivileged(Runnable function, Permission... perms) { 199 AccessController.doPrivileged(new PrivilegedAction<Void>() { 200 @Override 201 public Void run() { 202 function.run(); 203 return null; 204 } 205 }, null, perms); 206 } 207 208 private static void doPrivileged(Runnable function) { 209 AccessController.doPrivileged(new PrivilegedAction<Void>() { 210 @Override 211 public Void run() { 212 function.run(); 213 return null; 214 } 215 }); 216 } 217 218 private static <T> T doPrivilegedWithReturn(CallableWithoutCheckException<T> function, Permission... perms) { 219 return AccessController.doPrivileged(new PrivilegedAction<T>() { 220 @Override 221 public T run() { 222 return function.call(); 223 } 224 }, null, perms); 225 } 226 227 public static List<SafePath> getPredefinedJFCFiles() { 228 List<SafePath> list = new ArrayList<>(); 229 try { 230 Iterator<Path> pathIterator = doPrivilegedIOWithReturn(() -> { 231 return Files.newDirectoryStream(JFC_DIRECTORY.toPath(), "*").iterator(); 232 }); 233 while (pathIterator.hasNext()) { 234 Path path = pathIterator.next(); 235 if (path.toString().endsWith(".jfc")) { 236 list.add(new SafePath(path)); 237 } 238 } 239 } catch (IOException ioe) { 240 Logger.log(LogTag.JFR, LogLevel.WARN, "Could not access .jfc-files in " + JFC_DIRECTORY + ", " + ioe.getMessage()); 241 } 242 return list; 243 } 244 245 static void makeVisibleToJFR(Class<?> clazz) { 246 Module classModule = clazz.getModule(); 247 Modules.addReads(JFR_MODULE, classModule); 248 if (clazz.getPackage() != null) { 249 String packageName = clazz.getPackage().getName(); 250 Modules.addExports(classModule, packageName, JFR_MODULE); 251 Modules.addOpens(classModule, packageName, JFR_MODULE); 252 } 253 } 254 255 /** 256 * Adds a qualified export of the internal.jdk.jfr.internal.handlers package 257 * (for EventHandler) 258 */ 259 static void addHandlerExport(Class<?> clazz) { 260 Modules.addExports(JFR_MODULE, Utils.HANDLERS_PACKAGE_NAME, clazz.getModule()); 261 } 262 263 public static void registerEvent(Class<? extends Event> eventClass) { 264 doPrivileged(() -> FlightRecorder.register(eventClass), new FlightRecorderPermission(Utils.REGISTER_EVENT)); 265 } 266 267 static boolean getBooleanProperty(String propertyName) { 268 return doPrivilegedWithReturn(() -> Boolean.getBoolean(propertyName), new PropertyPermission(propertyName, "read")); 269 } 270 271 private static SafePath getPathInProperty(String prop, String subPath) { 272 return doPrivilegedWithReturn(() -> { 273 String path = System.getProperty(prop); 274 if (path == null) { 275 return null; 276 } 277 File file = subPath == null ? new File(path) : new File(path, subPath); 278 return new SafePath(file.getAbsolutePath()); 279 }, new PropertyPermission("*", "read")); 280 } 281 282 // Called by JVM during initialization of JFR 283 static Thread createRecorderThread(ThreadGroup systemThreadGroup, ClassLoader contextClassLoader) { 284 // The thread should have permission = new Permission[0], and not "modifyThreadGroup" and "modifyThread" on the stack, 285 // but it's hard circumvent if we are going to pass in system thread group in the constructor 286 Thread thread = doPrivilegedWithReturn(() -> new Thread(systemThreadGroup, "JFR Recorder Thread"), new RuntimePermission("modifyThreadGroup"), new RuntimePermission("modifyThread")); 287 doPrivileged(() -> thread.setContextClassLoader(contextClassLoader), new RuntimePermission("setContextClassLoader"), new RuntimePermission("modifyThread")); 288 return thread; 289 } 290 291 static void registerShutdownHook(Thread shutdownHook) { 292 doPrivileged(() -> Runtime.getRuntime().addShutdownHook(shutdownHook), new RuntimePermission("shutdownHooks")); 293 } 294 295 static void setUncaughtExceptionHandler(Thread thread, Thread.UncaughtExceptionHandler eh) { 296 doPrivileged(() -> thread.setUncaughtExceptionHandler(eh), new RuntimePermission("modifyThread")); 297 } 298 299 static void moveReplace(SafePath from, SafePath to) throws IOException { 300 doPrivilegedIOWithReturn(() -> Files.move(from.toPath(), to.toPath())); 301 } 302 303 static void clearDirectory(SafePath safePath) throws IOException { 304 doPriviligedIO(() -> Files.walkFileTree(safePath.toPath(), new DirectoryCleaner())); 305 } 306 307 static SafePath toRealPath(SafePath safePath) throws Exception { 308 return new SafePath(doPrivilegedIOWithReturn(() -> safePath.toPath().toRealPath())); 309 } 310 311 static boolean existDirectory(SafePath directory) throws IOException { 312 return doPrivilegedIOWithReturn(() -> Files.exists(directory.toPath())); 313 } 314 315 static RandomAccessFile createRandomAccessFile(SafePath path) throws Exception { 316 return doPrivilegedIOWithReturn(() -> new RandomAccessFile(path.toPath().toFile(), "rw")); 317 } 318 319 public static InputStream newFileInputStream(SafePath safePath) throws IOException { 320 return doPrivilegedIOWithReturn(() -> Files.newInputStream(safePath.toPath())); 321 } 322 323 public static long getFileSize(SafePath safePath) throws IOException { 324 return doPrivilegedIOWithReturn(() -> Files.size(safePath.toPath())); 325 } 326 327 static SafePath createDirectories(SafePath safePath) throws IOException { 328 Path p = doPrivilegedIOWithReturn(() -> Files.createDirectories(safePath.toPath())); 329 return new SafePath(p); 330 } 331 332 public static boolean exists(SafePath safePath) throws IOException { 333 return doPrivilegedIOWithReturn(() -> Files.exists(safePath.toPath())); 334 } 335 336 public static boolean isDirectory(SafePath safePath) throws IOException { 337 return doPrivilegedIOWithReturn(() -> Files.isDirectory(safePath.toPath())); 338 } 339 340 static void delete(SafePath localPath) throws IOException { 341 doPriviligedIO(() -> Files.delete(localPath.toPath())); 342 } 343 344 static boolean isWritable(SafePath safePath) throws IOException { 345 return doPrivilegedIOWithReturn(() -> Files.isWritable(safePath.toPath())); 346 } 347 348 static void deleteOnExit(SafePath safePath) { 349 doPrivileged(() -> safePath.toPath().toFile().deleteOnExit()); 350 } 351 352 static ReadableByteChannel newFileChannelToRead(SafePath safePath) throws IOException { 353 return doPrivilegedIOWithReturn(() -> FileChannel.open(safePath.toPath(), StandardOpenOption.READ)); 354 } 355 356 public static InputStream getResourceAsStream(String name) throws IOException { 357 return doPrivilegedIOWithReturn(() -> SecuritySupport.class.getResourceAsStream(name)); 358 } 359 360 public static Reader newFileReader(SafePath safePath) throws FileNotFoundException, IOException { 361 return doPrivilegedIOWithReturn(() -> Files.newBufferedReader(safePath.toPath())); 362 } 363 364 static void touch(SafePath path) throws IOException { 365 doPriviligedIO(() -> new RandomAccessFile(path.toPath().toFile(), "rw").close()); 366 } 367 368 static void setAccessible(Method method) { 369 doPrivileged(() -> method.setAccessible(true), new ReflectPermission("suppressAccessChecks")); 370 } 371 372 static void setAccessible(Field field) { 373 doPrivileged(() -> field.setAccessible(true), new ReflectPermission("suppressAccessChecks")); 374 } 375 376 static void setAccessible(Constructor<?> constructor) { 377 doPrivileged(() -> constructor.setAccessible(true), new ReflectPermission("suppressAccessChecks")); 378 } 379 380 static void ensureClassIsInitialized(Class<?> clazz) { 381 unsafe.ensureClassInitialized(clazz); 382 } 383 384 static Class<?> defineClass(String name, byte[] bytes, ClassLoader classLoader) { 385 return unsafe.defineClass(name, bytes, 0, bytes.length, classLoader, null); 386 } 387 388 static Thread createThreadWitNoPermissions(String threadName, Runnable runnable) { 389 return doPrivilegedWithReturn(() -> new Thread(runnable, threadName), new Permission[0]); 390 } 391 392 static void setDaemonThread(Thread t, boolean daeomn) { 393 doPrivileged(()-> t.setDaemon(daeomn), new RuntimePermission("modifyThread")); 394 } 395 396 public static SafePath getAbsolutePath(SafePath path) throws IOException { 397 return new SafePath(doPrivilegedIOWithReturn((()-> path.toPath().toAbsolutePath()))); 398 } 399 }