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