Security handling, but two tests fails with driver JEP-349-branch
authoregahlin
Tue, 13 Aug 2019 03:58:29 +0200
branchJEP-349-branch
changeset 57717 4ce66d271065
parent 57702 c75c241c492a
child 57726 6a7fa9735caf
Security handling, but two tests fails with driver
src/jdk.jfr/share/classes/jdk/jfr/consumer/EventDirectoryStream.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java
src/jdk.jfr/share/classes/jdk/jfr/internal/SecuritySupport.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/FileAccess.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RecordingInput.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java
test/jdk/jdk/jfr/api/consumer/security/TestMissingPermission.java
test/jdk/jdk/jfr/api/consumer/security/TestRecordingFile.java
test/jdk/jdk/jfr/api/consumer/security/TestRecordingStream.java
test/jdk/jdk/jfr/api/consumer/security/TestStreamingFile.java
test/jdk/jdk/jfr/api/consumer/security/TestStreamingLocal.java
test/jdk/jdk/jfr/api/consumer/security/TestStreamingRemote.java
test/jdk/jdk/jfr/api/consumer/security/local-streaming.policy
test/jdk/jdk/jfr/api/consumer/security/no-permission.policy
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventDirectoryStream.java	Fri Aug 09 19:16:55 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventDirectoryStream.java	Tue Aug 13 03:58:29 2019 +0200
@@ -35,7 +35,6 @@
 import java.util.Objects;
 import java.util.function.Consumer;
 
-import jdk.jfr.internal.SecuritySupport.SafePath;
 import jdk.jfr.internal.Utils;
 import jdk.jfr.internal.consumer.FileAccess;
 import jdk.jfr.internal.consumer.RecordingInput;
@@ -64,7 +63,7 @@
             super(acc, active);
             this.fileAccess = fileAccess;
             this.active = active;
-            repositoryFiles = new RepositoryFiles(fileAccess, p == null ? null : new SafePath(p));
+            repositoryFiles = new RepositoryFiles(fileAccess, p     );
         }
 
         @Override
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java	Fri Aug 09 19:16:55 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java	Tue Aug 13 03:58:29 2019 +0200
@@ -34,6 +34,7 @@
 import java.util.Objects;
 import java.util.function.Consumer;
 
+import jdk.jfr.internal.SecuritySupport;
 import jdk.jfr.internal.Utils;
 import jdk.jfr.internal.consumer.FileAccess;
 
@@ -60,7 +61,7 @@
      */
     public static EventStream openRepository() throws IOException {
         Utils.checkAccessFlightRecorder();
-        return new EventDirectoryStream(AccessController.getContext(), null, FileAccess.PRIVILIGED, false);
+        return new EventDirectoryStream(AccessController.getContext(), null, SecuritySupport.PRIVILIGED, false);
     }
 
     /**
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java	Fri Aug 09 19:16:55 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java	Tue Aug 13 03:58:29 2019 +0200
@@ -40,8 +40,8 @@
 import jdk.jfr.Recording;
 import jdk.jfr.internal.PlatformRecording;
 import jdk.jfr.internal.PrivateAccess;
+import jdk.jfr.internal.SecuritySupport;
 import jdk.jfr.internal.Utils;
-import jdk.jfr.internal.consumer.FileAccess;
 
 /**
  * A recording stream produces events from the current JVM (Java Virtual
@@ -100,7 +100,7 @@
         this.recording = new Recording();
         this.recording.setFlushInterval(Duration.ofMillis(1000));
         try {
-            this.stream = new EventDirectoryStream(acc, null, FileAccess.PRIVILIGED, true);
+            this.stream = new EventDirectoryStream(acc, null, SecuritySupport.PRIVILIGED, true);
         } catch (IOException ioe) {
             throw new IllegalStateException(ioe.getMessage());
         }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/SecuritySupport.java	Fri Aug 09 19:16:55 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/SecuritySupport.java	Tue Aug 13 03:58:29 2019 +0200
@@ -38,6 +38,7 @@
 import java.lang.reflect.ReflectPermission;
 import java.nio.channels.FileChannel;
 import java.nio.channels.ReadableByteChannel;
+import java.nio.file.DirectoryStream;
 import java.nio.file.FileVisitResult;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -65,6 +66,7 @@
 import jdk.jfr.FlightRecorderListener;
 import jdk.jfr.FlightRecorderPermission;
 import jdk.jfr.Recording;
+import jdk.jfr.internal.consumer.FileAccess;
 
 /**
  * Contains JFR code that does
@@ -75,7 +77,7 @@
     private final static MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
     private final static Module JFR_MODULE = Event.class.getModule();
     public  final static SafePath JFC_DIRECTORY = getPathInProperty("java.home", "lib/jfr");
-
+    public final static FileAccess PRIVILIGED = new Privileged();
     static final SafePath USER_HOME = getPathInProperty("user.home", null);
     static final SafePath JAVA_IO_TMPDIR = getPathInProperty("java.io.tmpdir", null);
 
@@ -150,7 +152,7 @@
      * a malicious provider.
      *
      */
