1 /*
   2  * Copyright (c) 2019, Red Hat, Inc.
   3  *
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * This code is free software; you can redistribute it and/or modify it
   7  * under the terms of the GNU General Public License version 2 only, as
   8  * published by the Free Software Foundation.
   9  *
  10  * This code is distributed in the hope that it will be useful, but WITHOUT
  11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  13  * version 2 for more details (a copy is included in the LICENSE file that
  14  * accompanied this code).
  15  *
  16  * You should have received a copy of the GNU General Public License version
  17  * 2 along with this work; if not, write to the Free Software Foundation,
  18  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  19  *
  20  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  21  * or visit www.oracle.com if you need additional information or have any
  22  * questions.
  23  */
  24 
  25 import java.io.File;
  26 import java.io.FilePermission;
  27 
  28 import java.nio.file.Files;
  29 import java.nio.file.Path;
  30 import java.nio.file.Paths;
  31 import java.nio.file.StandardOpenOption;
  32 
  33 import java.security.AccessControlContext;
  34 import java.security.AccessControlException;
  35 import java.security.AccessController;
  36 import java.security.AllPermission;
  37 import java.security.CodeSource;
  38 import java.security.Permission;
  39 import java.security.PermissionCollection;
  40 import java.security.Permissions;
  41 import java.security.Policy;
  42 import java.security.PrivilegedAction;
  43 import java.security.ProtectionDomain;
  44 
  45 import java.time.Duration;
  46 
  47 import java.util.ArrayList;
  48 import java.util.Comparator;
  49 
  50 import jdk.jfr.AnnotationElement;
  51 import jdk.jfr.Configuration;
  52 import jdk.jfr.consumer.RecordedEvent;
  53 import jdk.jfr.consumer.RecordingFile;
  54 import jdk.jfr.Event;
  55 import jdk.jfr.EventFactory;
  56 import jdk.jfr.EventType;
  57 import jdk.jfr.FlightRecorder;
  58 import jdk.jfr.FlightRecorderListener;
  59 import jdk.jfr.FlightRecorderPermission;
  60 import jdk.jfr.internal.JVM;
  61 import jdk.jfr.Label;
  62 import jdk.jfr.Name;
  63 import jdk.jfr.Recording;
  64 import jdk.jfr.ValueDescriptor;
  65 
  66 import jdk.test.lib.Platform;
  67 
  68 /*
  69  * @test
  70  * @requires (jdk.version.major >= 8)
  71  * @library /lib /
  72  * @key jfr
  73  * @run main/othervm/timeout=30 -XX:+FlightRecorder -XX:StartFlightRecording JFRSecurityTestSuite
  74  * @author Martin Balao (mbalao@redhat.com)
  75  */
  76 
  77 public class JFRSecurityTestSuite {
  78 
  79     private static boolean DEBUG = true;
  80     private static SecurityManager sm;
  81     private static String failureMessage = null;
  82     private static Path jfrTmpDirPath = null;
  83     private static Path recFilePath = null;
  84     private static String protectedLocationPath;
  85 
  86     interface F {
  87         void call() throws Exception;
  88     }
  89 
  90     @Label("MyEvent")
  91     static class MyEvent extends Event {
  92     }
  93 
  94     @Label("MyEvent2")
  95     static class MyEvent2 extends Event {
  96     }
  97 
  98     @Label("MyEvent3")
  99     static class MyEvent3 extends Event {
 100     }
 101 
 102     public static class MyPolicy extends Policy {
 103         public MyPolicy() {
 104             super();
 105         }
 106         @Override
 107         public PermissionCollection getPermissions(ProtectionDomain domain) {
 108             PermissionCollection perms = new Permissions();
 109             perms.add(new AllPermission());
 110             return perms;
 111         }
 112     }
 113 
 114     private static class MyProtectionDomain extends ProtectionDomain {
 115         MyProtectionDomain(CodeSource source, Permissions permissions) {
 116             super(source, permissions);
 117         }
 118 
 119         public boolean implies(Permission permission) {
 120             if (DEBUG) {
 121                 System.out.println("Permission checked: " + permission);
 122             }
 123             return super.implies(permission);
 124         }
 125     }
 126 
 127     private static final Runnable periodicEvent = new Runnable() {
 128                 @Override
 129                 public void run() {
 130                     if (DEBUG) {
 131                         System.out.println("Periodic event");
 132                     }
 133                 }
 134             };
 135     private static final FlightRecorderListener frl =
 136             new FlightRecorderListener() { };
 137 
 138     public static void main(String[] args) throws Throwable {
 139 
 140         // Temporary directory for JFR files
 141         jfrTmpDirPath = Files.createTempDirectory("jfr_test");
 142 
 143         try {
 144             setProtectedLocation();
 145 
 146             File recFile = new File(jfrTmpDirPath.toString(), "rec");
 147             recFile.createNewFile();
 148             recFilePath = recFile.toPath();
 149             if (DEBUG) {
 150                 System.out.println("Test JFR tmp directory: " + jfrTmpDirPath);
 151             }
 152 
 153             // Create a SecurityManager with all permissions enabled
 154             Policy.setPolicy(new MyPolicy());
 155             sm = new SecurityManager();
 156             System.setSecurityManager(sm);
 157 
 158             CodeSource source =
 159                     Thread.currentThread().getClass().
 160                             getProtectionDomain().getCodeSource();
 161 
 162             // Create a constrained execution environment
 163             Permissions noPermissions = new Permissions();
 164             ProtectionDomain constrainedPD = new MyProtectionDomain(source,
 165                     noPermissions);
 166             AccessControlContext constrainedContext =
 167                     new AccessControlContext(new ProtectionDomain[] {
 168                             constrainedPD });
 169 
 170             AccessController.doPrivileged(new PrivilegedAction<Void>() {
 171                 @Override
 172                 public Void run() {
 173                     try {
 174                         doInConstrainedEnvironment();
 175                     } catch (Throwable t) {
 176                         if (DEBUG) {
 177                             t.printStackTrace();
 178                         }
 179                         failureMessage = t.toString();
 180                     }
 181                     return null;
 182                 }
 183             }, constrainedContext);
 184 
 185             // Create a JFR execution environment
 186             Permissions JFRPermissions = new Permissions();
 187             JFRPermissions.add(new FilePermission(recFilePath.toString(),
 188                     "read,write"));
 189             JFRPermissions.add(new FlightRecorderPermission(
 190                     "accessFlightRecorder"));
 191             JFRPermissions.add(new FlightRecorderPermission(
 192                     "registerEvent"));
 193             JFRPermissions.add(new RuntimePermission(
 194                     "accessDeclaredMembers"));
 195             ProtectionDomain JFRPD = new MyProtectionDomain(source,
 196                     JFRPermissions);
 197             AccessControlContext JFRContext =
 198                     new AccessControlContext(new ProtectionDomain[] { JFRPD });
 199 
 200             AccessController.doPrivileged(new PrivilegedAction<Void>() {
 201                 @Override
 202                 public Void run() {
 203                     try {
 204                         doInJFREnvironment();
 205                     } catch (Throwable t) {
 206                         if (DEBUG) {
 207                             t.printStackTrace();
 208                         }
 209                         failureMessage = t.toString();
 210                     }
 211                     return null;
 212                 }
 213             }, JFRContext);
 214 
 215             if (failureMessage != null) {
 216                 throw new Exception("TEST FAILED" + System.lineSeparator() +
 217                         failureMessage);
 218             }
 219             System.out.println("TEST PASS - OK");
 220 
 221         } finally {
 222             Files.walk(jfrTmpDirPath)
 223                     .sorted(Comparator.reverseOrder())
 224                     .map(Path::toFile)
 225                     .forEach(File::delete);
 226         }
 227     }
 228 
 229     private static void setProtectedLocation() {
 230         if (Platform.isWindows()) {
 231             protectedLocationPath = System.getenv("%WINDIR%");
 232             if (protectedLocationPath == null) {
 233                 // fallback
 234                 protectedLocationPath = "c:\\windows";
 235             }
 236         } else {
 237             protectedLocationPath = "/etc";
 238         }
 239     }
 240 
 241     private static void doInConstrainedEnvironment() throws Throwable {
 242         checkNoDirectAccess();
 243 
 244         assertPermission(() -> {
 245                     FlightRecorder.getFlightRecorder();
 246                 }, FlightRecorderPermission.class, "accessFlightRecorder", false);
 247 
 248         assertPermission(() -> {
 249                     FlightRecorder.register(MyEvent.class);
 250                 }, FlightRecorderPermission.class, "registerEvent", false);
 251 
 252         assertPermission(() -> {
 253                     FlightRecorder.unregister(MyEvent.class);
 254                 }, FlightRecorderPermission.class, "registerEvent", false);
 255 
 256         assertPermission(() -> {
 257                     FlightRecorder.addPeriodicEvent(MyEvent.class, periodicEvent);
 258                 }, FlightRecorderPermission.class, "registerEvent", false);
 259 
 260         assertPermission(() -> {
 261                     FlightRecorder.removePeriodicEvent(periodicEvent);
 262                 }, FlightRecorderPermission.class, "registerEvent", false);
 263 
 264         assertPermission(() -> {
 265                     FlightRecorder.addListener(frl);
 266                 }, FlightRecorderPermission.class, "accessFlightRecorder", false);
 267 
 268         assertPermission(() -> {
 269                 FlightRecorder.removeListener(frl);
 270                 }, FlightRecorderPermission.class, "accessFlightRecorder", false);
 271 
 272         assertPermission(() -> {
 273                     new MyEvent().commit();
 274                 }, FlightRecorderPermission.class, "registerEvent", true);
 275 
 276         assertPermission(() -> {
 277                         Configuration.create(Paths.get(protectedLocationPath));
 278                 }, FilePermission.class, protectedLocationPath, false);
 279 
 280         assertPermission(() -> {
 281                     EventFactory.create(new ArrayList<AnnotationElement>(),
 282                             new ArrayList<ValueDescriptor>());
 283                 }, FlightRecorderPermission.class, "registerEvent", false);
 284 
 285         assertPermission(() -> {
 286                     new AnnotationElement(Name.class, "com.example.HelloWorld");
 287                 }, FlightRecorderPermission.class, "registerEvent", false);
 288 
 289         assertPermission(() -> {
 290                 new ValueDescriptor(MyEvent.class, "",
 291                         new ArrayList<AnnotationElement>());
 292                 }, FlightRecorderPermission.class, "registerEvent", false);
 293 
 294         assertPermission(() -> {
 295                     new Recording();
 296                 }, FlightRecorderPermission.class, "accessFlightRecorder", false);
 297 
 298         assertPermission(() -> {
 299                         new RecordingFile(Paths.get(protectedLocationPath));
 300                 }, FilePermission.class, protectedLocationPath, false);
 301 
 302         assertPermission(() -> {
 303                         RecordingFile.readAllEvents(Paths.get(protectedLocationPath));
 304                 }, FilePermission.class, protectedLocationPath, false);
 305 
 306         assertPermission(() -> {
 307                         EventType.getEventType(MyEvent2.class);
 308                 }, FlightRecorderPermission.class, "registerEvent", true);
 309     }
 310 
 311     private static void doInJFREnvironment() throws Throwable {
 312 
 313         checkNoDirectAccess();
 314 
 315         FlightRecorder fr = FlightRecorder.getFlightRecorder();
 316 
 317         Configuration c = Configuration.getConfiguration("default");
 318 
 319         Recording recordingDefault = new Recording(c);
 320 
 321         recordingDefault.start();
 322 
 323         FlightRecorder.addListener(frl);
 324 
 325         FlightRecorder.register(MyEvent3.class);
 326 
 327         FlightRecorder.addPeriodicEvent(MyEvent3.class, periodicEvent);
 328 
 329         new MyEvent3().commit();
 330 
 331         FlightRecorder.removePeriodicEvent(periodicEvent);
 332 
 333         FlightRecorder.unregister(MyEvent3.class);
 334 
 335         FlightRecorder.removeListener(frl);
 336 
 337         recordingDefault.stop();
 338 
 339         try (Recording snapshot = fr.takeSnapshot()) {
 340             if (snapshot.getSize() > 0) {
 341                 snapshot.setMaxSize(100_000_000);
 342                 snapshot.setMaxAge(Duration.ofMinutes(5));
 343                 snapshot.dump(recFilePath);
 344             }
 345         }
 346         checkRecording();
 347 
 348         Files.write(recFilePath, new byte[0],
 349                 StandardOpenOption.TRUNCATE_EXISTING);
 350         recordingDefault.dump(recFilePath);
 351         checkRecording();
 352 
 353         try {
 354             class MyEventHandler extends jdk.jfr.internal.handlers.EventHandler {
 355                 MyEventHandler() {
 356                     super(true, null, null);
 357                 }
 358             }
 359             MyEventHandler myEv = new MyEventHandler();
 360             throw new Exception("EventHandler must not be subclassable");
 361          } catch (SecurityException e) {}
 362     }
 363 
 364     private static void checkRecording() throws Throwable {
 365         boolean myEvent3Found = false;
 366         try (RecordingFile rf = new RecordingFile(recFilePath)) {
 367             while (rf.hasMoreEvents()) {
 368                 RecordedEvent event = rf.readEvent();
 369                 if (event.getEventType().getName().equals(
 370                         "JFRSecurityTestSuite$MyEvent3")) {
 371                     if (DEBUG) {
 372                         System.out.println("Recording of MyEvent3 event:");
 373                         System.out.println(event);
 374                     }
 375                     myEvent3Found = true;
 376                     break;
 377                 }
 378             }
 379         }
 380         if (!myEvent3Found) {
 381             throw new Exception("MyEvent3 event was expected in JFR recording");
 382         }
 383     }
 384 
 385     private static void checkNoDirectAccess() throws Throwable {
 386         assertPermission(() -> {
 387                     sm.checkPackageAccess("jdk.jfr.internal");
 388                 }, RuntimePermission.class, null, false);
 389 
 390         assertPermission(() -> {
 391                     Class.forName("jdk.jfr.internal.JVM");
 392                 }, RuntimePermission.class, null, false);
 393 
 394         assertPermission(() -> {
 395                     JVM.getJVM();
 396                 }, RuntimePermission.class, null, false);
 397 
 398         assertPermission(() -> {
 399                     sm.checkPackageAccess("jdk.jfr.events");
 400                 }, RuntimePermission.class, null, false);
 401 
 402         assertPermission(() -> {
 403                     Class.forName("jdk.jfr.events.AbstractJDKEvent");
 404                 }, RuntimePermission.class, null, false);
 405 
 406         assertPermission(() -> {
 407                     sm.checkPackageAccess("jdk.management.jfr.internal");
 408                 }, RuntimePermission.class, null, false);
 409     }
 410 
 411     private static void assertPermission(F f, Class<?> expectedPermClass,
 412             String expectedPermName, boolean expectedIsCause)
 413             throws Throwable {
 414         String exceptionMessage = expectedPermClass.toString() +
 415                 (expectedPermName != null && !expectedPermName.isEmpty() ?
 416                         " " + expectedPermName : "") +
 417                 " permission check expected.";
 418         try {
 419             f.call();
 420             throw new Exception(exceptionMessage);
 421         } catch (Throwable t) {
 422             Throwable t2 = null;
 423             if (expectedIsCause) {
 424                 t2 = t.getCause();
 425             } else {
 426                 t2 = t;
 427             }
 428             if (t2 instanceof AccessControlException) {
 429                 AccessControlException ace = (AccessControlException)t2;
 430                 Permission p = ace.getPermission();
 431                 if (!p.getClass().equals(expectedPermClass) ||
 432                         (expectedPermName != null && !expectedPermName.isEmpty() &&
 433                                 !p.getName().equals(expectedPermName))) {
 434                     throw new Exception(exceptionMessage, ace);
 435                 }
 436             } else {
 437                 throw t;
 438             }
 439         }
 440     }
 441 }
 442