1 /* 2 * Copyright (c) 2016, 2019, 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 public static void registerEvent(Class<? extends Event> eventClass) { 244 doPrivileged(() -> FlightRecorder.register(eventClass), new FlightRecorderPermission(Utils.REGISTER_EVENT)); 245 } 246 247 static boolean getBooleanProperty(String propertyName) { 248 return doPrivilegedWithReturn(() -> Boolean.getBoolean(propertyName), new PropertyPermission(propertyName, "read")); 249 } 250 251 private static SafePath getPathInProperty(String prop, String subPath) { 252 return doPrivilegedWithReturn(() -> { 253 String path = System.getProperty(prop); 254 if (path == null) { 255 return null; 256 } 257 File file = subPath == null ? new File(path) : new File(path, subPath); 258 return new SafePath(file.getAbsolutePath()); 259 }, new PropertyPermission("*", "read")); 260 } 261 262 // Called by JVM during initialization of JFR 263 static Thread createRecorderThread(ThreadGroup systemThreadGroup, ClassLoader contextClassLoader) { 264 // The thread should have permission = new Permission[0], and not "modifyThreadGroup" and "modifyThread" on the stack, 265 // but it's hard circumvent if we are going to pass in system thread group in the constructor 266 Thread thread = doPrivilegedWithReturn(() -> new Thread(systemThreadGroup, "JFR Recorder Thread"), new RuntimePermission("modifyThreadGroup"), new RuntimePermission("modifyThread")); 267 doPrivileged(() -> thread.setContextClassLoader(contextClassLoader), new RuntimePermission("setContextClassLoader"), new RuntimePermission("modifyThread")); 268 return thread; 269 } 270 271 static void registerShutdownHook(Thread shutdownHook) { 272 doPrivileged(() -> Runtime.getRuntime().addShutdownHook(shutdownHook), new RuntimePermission("shutdownHooks")); 273 } 274 275 static void setUncaughtExceptionHandler(Thread thread, Thread.UncaughtExceptionHandler eh) { 276 doPrivileged(() -> thread.setUncaughtExceptionHandler(eh), new RuntimePermission("modifyThread")); 277 } 278 279 static void moveReplace(SafePath from, SafePath to) throws IOException { 280 doPrivilegedIOWithReturn(() -> Files.move(from.toPath(), to.toPath())); 281 } 282 283 static void clearDirectory(SafePath safePath) throws IOException { 284 doPriviligedIO(() -> Files.walkFileTree(safePath.toPath(), new DirectoryCleaner())); 285 } 286 287 static SafePath toRealPath(SafePath safePath) throws Exception { 288 return new SafePath(doPrivilegedIOWithReturn(() -> safePath.toPath().toRealPath())); 289 } 290 291 static boolean existDirectory(SafePath directory) throws IOException { 292 return doPrivilegedIOWithReturn(() -> Files.exists(directory.toPath())); 293 } 294 295 static RandomAccessFile createRandomAccessFile(SafePath path) throws Exception { 296 return doPrivilegedIOWithReturn(() -> new RandomAccessFile(path.toPath().toFile(), "rw")); 297 } 298 299 public static InputStream newFileInputStream(SafePath safePath) throws IOException { 300 return doPrivilegedIOWithReturn(() -> Files.newInputStream(safePath.toPath())); 301 } 302 303 public static long getFileSize(SafePath safePath) throws IOException { 304 return doPrivilegedIOWithReturn(() -> Files.size(safePath.toPath())); 305 } 306 307 static SafePath createDirectories(SafePath safePath) throws IOException { 308 Path p = doPrivilegedIOWithReturn(() -> Files.createDirectories(safePath.toPath())); 309 return new SafePath(p); 310 } 311 312 public static boolean exists(SafePath safePath) throws IOException { 313 return doPrivilegedIOWithReturn(() -> Files.exists(safePath.toPath())); 314 } 315 316 public static boolean isDirectory(SafePath safePath) throws IOException { 317 return doPrivilegedIOWithReturn(() -> Files.isDirectory(safePath.toPath())); 318 } 319 320 static void delete(SafePath localPath) throws IOException { 321 doPriviligedIO(() -> Files.delete(localPath.toPath())); 322 } 323 324 static boolean isWritable(SafePath safePath) throws IOException { 325 return doPrivilegedIOWithReturn(() -> Files.isWritable(safePath.toPath())); 326 } 327 328 static void deleteOnExit(SafePath safePath) { 329 doPrivileged(() -> safePath.toPath().toFile().deleteOnExit()); 330 } 331 332 static ReadableByteChannel newFileChannelToRead(SafePath safePath) throws IOException { 333 return doPrivilegedIOWithReturn(() -> FileChannel.open(safePath.toPath(), StandardOpenOption.READ)); 334 } 335 336 public static InputStream getResourceAsStream(String name) throws IOException { 337 return doPrivilegedIOWithReturn(() -> SecuritySupport.class.getResourceAsStream(name)); 338 } 339 340 public static Reader newFileReader(SafePath safePath) throws FileNotFoundException, IOException { 341 return doPrivilegedIOWithReturn(() -> Files.newBufferedReader(safePath.toPath())); 342 } 343 344 static void touch(SafePath path) throws IOException { 345 doPriviligedIO(() -> new RandomAccessFile(path.toPath().toFile(), "rw").close()); 346 } 347 348 static void setAccessible(Method method) { 349 doPrivileged(() -> method.setAccessible(true), new ReflectPermission("suppressAccessChecks")); 350 } 351 352 static void setAccessible(Field field) { 353 doPrivileged(() -> field.setAccessible(true), new ReflectPermission("suppressAccessChecks")); 354 } 355 356 static void setAccessible(Constructor<?> constructor) { 357 doPrivileged(() -> constructor.setAccessible(true), new ReflectPermission("suppressAccessChecks")); 358 } 359 360 static void ensureClassIsInitialized(Class<?> clazz) { 361 unsafe.ensureClassInitialized(clazz); 362 } 363 364 static Class<?> defineClass(String name, byte[] bytes, ClassLoader classLoader) { 365 return unsafe.defineClass(name, bytes, 0, bytes.length, classLoader, null); 366 } 367 368 static Thread createThreadWitNoPermissions(String threadName, Runnable runnable) { 369 return doPrivilegedWithReturn(() -> new Thread(runnable, threadName), new Permission[0]); 370 } 371 372 static void setDaemonThread(Thread t, boolean daeomn) { 373 doPrivileged(()-> t.setDaemon(daeomn), new RuntimePermission("modifyThread")); 374 } 375 376 public static SafePath getAbsolutePath(SafePath path) throws IOException { 377 return new SafePath(doPrivilegedIOWithReturn((()-> path.toPath().toAbsolutePath()))); 378 } 379 }