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