-    public static final class SafePath {
+    public static final class SafePath implements Comparable<SafePath> {
         private final Path path;
         private final String text;
 
@@ -168,9 +170,18 @@
             return path;
         }
 
+        public File toFile() {
+            return path.toFile();
+        }
+
         public String toString() {
             return text;
         }
+
+        @Override
+        public int compareTo(SafePath that) {
+            return that.text.compareTo(this.text);
+        }
     }
 
     private interface RunnableWithCheckedException {
@@ -436,4 +447,32 @@
     public static SafePath getAbsolutePath(SafePath path) throws IOException {
         return new SafePath(doPrivilegedIOWithReturn((()-> path.toPath().toAbsolutePath())));
     }
+
+    private final static class Privileged extends FileAccess {
+        @Override
+        public RandomAccessFile openRAF(File f, String mode) throws IOException {
+            return doPrivilegedIOWithReturn( () -> new RandomAccessFile(f, mode));
+        }
+
+        @Override
+        public  DirectoryStream<Path> newDirectoryStream(Path directory)  throws IOException  {
+            return doPrivilegedIOWithReturn( () -> Files.newDirectoryStream(directory));
+        }
+
+        @Override
+        public  String getAbsolutePath(File f) throws IOException {
+            return doPrivilegedIOWithReturn( () ->f.getAbsolutePath());
+        }
+        @Override
+        public long length(File f) throws IOException {
+            return doPrivilegedIOWithReturn( () ->f.length());
+        }
+
+        @Override
+        public  long fileSize(Path p) throws IOException {
+            return doPrivilegedIOWithReturn( () ->Files.size(p));
+        }
+    }
+
+
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/FileAccess.java	Fri Aug 09 19:16:55 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/FileAccess.java	Tue Aug 13 03:58:29 2019 +0200
@@ -1,7 +1,6 @@
 package jdk.jfr.internal.consumer;
 
 import java.io.File;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.nio.file.DirectoryStream;
@@ -10,37 +9,40 @@
 
 // Protected by modular boundaries.
 public abstract class FileAccess {
-    public final static FileAccess PRIVILIGED = new UnPriviliged();
-    // TODO: Should be changed Priviliged class
     public final static FileAccess UNPRIVILIGED = new UnPriviliged();
 
-    abstract RandomAccessFile openRAF(File f, String mode) throws FileNotFoundException;
-    abstract DirectoryStream<Path> newDirectoryStream(Path repository) throws IOException;
+    public abstract RandomAccessFile openRAF(File f, String mode) throws IOException;
 
-    static class Priviliged extends FileAccess {
-        @Override
-        RandomAccessFile openRAF(File f, String mode) {
-            // TDOO: Implement
-            return null;
-        }
+    public abstract DirectoryStream<Path> newDirectoryStream(Path repository) throws IOException;
+
+    public abstract String getAbsolutePath(File f) throws IOException;
 
+    public abstract long length(File f) throws IOException;
+
+    public abstract long fileSize(Path p) throws IOException;
+
+    private static class UnPriviliged extends FileAccess {
         @Override
-        protected DirectoryStream<Path> newDirectoryStream(Path repository) {
-            // TDOO: Implement
-            return null;
-        }
-    }
-
-    static class UnPriviliged extends FileAccess {
-        @Override
-        RandomAccessFile openRAF(File f, String mode) throws FileNotFoundException {
+        public RandomAccessFile openRAF(File f, String mode) throws IOException {
             return new RandomAccessFile(f, mode);
         }
 
         @Override
-        DirectoryStream<Path> newDirectoryStream(Path dir) throws IOException {
+        public DirectoryStream<Path> newDirectoryStream(Path dir) throws IOException {
             return Files.newDirectoryStream(dir);
         }
 
+        public String getAbsolutePath(File f) throws IOException {
+            return f.getAbsolutePath();
+        }
+
+        public long length(File f) throws IOException {
+            return f.length();
+        }
+
+        @Override
+        public long fileSize(Path p) throws IOException {
+            return Files.size(p);
+        }
     }
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RecordingInput.java	Fri Aug 09 19:16:55 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RecordingInput.java	Tue Aug 13 03:58:29 2019 +0200
@@ -81,13 +81,13 @@
     }
 
     private void initialize(File f) throws IOException {
-        this.filename = f.getAbsolutePath().toString();
+        this.filename = fileAccess.getAbsolutePath(f);
         this.file = fileAccess.openRAF(f, "r");
         this.position = 0;
         this.size = -1;
         this.currentBlock.reset();
-        this.previousBlock.reset();
-        if (f.length() < 8) {
+        previousBlock.reset();
+        if (fileAccess.length(f) < 8) {
             throw new IOException("Not a valid Flight Recorder file. File length is only " + f.length() + " bytes.");
         }
     }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java	Fri Aug 09 19:16:55 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java	Tue Aug 13 03:58:29 2019 +0200
@@ -26,7 +26,6 @@
 
 import java.io.IOException;
 import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -52,8 +51,8 @@
     private volatile boolean closed;
     private Path repository;
 
-    public RepositoryFiles(FileAccess fileAccess, SafePath repository) {
-        this.repository = repository == null ? null : repository.toPath();
+    public RepositoryFiles(FileAccess fileAccess, Path repository) {
+        this.repository = repository;
         this.fileAccess = fileAccess;
     }
 
@@ -62,66 +61,71 @@
     }
 
     public Path lastPath() {
-        // Wait for chunks
+        if (waitForPaths()) {
+            return pathSet.lastEntry().getValue();
+        }
+        return null; // closed
+    }
+
+    public Path firstPath(long startTimeNanos) {
+        if (waitForPaths()) {
+            // Pick closest chunk before timestamp
+            Long time = pathSet.floorKey(startTimeNanos);
+            if (time != null) {
+                startTimeNanos = time;
+            }
+            return path(startTimeNanos);
+        }
+        return null; // closed
+    }
+
+    private boolean waitForPaths() {
         while (!closed) {
             try {
                 if (updatePaths()) {
                     break;
                 }
             } catch (IOException e) {
-                // ignore, not yet available
+                Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.DEBUG, "IOException during repository file scan " + e.getMessage());
+                // This can happen if a chunk is being removed
+                // between the file was discovered and an instance
+                // was accessed, or if new file has been written yet
+                // Just ignore, and retry later.
             }
-        }
-        if (closed) {
-            return null;
+            nap();
         }
-        // Pick the last
-        return pathSet.lastEntry().getValue();
-    }
-
-    public Path firstPath(long startTimeNanos) {
-        return path(startTimeNanos, true);
+        return !closed;
     }
 
     public Path nextPath(long startTimeNanos) {
-        return path(startTimeNanos, false);
+        return path(startTimeNanos);
     }
 
-    private Path path(long timestamp, boolean first) {
-        while (!closed) {
-            Long time = timestamp;
-            if (first) {
-                // Pick closest chunk before timestamp
-                time = pathSet.floorKey(timestamp);
-            }
-            if (time != null) {
-                SortedMap<Long, Path> after = pathSet.tailMap(time);
-                if (!after.isEmpty()) {
-                    Path path = after.get(after.firstKey());
-                    Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.TRACE, "Return path " + path + " for start time nanos " + timestamp);
-                    return path;
-                }
+    private Path path(long timestamp) {
+        if (closed) {
+            return null;
+        }
+        while (true) {
+            SortedMap<Long, Path> after = pathSet.tailMap(timestamp);
+            if (!after.isEmpty()) {
+                Path path = after.get(after.firstKey());
+                Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.TRACE, "Return path " + path + " for start time nanos " + timestamp);
+                return path;
             }
-            try {
-                if (updatePaths()) {
-                    continue;
-                }
-            } catch (IOException e) {
-                Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.DEBUG, "IOException during repository file scan " + e.getMessage());
-                // This can happen if a chunk is being removed
-                // between the file was discovered and an instance
-                // of an EventSet was constructed. Just ignore,
-                // and retry later.
-            }
-            try {
-                synchronized (pathSet) {
-                    pathSet.wait(1000);
-                }
-            } catch (InterruptedException e) {
-                // ignore
+            if (!waitForPaths()) {
+                return null; // closed
             }
         }
-        return null;
+    }
+
+    private void nap() {
+        try {
+            synchronized (pathSet) {
+                pathSet.wait(1000);
+            }
+        } catch (InterruptedException e) {
+            // ignore
+        }
     }
 
     private boolean updatePaths() throws IOException {
@@ -163,7 +167,7 @@
         for (Path p : added) {
             // Only add files that have a complete header
             // as the JVM may be in progress writing the file
-            long size = Files.size(p);
+            long size = fileAccess.fileSize(p);
             if (size >= ChunkHeader.HEADER_SIZE) {
                 long startNanos = readStartTime(p);
                 pathSet.put(startNanos, p);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/security/TestMissingPermission.java	Tue Aug 13 03:58:29 2019 +0200
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.security;
+
+import java.io.IOException;
+
+import jdk.jfr.consumer.EventStream;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Tests that streaming doesn't work if
+ *          FlightRecordingPermission("accessFlightRecorder") is missing
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm/secure=java.lang.SecurityManager/java.security.policy=no-permission.policy
+ *      jdk.jfr.api.consumer.security.TestMissingPermission
+ */
+public class TestMissingPermission {
+
+    public static void main(String... args) throws Exception {
+        testOpenRepository();
+        testRecordingStream();
+    }
+
+    private static void testRecordingStream() throws IOException {
+        try {
+            try (EventStream es = EventStream.openRepository()) {
+                fail();
+            }
+        } catch (SecurityException se) {
+            // OK, as expected
+        }
+    }
+
+    private static void testOpenRepository() throws IOException {
+        try {
+            try (RecordingStream es = new RecordingStream()) {
+                fail();
+            }
+        } catch (SecurityException se) {
+            // OK, as expected
+        }
+    }
+
+    private static void fail() {
+        throw new AssertionError("Should not be able to create EventStream with FlightRecorderPermission");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/security/TestRecordingFile.java	Tue Aug 13 03:58:29 2019 +0200
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.security;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.RecordingFile;
+import jdk.test.lib.jfr.EventNames;
+
+/**
+ * @test
+ * @summary Test that a recording file can't be opened without permissions
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run driver jdk.jfr.api.consumer.security.TestRecordingFile$Dumper
+ * @run main/othervm/secure=java.lang.SecurityManager/java.security.policy=no-permission.policy
+ *      jdk.jfr.api.consumer.security.TestRecordingFile
+ *
+ */
+public class TestRecordingFile {
+    public final static Path DUMP_FILE = Paths.get("dump.jfr");
+
+    public static class Dumper {
+        public static void main(String... args) throws Exception {
+            try (Recording r = new Recording()) {
+                // Enable JVM event, no write permission needed
+                r.enable(EventNames.JVMInformation);
+                r.start();
+                r.stop();
+                r.dump(DUMP_FILE);
+            }
+        }
+    }
+
+    public static void main(String... args) throws Exception {
+        try {
+            RecordingFile.readAllEvents(DUMP_FILE);
+            throw new AssertionError("Expected SecurityException");
+        } catch (SecurityException se) {
+            // OK, as expected
+            return;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/security/TestRecordingStream.java	Tue Aug 13 03:58:29 2019 +0200
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.security;
+
+import java.time.Instant;
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.consumer.RecordingStream;
+import jdk.test.lib.jfr.EventNames;
+
+/**
+ * @test
+ * @summary Tests that a RecordingStream works using only
+ *          FlightRecordingPermission("accessFlightRecorder")
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm/secure=java.lang.SecurityManager/java.security.policy=local-streaming.policy
+ *      jdk.jfr.api.consumer.security.TestStreamingLocal
+ */
+public class TestRecordingStream {
+
+    public static void main(String... args) throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        try (RecordingStream r = new RecordingStream()) {
+            // Enable JVM event, no write permission needed
+            r.enable(EventNames.JVMInformation);
+            r.setStartTime(Instant.EPOCH);
+            r.onEvent(EventNames.JVMInformation, e -> {
+                latch.countDown();
+            });
+            r.startAsync();
+            latch.await();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/security/TestStreamingFile.java	Tue Aug 13 03:58:29 2019 +0200
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.security;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.EventStream;
+
+/**
+ * @test
+ * @summary Test that a event file stream can't be opened without permissions
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @build jdk.jfr.api.consumer.security.TestStreamingFile
+ * @run driver jdk.jfr.api.consumer.security.TestStreamingFile$Dumper
+ * @run main/othervm/secure=java.lang.SecurityManager/java.security.policy=no-permission.policy
+ *      jdk.jfr.api.consumer.security.TestStreamingFile
+ *
+ */
+public class TestStreamingFile {
+    public final static Path DUMP_FILE = Paths.get("dump.jfr");
+
+    public static class Dumper {
+        public static void main(String... args) throws Exception {
+            try (Recording r = new Recording()) {
+                // Enable JVM event, no write permission needed
+                r.enable("jdk.JVMInformation");
+                r.start();
+                r.stop();
+                r.dump(DUMP_FILE);
+            }
+        }
+    }
+
+    public static void main(String... args) throws Exception {
+        try (EventStream es = EventStream.openFile(DUMP_FILE)) {
+            throw new AssertionError("Expected SecurityException");
+        } catch (SecurityException se) {
+            // OK, as expected
+            return;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/security/TestStreamingLocal.java	Tue Aug 13 03:58:29 2019 +0200
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.security;
+
+import java.time.Instant;
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.EventStream;
+import jdk.test.lib.jfr.EventNames;
+
+/**
+ * @test
+ * @summary Tests that local streaming works using only
+ *          FlightRecordingPermission("accessFlightRecorder")
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm/secure=java.lang.SecurityManager/java.security.policy=local-streaming.policy
+ *      jdk.jfr.api.consumer.security.TestStreamingLocal
+ */
+public class TestStreamingLocal {
+
+    public static void main(String... args) throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        try (Recording r = new Recording()) {
+            // Enable JVM event, no write permission needed
+            r.enable(EventNames.JVMInformation);
+            r.start();
+            r.stop();
+            try (EventStream es = EventStream.openRepository()) {
+                es.setStartTime(Instant.EPOCH);
+                es.onEvent("jdk.JVMInformation", e -> {
+                    latch.countDown();
+                });
+                es.startAsync();
+                latch.await();
+            }
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/security/TestStreamingRemote.java	Tue Aug 13 03:58:29 2019 +0200
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.security;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.time.Instant;
+
+import jdk.jfr.Event;
+import jdk.jfr.Name;
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.EventStream;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+
+/**
+ * @test
+ * @summary Test that a stream can be opened against a remote repository using
+ *          only file permission
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.security.TestStreamingRemote
+ */
+public class TestStreamingRemote {
+
+    private static final String SUCCESS = "Success!";
+
+    @Name("Test")
+    public static class TestEvent extends Event {
+    }
+
+    public static class Test {
+        public static void main(String... args) throws Exception {
+            Path repo = Paths.get(args[0]);
+            System.out.println("Repository: " + repo);
+            try (EventStream es = EventStream.openRepository(repo)) {
+                es.setStartTime(Instant.EPOCH);
+                es.onEvent(e -> {
+                    System.out.println(SUCCESS);
+                    es.close();
+                });
+                es.start();
+            }
+        }
+    }
+
+    public static void main(String... args) throws Exception {
+        try (Recording r = new Recording()) {
+            r.setFlushInterval(Duration.ofSeconds(1));
+            r.start();
+            String repository = System.getProperty("jdk.jfr.repository");
+            Path policy = createPolicyFile(repository);
+            TestEvent e = new TestEvent();
+            e.commit();
+            String[] c = new String[4];
+            c[0] = "-Djava.security.manager";
+            c[1] = "-Djava.security.policy=" + escape(policy.toString());
+            c[2] = Test.class.getName();
+            c[3] = repository;
+            OutputAnalyzer oa = ProcessTools.executeTestJvm(c);
+            oa.shouldContain(SUCCESS);
+        }
+    }
+
+    private static Path createPolicyFile(String repository) throws IOException {
+        Path p = Paths.get("permission.policy").toAbsolutePath();
+        try (PrintWriter pw = new PrintWriter(p.toFile())) {
+            pw.println("grant {");
+            // All the files and directories the contained in path
+            String dir = escape(repository);
+            String contents = escape(repository + File.separatorChar + "-");
+            pw.println("   permission java.io.FilePermission \"" + dir + "\", \"read\";");
+            pw.println("   permission java.io.FilePermission \"" + contents + "\", \"read\";");
+            pw.println("};");
+            pw.println();
+        }
+        System.out.println("Permission file: " + p);
+        for (String line : Files.readAllLines(p)) {
+            System.out.println(line);
+        }
+        System.out.println();
+        return p;
+    }
+
+    // Double quote needed for Windows
+    private static String escape(String text) {
+        StringBuilder sb = new StringBuilder();
+        for (char c : text.toCharArray()) {
+            if (c == '\\') {
+                sb.append(c);
+            }
+            sb.append(c);
+        }
+        return sb.toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/security/local-streaming.policy	Tue Aug 13 03:58:29 2019 +0200
@@ -0,0 +1,4 @@
+// Minimum policy to stream locally
+grant {
+permission jdk.jfr.FlightRecorderPermission "accessFlightRecorder";
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/security/no-permission.policy	Tue Aug 13 03:58:29 2019 +0200
@@ -0,0 +1,3 @@
+// No permission
+grant {
+};