--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/SecuritySupport.java Tue May 15 20:24:34 2018 +0200
@@ -0,0 +1,399 @@
+/*
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jfr.internal;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.io.Reader;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.ReflectPermission;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.Permission;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.PropertyPermission;
+import java.util.concurrent.Callable;
+
+import jdk.internal.misc.Unsafe;
+import jdk.internal.module.Modules;
+import jdk.jfr.Event;
+import jdk.jfr.FlightRecorder;
+import jdk.jfr.FlightRecorderListener;
+import jdk.jfr.FlightRecorderPermission;
+import jdk.jfr.Recording;
+
+/**
+ * Contains JFR code that does
+ * {@link AccessController#doPrivileged(PrivilegedAction)}
+ */
+public final class SecuritySupport {
+ private final static Unsafe unsafe = Unsafe.getUnsafe();
+ private final static Module JFR_MODULE = Event.class.getModule();
+ public final static SafePath JFC_DIRECTORY = getPathInProperty("java.home", "lib/jfr");
+
+ static final SafePath USER_HOME = getPathInProperty("user.home", null);
+ static final SafePath JAVA_IO_TMPDIR = getPathInProperty("java.io.tmpdir", null);
+
+ final static class SecureRecorderListener implements FlightRecorderListener {
+
+ private final AccessControlContext context;
+ private final FlightRecorderListener changeListener;
+
+ SecureRecorderListener(AccessControlContext context, FlightRecorderListener changeListener) {
+ this.context = Objects.requireNonNull(context);
+ this.changeListener = Objects.requireNonNull(changeListener);
+ }
+
+ @Override
+ public void recordingStateChanged(Recording recording) {
+ AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
+ try {
+ changeListener.recordingStateChanged(recording);
+ } catch (Throwable t) {
+ // Prevent malicious user to propagate exception callback in the wrong context
+ Logger.log(LogTag.JFR, LogLevel.WARN, "Unexpected exception in listener " + changeListener.getClass()+ " at recording state change");
+ }
+ return null;
+ }, context);
+ }
+
+ @Override
+ public void recorderInitialized(FlightRecorder recorder) {
+ AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
+ try {
+ changeListener.recorderInitialized(recorder);
+ } catch (Throwable t) {
+ // Prevent malicious user to propagate exception callback in the wrong context
+ Logger.log(LogTag.JFR, LogLevel.WARN, "Unexpected exception in listener " + changeListener.getClass()+ " when initializing FlightRecorder");
+ }
+ return null;
+ }, context);
+ }
+
+ public FlightRecorderListener getChangeListener() {
+ return changeListener;
+ }
+ }
+
+ private static final class DirectoryCleaner extends SimpleFileVisitor<Path> {
+ @Override
+ public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
+ Files.delete(path);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+ if (exc != null) {
+ throw exc;
+ }
+ Files.delete(dir);
+ return FileVisitResult.CONTINUE;
+ }
+ }
+
+ /**
+ * Path created by the default file provider,and not
+ * a malicious provider.
+ *
+ */
+ public static final class SafePath {
+ private final Path path;
+ private final String text;
+
+ public SafePath(Path p) {
+ // sanitize
+ text = p.toString();
+ path = Paths.get(text);
+ }
+
+ public SafePath(String path) {
+ this(Paths.get(path));
+ }
+
+ public Path toPath() {
+ return path;
+ }
+
+ public String toString() {
+ return text;
+ }
+ }
+
+ private interface RunnableWithCheckedException {
+ public void run() throws Exception;
+ }
+
+ private interface CallableWithoutCheckException<T> {
+ public T call();
+ }
+
+ private static <U> U doPrivilegedIOWithReturn(Callable<U> function) throws IOException {
+ try {
+ return AccessController.doPrivileged(new PrivilegedExceptionAction<U>() {
+ @Override
+ public U run() throws Exception {
+ return function.call();
+ }
+ }, null);
+ } catch (PrivilegedActionException e) {
+ Throwable t = e.getCause();
+ if (t instanceof IOException) {
+ throw (IOException) t;
+ }
+ throw new IOException("Unexpected error during I/O operation. " + t.getMessage(), t);
+ }
+ }
+
+ private static void doPriviligedIO(RunnableWithCheckedException function) throws IOException {
+ doPrivilegedIOWithReturn(() -> {
+ function.run();
+ return null;
+ });
+ }
+
+ private static void doPrivileged(Runnable function, Permission... perms) {
+ AccessController.doPrivileged(new PrivilegedAction<Void>() {
+ @Override
+ public Void run() {
+ function.run();
+ return null;
+ }
+ }, null, perms);
+ }
+
+ private static void doPrivileged(Runnable function) {
+ AccessController.doPrivileged(new PrivilegedAction<Void>() {
+ @Override
+ public Void run() {
+ function.run();
+ return null;
+ }
+ });
+ }
+
+ private static <T> T doPrivilegedWithReturn(CallableWithoutCheckException<T> function, Permission... perms) {
+ return AccessController.doPrivileged(new PrivilegedAction<T>() {
+ @Override
+ public T run() {
+ return function.call();
+ }
+ }, null, perms);
+ }
+
+ public static List<SafePath> getPredefinedJFCFiles() {
+ List<SafePath> list = new ArrayList<>();
+ try {
+ Iterator<Path> pathIterator = doPrivilegedIOWithReturn(() -> {
+ return Files.newDirectoryStream(JFC_DIRECTORY.toPath(), "*").iterator();
+ });
+ while (pathIterator.hasNext()) {
+ Path path = pathIterator.next();
+ if (path.toString().endsWith(".jfc")) {
+ list.add(new SafePath(path));
+ }
+ }
+ } catch (IOException ioe) {
+ Logger.log(LogTag.JFR, LogLevel.WARN, "Could not access .jfc-files in " + JFC_DIRECTORY + ", " + ioe.getMessage());
+ }
+ return list;
+ }
+
+ static void makeVisibleToJFR(Class<?> clazz) {
+ Module classModule = clazz.getModule();
+ Modules.addReads(JFR_MODULE, classModule);
+ if (clazz.getPackage() != null) {
+ String packageName = clazz.getPackage().getName();
+ Modules.addExports(classModule, packageName, JFR_MODULE);
+ Modules.addOpens(classModule, packageName, JFR_MODULE);
+ }
+ }
+
+ /**
+ * Adds a qualified export of the internal.jdk.jfr.internal.handlers package
+ * (for EventHandler)
+ */
+ static void addHandlerExport(Class<?> clazz) {
+ Modules.addExports(JFR_MODULE, Utils.HANDLERS_PACKAGE_NAME, clazz.getModule());
+ }
+
+ public static void registerEvent(Class<? extends Event> eventClass) {
+ doPrivileged(() -> FlightRecorder.register(eventClass), new FlightRecorderPermission(Utils.REGISTER_EVENT));
+ }
+
+ static boolean getBooleanProperty(String propertyName) {
+ return doPrivilegedWithReturn(() -> Boolean.getBoolean(propertyName), new PropertyPermission(propertyName, "read"));
+ }
+
+ private static SafePath getPathInProperty(String prop, String subPath) {
+ return doPrivilegedWithReturn(() -> {
+ String path = System.getProperty(prop);
+ if (path == null) {
+ return null;
+ }
+ File file = subPath == null ? new File(path) : new File(path, subPath);
+ return new SafePath(file.getAbsolutePath());
+ }, new PropertyPermission("*", "read"));
+ }
+
+ // Called by JVM during initialization of JFR
+ static Thread createRecorderThread(ThreadGroup systemThreadGroup, ClassLoader contextClassLoader) {
+ // The thread should have permission = new Permission[0], and not "modifyThreadGroup" and "modifyThread" on the stack,
+ // but it's hard circumvent if we are going to pass in system thread group in the constructor
+ Thread thread = doPrivilegedWithReturn(() -> new Thread(systemThreadGroup, "JFR Recorder Thread"), new RuntimePermission("modifyThreadGroup"), new RuntimePermission("modifyThread"));
+ doPrivileged(() -> thread.setContextClassLoader(contextClassLoader), new RuntimePermission("setContextClassLoader"), new RuntimePermission("modifyThread"));
+ return thread;
+ }
+
+ static void registerShutdownHook(Thread shutdownHook) {
+ doPrivileged(() -> Runtime.getRuntime().addShutdownHook(shutdownHook), new RuntimePermission("shutdownHooks"));
+ }
+
+ static void setUncaughtExceptionHandler(Thread thread, Thread.UncaughtExceptionHandler eh) {
+ doPrivileged(() -> thread.setUncaughtExceptionHandler(eh), new RuntimePermission("modifyThread"));
+ }
+
+ static void moveReplace(SafePath from, SafePath to) throws IOException {
+ doPrivilegedIOWithReturn(() -> Files.move(from.toPath(), to.toPath()));
+ }
+
+ static void clearDirectory(SafePath safePath) throws IOException {
+ doPriviligedIO(() -> Files.walkFileTree(safePath.toPath(), new DirectoryCleaner()));
+ }
+
+ static SafePath toRealPath(SafePath safePath) throws Exception {
+ return new SafePath(doPrivilegedIOWithReturn(() -> safePath.toPath().toRealPath()));
+ }
+
+ static boolean existDirectory(SafePath directory) throws IOException {
+ return doPrivilegedIOWithReturn(() -> Files.exists(directory.toPath()));
+ }
+
+ static RandomAccessFile createRandomAccessFile(SafePath path) throws Exception {
+ return doPrivilegedIOWithReturn(() -> new RandomAccessFile(path.toPath().toFile(), "rw"));
+ }
+
+ public static InputStream newFileInputStream(SafePath safePath) throws IOException {
+ return doPrivilegedIOWithReturn(() -> Files.newInputStream(safePath.toPath()));
+ }
+
+ public static long getFileSize(SafePath safePath) throws IOException {
+ return doPrivilegedIOWithReturn(() -> Files.size(safePath.toPath()));
+ }
+
+ static SafePath createDirectories(SafePath safePath) throws IOException {
+ Path p = doPrivilegedIOWithReturn(() -> Files.createDirectories(safePath.toPath()));
+ return new SafePath(p);
+ }
+
+ public static boolean exists(SafePath safePath) throws IOException {
+ return doPrivilegedIOWithReturn(() -> Files.exists(safePath.toPath()));
+ }
+
+ public static boolean isDirectory(SafePath safePath) throws IOException {
+ return doPrivilegedIOWithReturn(() -> Files.isDirectory(safePath.toPath()));
+ }
+
+ static void delete(SafePath localPath) throws IOException {
+ doPriviligedIO(() -> Files.delete(localPath.toPath()));
+ }
+
+ static boolean isWritable(SafePath safePath) throws IOException {
+ return doPrivilegedIOWithReturn(() -> Files.isWritable(safePath.toPath()));
+ }
+
+ static void deleteOnExit(SafePath safePath) {
+ doPrivileged(() -> safePath.toPath().toFile().deleteOnExit());
+ }
+
+ static ReadableByteChannel newFileChannelToRead(SafePath safePath) throws IOException {
+ return doPrivilegedIOWithReturn(() -> FileChannel.open(safePath.toPath(), StandardOpenOption.READ));
+ }
+
+ public static InputStream getResourceAsStream(String name) throws IOException {
+ return doPrivilegedIOWithReturn(() -> SecuritySupport.class.getResourceAsStream(name));
+ }
+
+ public static Reader newFileReader(SafePath safePath) throws FileNotFoundException, IOException {
+ return doPrivilegedIOWithReturn(() -> Files.newBufferedReader(safePath.toPath()));
+ }
+
+ static void touch(SafePath path) throws IOException {
+ doPriviligedIO(() -> new RandomAccessFile(path.toPath().toFile(), "rw").close());
+ }
+
+ static void setAccessible(Method method) {
+ doPrivileged(() -> method.setAccessible(true), new ReflectPermission("suppressAccessChecks"));
+ }
+
+ static void setAccessible(Field field) {
+ doPrivileged(() -> field.setAccessible(true), new ReflectPermission("suppressAccessChecks"));
+ }
+
+ static void setAccessible(Constructor<?> constructor) {
+ doPrivileged(() -> constructor.setAccessible(true), new ReflectPermission("suppressAccessChecks"));
+ }
+
+ static void ensureClassIsInitialized(Class<?> clazz) {
+ unsafe.ensureClassInitialized(clazz);
+ }
+
+ static Class<?> defineClass(String name, byte[] bytes, ClassLoader classLoader) {
+ return unsafe.defineClass(name, bytes, 0, bytes.length, classLoader, null);
+ }
+
+ static Thread createThreadWitNoPermissions(String threadName, Runnable runnable) {
+ return doPrivilegedWithReturn(() -> new Thread(runnable, threadName), new Permission[0]);
+ }
+
+ static void setDaemonThread(Thread t, boolean daeomn) {
+ doPrivileged(()-> t.setDaemon(daeomn), new RuntimePermission("modifyThread"));
+ }
+
+ public static SafePath getAbsolutePath(SafePath path) throws IOException {
+ return new SafePath(doPrivilegedIOWithReturn((()-> path.toPath().toAbsolutePath())));
+ }
+}