diff --git a/src/share/classes/jdk/jfr/internal/EventHandlerCreator.java b/src/share/classes/jdk/jfr/internal/EventHandlerCreator.java index 0d5fe27ddbc3690cee58a0fcfb25a3f48caf06e9..c26dced5c470208168c568c10d5e7fc2c4564207 100644 --- a/src/share/classes/jdk/jfr/internal/EventHandlerCreator.java +++ b/src/share/classes/jdk/jfr/internal/EventHandlerCreator.java @@ -63,10 +63,11 @@ final class EventHandlerCreator { private static final String FIELD_EVENT_TYPE = "platformEventType"; private static final String FIELD_PREFIX_STRING_POOL = "stringPool"; + private final static Class eventHandlerProxy = EventHandlerProxyCreator.proxyClass; private final static Type TYPE_STRING_POOL = Type.getType(StringPool.class); private final static Type TYPE_EVENT_WRITER = Type.getType(EventWriter.class); private final static Type TYPE_PLATFORM_EVENT_TYPE = Type.getType(PlatformEventType.class); - private final static Type TYPE_EVENT_HANDLER = Type.getType(EventHandler.class); + private final static Type TYPE_EVENT_HANDLER = Type.getType(eventHandlerProxy); private final static Type TYPE_SETTING_CONTROL = Type.getType(SettingControl.class); private final static Type TYPE_EVENT_TYPE = Type.getType(EventType.class); private final static Type TYPE_EVENT_CONTROL = Type.getType(EventControl.class); @@ -90,7 +91,7 @@ final class EventHandlerCreator { } public static String makeEventHandlerName(long id) { - return EventHandler.class.getName() + id + SUFFIX; + return eventHandlerProxy.getName() + id + SUFFIX; } public EventHandlerCreator(long id, List settingInfos, EventType type, Class eventClass) { @@ -168,7 +169,7 @@ final class EventHandlerCreator { mv.visitVarInsn(Opcodes.ILOAD, 1); // registered mv.visitVarInsn(Opcodes.ALOAD, 2); // event type mv.visitVarInsn(Opcodes.ALOAD, 3); // event control - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(EventHandler.class), METHOD_EVENT_HANDLER_CONSTRUCTOR.getName(), METHOD_EVENT_HANDLER_CONSTRUCTOR.getDescriptor(), false); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(eventHandlerProxy), METHOD_EVENT_HANDLER_CONSTRUCTOR.getName(), METHOD_EVENT_HANDLER_CONSTRUCTOR.getDescriptor(), false); for (SettingInfo si : settingInfos) { mv.visitVarInsn(Opcodes.ALOAD, 0); // this mv.visitVarInsn(Opcodes.ALOAD, si.index + 4); // Setting Control @@ -180,7 +181,7 @@ final class EventHandlerCreator { if (field.isString()) { mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ALOAD, 0); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(EventHandler.class), "createStringFieldWriter", "()" + TYPE_STRING_POOL.getDescriptor(), false); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(eventHandlerProxy), "createStringFieldWriter", "()" + TYPE_STRING_POOL.getDescriptor(), false); mv.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, FIELD_PREFIX_STRING_POOL + fieldIndex, TYPE_STRING_POOL.getDescriptor()); } fieldIndex++; @@ -191,7 +192,7 @@ final class EventHandlerCreator { } private void buildClassInfo() { - String internalSuperName = ASMToolkit.getInternalName(EventHandler.class.getName()); + String internalSuperName = ASMToolkit.getInternalName(eventHandlerProxy.getName()); classWriter.visit(CLASS_VERSION, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, internalClassName, null, internalSuperName, null); for (SettingInfo si : settingInfos) { classWriter.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL, si.fieldName, TYPE_SETTING_CONTROL.getDescriptor(), null, null); diff --git a/src/share/classes/jdk/jfr/internal/EventHandlerProxyCreator.java b/src/share/classes/jdk/jfr/internal/EventHandlerProxyCreator.java new file mode 100644 index 0000000000000000000000000000000000000000..5eac8a97968137a818eebe4c987cf2538c135eb6 --- /dev/null +++ b/src/share/classes/jdk/jfr/internal/EventHandlerProxyCreator.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, Red Hat, Inc. + * + * 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. + * + * 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.util.StringJoiner; + +import jdk.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.MethodVisitor; +import jdk.internal.org.objectweb.asm.Opcodes; +import jdk.internal.org.objectweb.asm.Type; +import jdk.internal.org.objectweb.asm.commons.Method; +import jdk.jfr.Event; +import jdk.jfr.EventType; +import jdk.jfr.internal.handlers.EventHandler; + +/* + * Generates an EventHandler subclass dynamically, named EventHandlerProxy. + * EventHandlerProxy is located in a publicly accessible package (jdk.jfr) + * and is used as a superclass for classes generated by EventHandlerCreator. + * The rationale behind this scheme is to block access to jdk.jfr.internal + * package and sub-packages when there is a SecurityManager installed, while + * allowing application-defined event classes to invoke the required internal + * APIs. + */ +final class EventHandlerProxyCreator { + private static final int CLASS_VERSION = 52; + + private final static Type TYPE_EVENT_TYPE = Type.getType(EventType.class); + private final static Type TYPE_EVENT_CONTROL = Type.getType(EventControl.class); + private final static String DESCRIPTOR_EVENT_HANDLER = "(" + Type.BOOLEAN_TYPE.getDescriptor() + TYPE_EVENT_TYPE.getDescriptor() + TYPE_EVENT_CONTROL.getDescriptor() + ")V"; + private final static Method METHOD_EVENT_HANDLER_CONSTRUCTOR = new Method("", DESCRIPTOR_EVENT_HANDLER); + private final static String DESCRIPTOR_TIME_STAMP = "()" + Type.LONG_TYPE.getDescriptor(); + private final static Method METHOD_TIME_STAMP = new Method("timestamp", DESCRIPTOR_TIME_STAMP); + private final static String DESCRIPTOR_DURATION = "(" + Type.LONG_TYPE.getDescriptor() + ")" + Type.LONG_TYPE.getDescriptor(); + private final static Method METHOD_DURATION = new Method("duration", DESCRIPTOR_DURATION); + + private final static ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + private final static String className = "jdk.jfr.proxy.internal.EventHandlerProxy"; + private final static String internalClassName = ASMToolkit.getInternalName(className); + + // Create the Proxy class instance after all the previous static fields were initialized (textual order) + static final Class proxyClass = EventHandlerProxyCreator.makeEventHandlerProxyClass(); + + static void ensureInitialized() { + // trigger clinit which will setup the EventHandlerProxy class. + } + + public static Class makeEventHandlerProxyClass() { + buildClassInfo(); + buildConstructor(); + buildTimestampMethod(); + buildDurationMethod(); + byte[] bytes = classWriter.toByteArray(); + ASMToolkit.logASM(className, bytes); + return SecuritySupport.defineClass(className, bytes, Event.class.getClassLoader()).asSubclass(EventHandler.class); + } + + private static void buildConstructor() { + MethodVisitor mv = classWriter.visitMethod(0x0, METHOD_EVENT_HANDLER_CONSTRUCTOR.getName(), makeConstructorDescriptor(), null, null); + mv.visitVarInsn(Opcodes.ALOAD, 0); // this + mv.visitVarInsn(Opcodes.ILOAD, 1); // registered + mv.visitVarInsn(Opcodes.ALOAD, 2); // event type + mv.visitVarInsn(Opcodes.ALOAD, 3); // event control + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(EventHandler.class), METHOD_EVENT_HANDLER_CONSTRUCTOR.getName(), METHOD_EVENT_HANDLER_CONSTRUCTOR.getDescriptor(), false); + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + private static void buildClassInfo() { + String internalSuperName = ASMToolkit.getInternalName(EventHandler.class.getName()); + classWriter.visit(CLASS_VERSION, Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT + Opcodes.ACC_SUPER, internalClassName, null, internalSuperName, null); + } + + private static void buildTimestampMethod() { + MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, METHOD_TIME_STAMP.getName(), METHOD_TIME_STAMP.getDescriptor(), null, null); + mv.visitCode(); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(EventHandler.class), METHOD_TIME_STAMP.getName(), METHOD_TIME_STAMP.getDescriptor(), false); + mv.visitInsn(Opcodes.LRETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + private static void buildDurationMethod() { + MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, METHOD_DURATION.getName(), METHOD_DURATION.getDescriptor(), null, null); + mv.visitCode(); + mv.visitVarInsn(Opcodes.LLOAD, 0); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(EventHandler.class), METHOD_DURATION.getName(), METHOD_DURATION.getDescriptor(), false); + mv.visitInsn(Opcodes.LRETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + private static String makeConstructorDescriptor() { + StringJoiner constructordescriptor = new StringJoiner("", "(", ")V"); + constructordescriptor.add(Type.BOOLEAN_TYPE.getDescriptor()); + constructordescriptor.add(Type.getType(EventType.class).getDescriptor()); + constructordescriptor.add(Type.getType(EventControl.class).getDescriptor()); + return constructordescriptor.toString(); + } +} diff --git a/src/share/classes/jdk/jfr/internal/EventInstrumentation.java b/src/share/classes/jdk/jfr/internal/EventInstrumentation.java index 8b3654587169d7848b5a1a24b80d218a9f31eaa7..70c48065623a099c24abe8191eadb27fc14537aa 100644 --- a/src/share/classes/jdk/jfr/internal/EventInstrumentation.java +++ b/src/share/classes/jdk/jfr/internal/EventInstrumentation.java @@ -97,10 +97,11 @@ public final class EventInstrumentation { static final String FIELD_EVENT_HANDLER = "eventHandler"; static final String FIELD_START_TIME = "startTime"; + private static final Class eventHandlerProxy = EventHandlerProxyCreator.proxyClass; private static final Type ANNOTATION_TYPE_NAME = Type.getType(Name.class); private static final Type ANNOTATION_TYPE_REGISTERED = Type.getType(Registered.class); private static final Type ANNOTATION_TYPE_ENABLED = Type.getType(Enabled.class); - private static final Type TYPE_EVENT_HANDLER = Type.getType(EventHandler.class); + private static final Type TYPE_EVENT_HANDLER = Type.getType(eventHandlerProxy); private static final Type TYPE_SETTING_CONTROL = Type.getType(SettingControl.class); private static final Method METHOD_COMMIT = new Method("commit", Type.VOID_TYPE, new Type[0]); private static final Method METHOD_BEGIN = new Method("begin", Type.VOID_TYPE, new Type[0]); @@ -408,7 +409,7 @@ public final class EventInstrumentation { // eventHandler.write(...); // } methodVisitor.visitJumpInsn(Opcodes.IFEQ, end); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_HANDLER, Type.getDescriptor(EventHandler.class)); + methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_HANDLER, Type.getDescriptor(eventHandlerProxy)); methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, eventHandlerXInternalName); for (FieldInfo fi : fieldInfos) { @@ -427,7 +428,7 @@ public final class EventInstrumentation { updateMethod(METHOD_EVENT_SHOULD_COMMIT, methodVisitor -> { Label fail = new Label(); // if (!eventHandler.shoouldCommit(duration) goto fail; - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_HANDLER, Type.getDescriptor(EventHandler.class)); + methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_HANDLER, Type.getDescriptor(eventHandlerProxy)); methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_DURATION, "J"); ASMToolkit.invokeVirtual(methodVisitor, TYPE_EVENT_HANDLER.getInternalName(), METHOD_EVENT_HANDLER_SHOULD_COMMIT); @@ -435,7 +436,7 @@ public final class EventInstrumentation { for (SettingInfo si : settingInfos) { // if (!settingsMethod(eventHandler.settingX)) goto fail; methodVisitor.visitIntInsn(Opcodes.ALOAD, 0); - methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_HANDLER, Type.getDescriptor(EventHandler.class)); + methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_HANDLER, Type.getDescriptor(eventHandlerProxy)); methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, eventHandlerXInternalName); methodVisitor.visitFieldInsn(Opcodes.GETFIELD, eventHandlerXInternalName, si.fieldName, TYPE_SETTING_CONTROL.getDescriptor()); methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, si.internalSettingName); diff --git a/src/share/classes/jdk/jfr/internal/JVM.java b/src/share/classes/jdk/jfr/internal/JVM.java index 7c7baddebedc21a7ec44ced72d7a4af88c4f4db1..d577d38b2489ecec3e6d8073470f2847f2879b0f 100644 --- a/src/share/classes/jdk/jfr/internal/JVM.java +++ b/src/share/classes/jdk/jfr/internal/JVM.java @@ -54,6 +54,7 @@ public final class JVM { // subscribeLogLevel(tag, tag.id); // } Options.ensureInitialized(); + EventHandlerProxyCreator.ensureInitialized(); } /** diff --git a/src/share/classes/jdk/jfr/internal/handlers/EventHandler.java b/src/share/classes/jdk/jfr/internal/handlers/EventHandler.java index 79b72757d515244e9c9ce8ae6f6a14ccf5509106..52af9c13b46fe98fa3768db090ef7e219f01c1be 100644 --- a/src/share/classes/jdk/jfr/internal/handlers/EventHandler.java +++ b/src/share/classes/jdk/jfr/internal/handlers/EventHandler.java @@ -45,7 +45,13 @@ public abstract class EventHandler { private final EventControl eventControl; // Accessed by generated sub class - EventHandler(boolean registered, EventType eventType, EventControl eventControl) { + protected EventHandler(boolean registered, EventType eventType, EventControl eventControl) { + if (System.getSecurityManager() != null) { + // Do not allow user subclasses when security is enforced. + if (EventHandler.class.getClassLoader() != this.getClass().getClassLoader()) { + throw new SecurityException("Illegal subclass"); + } + } this.eventType = eventType; this.platformEventType = PrivateAccess.getInstance().getPlatformEventType(eventType); this.eventControl = eventControl; diff --git a/src/share/lib/security/java.security-aix b/src/share/lib/security/java.security-aix index e2c0ff9b3e92116113bb9559b0916ee05530dea9..9e9bd5984f2b82325067265a33fc8b33fcbde604 100644 --- a/src/share/lib/security/java.security-aix +++ b/src/share/lib/security/java.security-aix @@ -223,7 +223,10 @@ package.access=sun.,\ jdk.nashorn.internal.,\ jdk.nashorn.tools.,\ jdk.xml.internal.,\ - com.sun.activation.registries. + com.sun.activation.registries.,\ + jdk.jfr.events.,\ + jdk.jfr.internal.,\ + jdk.management.jfr.internal. # # List of comma-separated packages that start with or equal this string diff --git a/src/share/lib/security/java.security-linux b/src/share/lib/security/java.security-linux index 2f33b1acbc1f2f6443b61dc3888c8074dd39982a..44f9109bb457949d4473c866124c8279061e9740 100644 --- a/src/share/lib/security/java.security-linux +++ b/src/share/lib/security/java.security-linux @@ -223,7 +223,10 @@ package.access=sun.,\ jdk.nashorn.internal.,\ jdk.nashorn.tools.,\ jdk.xml.internal.,\ - com.sun.activation.registries. + com.sun.activation.registries.,\ + jdk.jfr.events.,\ + jdk.jfr.internal.,\ + jdk.management.jfr.internal. # # List of comma-separated packages that start with or equal this string diff --git a/src/share/lib/security/java.security-macosx b/src/share/lib/security/java.security-macosx index 277c3356d331c8851adb3de5b779329676e09eb3..180db8e680c7c03a0b94beb91a0787af01ac56d3 100644 --- a/src/share/lib/security/java.security-macosx +++ b/src/share/lib/security/java.security-macosx @@ -225,7 +225,10 @@ package.access=sun.,\ jdk.nashorn.tools.,\ jdk.xml.internal.,\ com.sun.activation.registries.,\ - apple. + apple.,\ + jdk.jfr.events.,\ + jdk.jfr.internal.,\ + jdk.management.jfr.internal. # # List of comma-separated packages that start with or equal this string diff --git a/src/share/lib/security/java.security-solaris b/src/share/lib/security/java.security-solaris index 27ce9669e78a31a41f983183c62ac3c215b8fc0d..e1ce31851e6d858c07b57308acadd8eee49980f1 100644 --- a/src/share/lib/security/java.security-solaris +++ b/src/share/lib/security/java.security-solaris @@ -225,7 +225,10 @@ package.access=sun.,\ jdk.nashorn.internal.,\ jdk.nashorn.tools.,\ jdk.xml.internal.,\ - com.sun.activation.registries. + com.sun.activation.registries.,\ + jdk.jfr.events.,\ + jdk.jfr.internal.,\ + jdk.management.jfr.internal. # # List of comma-separated packages that start with or equal this string diff --git a/src/share/lib/security/java.security-windows b/src/share/lib/security/java.security-windows index 58aad3a1d8df4f3218ffd37bac97b5d730f9919e..0d392fcfd117e0c3c00167d1996b3d4bba4606cc 100644 --- a/src/share/lib/security/java.security-windows +++ b/src/share/lib/security/java.security-windows @@ -225,7 +225,10 @@ package.access=sun.,\ jdk.nashorn.tools.,\ jdk.xml.internal.,\ com.sun.activation.registries.,\ - com.sun.java.accessibility. + com.sun.java.accessibility.,\ + jdk.jfr.events.,\ + jdk.jfr.internal.,\ + jdk.management.jfr.internal. # # List of comma-separated packages that start with or equal this string diff --git a/test/jdk/jfr/security/JFRSecurityTestSuite.java b/test/jdk/jfr/security/JFRSecurityTestSuite.java new file mode 100644 index 0000000000000000000000000000000000000000..d0360577a244f1593697ed1c755d3d7d955f3191 --- /dev/null +++ b/test/jdk/jfr/security/JFRSecurityTestSuite.java @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2019, Red Hat, Inc. + * + * 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. + * + * 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. + */ + +import java.io.File; +import java.io.FilePermission; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +import java.security.AccessControlContext; +import java.security.AccessControlException; +import java.security.AccessController; +import java.security.AllPermission; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; + +import java.time.Duration; + +import java.util.ArrayList; +import java.util.Comparator; + +import jdk.jfr.AnnotationElement; +import jdk.jfr.Configuration; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordingFile; +import jdk.jfr.Event; +import jdk.jfr.EventFactory; +import jdk.jfr.EventType; +import jdk.jfr.FlightRecorder; +import jdk.jfr.FlightRecorderListener; +import jdk.jfr.FlightRecorderPermission; +import jdk.jfr.internal.JVM; +import jdk.jfr.Label; +import jdk.jfr.Name; +import jdk.jfr.Recording; +import jdk.jfr.ValueDescriptor; + +/* + * @test + * @requires (jdk.version.major >= 8) + * @run main/othervm/timeout=30 -XX:+FlightRecorder -XX:StartFlightRecording JFRSecurityTestSuite + * @author Martin Balao (mbalao@redhat.com) + */ + +public class JFRSecurityTestSuite { + + private static boolean DEBUG = true; + private static SecurityManager sm; + private static String failureMessage = null; + private static Path jfrTmpDirPath = null; + private static Path recFilePath = null; + + interface F { + void call() throws Exception; + } + + @Label("MyEvent") + static class MyEvent extends Event { + } + + @Label("MyEvent2") + static class MyEvent2 extends Event { + } + + @Label("MyEvent3") + static class MyEvent3 extends Event { + } + + public static class MyPolicy extends Policy { + public MyPolicy() { + super(); + } + @Override + public PermissionCollection getPermissions(ProtectionDomain domain) { + PermissionCollection perms = new Permissions(); + perms.add(new AllPermission()); + return perms; + } + } + + private static class MyProtectionDomain extends ProtectionDomain { + MyProtectionDomain(CodeSource source, Permissions permissions) { + super(source, permissions); + } + + public boolean implies(Permission permission) { + if (DEBUG) { + System.out.println("Permission checked: " + permission); + } + return super.implies(permission); + } + } + + private static final Runnable periodicEvent = new Runnable() { + @Override + public void run() { + if (DEBUG) { + System.out.println("Periodic event"); + } + } + }; + private static final FlightRecorderListener frl = + new FlightRecorderListener() { }; + + public static void main(String[] args) throws Throwable { + + // Temporary directory for JFR files + jfrTmpDirPath = Files.createTempDirectory("jfr_test"); + + try { + File recFile = new File(jfrTmpDirPath.toString(), "rec"); + recFile.createNewFile(); + recFilePath = recFile.toPath(); + if (DEBUG) { + System.out.println("Test JFR tmp directory: " + jfrTmpDirPath); + } + + // Create a SecurityManager with all permissions enabled + Policy.setPolicy(new MyPolicy()); + sm = new SecurityManager(); + System.setSecurityManager(sm); + + CodeSource source = + Thread.currentThread().getClass(). + getProtectionDomain().getCodeSource(); + + // Create a constrained execution environment + Permissions noPermissions = new Permissions(); + ProtectionDomain constrainedPD = new MyProtectionDomain(source, + noPermissions); + AccessControlContext constrainedContext = + new AccessControlContext(new ProtectionDomain[] { + constrainedPD }); + + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + try { + doInConstrainedEnvironment(); + } catch (Throwable t) { + if (DEBUG) { + t.printStackTrace(); + } + failureMessage = t.toString(); + } + return null; + } + }, constrainedContext); + + // Create a JFR execution environment + Permissions JFRPermissions = new Permissions(); + JFRPermissions.add(new FilePermission(recFilePath.toString(), + "read,write")); + JFRPermissions.add(new FlightRecorderPermission( + "accessFlightRecorder")); + JFRPermissions.add(new FlightRecorderPermission( + "registerEvent")); + JFRPermissions.add(new RuntimePermission( + "accessDeclaredMembers")); + ProtectionDomain JFRPD = new MyProtectionDomain(source, + JFRPermissions); + AccessControlContext JFRContext = + new AccessControlContext(new ProtectionDomain[] { JFRPD }); + + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + try { + doInJFREnvironment(); + } catch (Throwable t) { + if (DEBUG) { + t.printStackTrace(); + } + failureMessage = t.toString(); + } + return null; + } + }, JFRContext); + + if (failureMessage != null) { + throw new Exception("TEST FAILED" + System.lineSeparator() + + failureMessage); + } + System.out.println("TEST PASS - OK"); + + } finally { + Files.walk(jfrTmpDirPath) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + } + + private static void doInConstrainedEnvironment() throws Throwable { + + final String testPath = "/etc"; + + checkNoDirectAccess(); + + assertPermission(() -> { + FlightRecorder.getFlightRecorder(); + }, FlightRecorderPermission.class, "accessFlightRecorder", false); + + assertPermission(() -> { + FlightRecorder.register(MyEvent.class); + }, FlightRecorderPermission.class, "registerEvent", false); + + assertPermission(() -> { + FlightRecorder.unregister(MyEvent.class); + }, FlightRecorderPermission.class, "registerEvent", false); + + assertPermission(() -> { + FlightRecorder.addPeriodicEvent(MyEvent.class, periodicEvent); + }, FlightRecorderPermission.class, "registerEvent", false); + + assertPermission(() -> { + FlightRecorder.removePeriodicEvent(periodicEvent); + }, FlightRecorderPermission.class, "registerEvent", false); + + assertPermission(() -> { + FlightRecorder.addListener(frl); + }, FlightRecorderPermission.class, "accessFlightRecorder", false); + + assertPermission(() -> { + FlightRecorder.removeListener(frl); + }, FlightRecorderPermission.class, "accessFlightRecorder", false); + + assertPermission(() -> { + new MyEvent().commit(); + }, FlightRecorderPermission.class, "registerEvent", true); + + assertPermission(() -> { + Configuration.create(Paths.get(testPath)); + }, FilePermission.class, testPath, false); + + assertPermission(() -> { + EventFactory.create(new ArrayList(), + new ArrayList()); + }, FlightRecorderPermission.class, "registerEvent", false); + + assertPermission(() -> { + new AnnotationElement(Name.class, "com.example.HelloWorld"); + }, FlightRecorderPermission.class, "registerEvent", false); + + assertPermission(() -> { + new ValueDescriptor(MyEvent.class, "", + new ArrayList()); + }, FlightRecorderPermission.class, "registerEvent", false); + + assertPermission(() -> { + new Recording(); + }, FlightRecorderPermission.class, "accessFlightRecorder", false); + + assertPermission(() -> { + new RecordingFile(Paths.get(testPath)); + }, FilePermission.class, testPath, false); + + assertPermission(() -> { + RecordingFile.readAllEvents(Paths.get(testPath)); + }, FilePermission.class, testPath, false); + + assertPermission(() -> { + EventType.getEventType(MyEvent2.class); + }, FlightRecorderPermission.class, "registerEvent", true); + } + + private static void doInJFREnvironment() throws Throwable { + + checkNoDirectAccess(); + + FlightRecorder fr = FlightRecorder.getFlightRecorder(); + + Configuration c = Configuration.getConfiguration("default"); + + Recording recordingDefault = new Recording(c); + + recordingDefault.start(); + + FlightRecorder.addListener(frl); + + FlightRecorder.register(MyEvent3.class); + + FlightRecorder.addPeriodicEvent(MyEvent3.class, periodicEvent); + + new MyEvent3().commit(); + + FlightRecorder.removePeriodicEvent(periodicEvent); + + FlightRecorder.unregister(MyEvent3.class); + + FlightRecorder.removeListener(frl); + + recordingDefault.stop(); + + try (Recording snapshot = fr.takeSnapshot()) { + if (snapshot.getSize() > 0) { + snapshot.setMaxSize(100_000_000); + snapshot.setMaxAge(Duration.ofMinutes(5)); + snapshot.dump(recFilePath); + } + } + checkRecording(); + + Files.write(recFilePath, new byte[0], + StandardOpenOption.TRUNCATE_EXISTING); + recordingDefault.dump(recFilePath); + checkRecording(); + + try { + class MyEventHandler extends jdk.jfr.internal.handlers.EventHandler { + MyEventHandler() { + super(true, null, null); + } + } + MyEventHandler myEv = new MyEventHandler(); + throw new Exception("EventHandler must not be subclassable"); + } catch (SecurityException e) {} + } + + private static void checkRecording() throws Throwable { + boolean myEvent3Found = false; + try (RecordingFile rf = new RecordingFile(recFilePath)) { + while (rf.hasMoreEvents()) { + RecordedEvent event = rf.readEvent(); + if (event.getEventType().getName().equals( + "JFRSecurityTestSuite$MyEvent3")) { + if (DEBUG) { + System.out.println("Recording of MyEvent3 event:"); + System.out.println(event); + } + myEvent3Found = true; + break; + } + } + } + if (!myEvent3Found) { + throw new Exception("MyEvent3 event was expected in JFR recording"); + } + } + + private static void checkNoDirectAccess() throws Throwable { + assertPermission(() -> { + sm.checkPackageAccess("jdk.jfr.internal"); + }, RuntimePermission.class, null, false); + + assertPermission(() -> { + Class.forName("jdk.jfr.internal.JVM"); + }, RuntimePermission.class, null, false); + + assertPermission(() -> { + JVM.getJVM(); + }, RuntimePermission.class, null, false); + + assertPermission(() -> { + sm.checkPackageAccess("jdk.jfr.events"); + }, RuntimePermission.class, null, false); + + assertPermission(() -> { + Class.forName("jdk.jfr.events.AbstractJDKEvent"); + }, RuntimePermission.class, null, false); + + assertPermission(() -> { + sm.checkPackageAccess("jdk.management.jfr.internal"); + }, RuntimePermission.class, null, false); + } + + private static void assertPermission(F f, Class expectedPermClass, + String expectedPermName, boolean expectedIsCause) + throws Throwable { + String exceptionMessage = expectedPermClass.toString() + + (expectedPermName != null && !expectedPermName.isEmpty() ? + " " + expectedPermName : "") + + " permission check expected."; + try { + f.call(); + throw new Exception(exceptionMessage); + } catch (Throwable t) { + Throwable t2 = null; + if (expectedIsCause) { + t2 = t.getCause(); + } else { + t2 = t; + } + if (t2 instanceof AccessControlException) { + AccessControlException ace = (AccessControlException)t2; + Permission p = ace.getPermission(); + if (!p.getClass().equals(expectedPermClass) || + (expectedPermName != null && !expectedPermName.isEmpty() && + !p.getName().equals(expectedPermName))) { + throw new Exception(exceptionMessage, ace); + } + } else { + throw t; + } + } + } +} +