Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8343427

Class file load hook crashes on archived classes from multi-release JARs

XMLWordPrintable

    • b26
    • x86_64
    • linux_ubuntu

      A DESCRIPTION OF THE PROBLEM :
      Let's say we registered a class file transformer in a Java agent, which enables the class file load hook. Whenever a class is loaded, we need to call the hook with the contents of the corresponding class file. If the class is loaded from an AppCDS archive, KlassFactory::check_shared_class_file_load_hook uses the class name to construct the name of the JAR entry (e.g. com/bar/Foo -> com/bar/Foo.class), and searches the original JAR for it.

      This doesn't work if the archived class comes from a multi-release JAR, because the path to Foo might be something like META-INF/versions/9/com/bar/Foo.class instead. When we look for com/bar/Foo.class and no matching entry is found, ClassPathZipEntry::open_entry returns null, causing a null pointer dereference in KlassFactory::check_shared_class_file_load_hook.

      I reproduced the issue on JDK 17, JDK 21, and JDK 23 using the instructions below.

      In JDK 17, starting a JFR recording with -XX:StartFlightRecording also enables the class file load hook, which is how we originally encountered this issue. However, JFR doesn't seem to enable the hook anymore in JDK 21.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Please see the source code section below.

      ACTUAL -
      The JVM segfaults with an error like:

      #
      # A fatal error has been detected by the Java Runtime Environment:
      #
      # SIGSEGV (0xb) at pc=0x00007f93daabd93c, pid=354536, tid=354537
      #
      # JRE version: Java(TM) SE Runtime Environment (17.0.13+10) (build 17.0.13+10-LTS-268)
      # Java VM: Java HotSpot(TM) 64-Bit Server VM (17.0.13+10-LTS-268, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, linux-amd64)
      # Problematic frame:
      # V [libjvm.so+0xa3d93c] KlassFactory::check_shared_class_file_load_hook(InstanceKlass*, Symbol*, Handle, Handle, ClassFileStream const*, JavaThread*)+0x5c
      #
      # No core dump will be written. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
      #
      # An error report file with more information is saved as:
      # /tmp/tmp.8XZioO8kdD/hs_err_pid354536.log
      #
      # If you would like to submit a bug report, please visit:
      # https://bugreport.java.com/bugreport/crash.jsp
      #

      ---------- BEGIN SOURCE ----------
      This Bash script reproduces the issue on JDK 17, 21, and 23:

      #!/bin/bash

      set -eux -o pipefail

      cd "$(mktemp -d)"

      # Downloaded from this page:
      # https://www.oracle.com/java/technologies/downloads/#java17
      JAVA_HOME=$HOME/jdk-17.0.13
      $JAVA_HOME/bin/java -version

      cat > Foo.java << EOF
      class Foo {}
      EOF

      cat > Main.java << EOF
      public class Main {
        public static void main(String[] args) throws Exception {
          System.out.println(Class.forName("Foo"));
        }
      }
      EOF

      cat > Agent.java << EOF
      import java.lang.instrument.Instrumentation;

      public class Agent {
        public static void premain(String agentArgs, Instrumentation inst) {
          inst.addTransformer(new Transformer());
        }
      }
      EOF

      cat > Transformer.java << EOF
      import java.lang.instrument.ClassFileTransformer;
      import java.security.ProtectionDomain;

      public class Transformer implements ClassFileTransformer {
        public byte[] transform(
          ClassLoader loader,
          String className,
          Class<?> classBeingRedefined,
          ProtectionDomain protectionDomain,
          byte[] classfileBuffer
        ) {
          return null;
        }
      }
      EOF

      cat > MANIFEST.MF << EOF
      Manifest-Version: 1.0
      Premain-Class: Agent
      Can-Retransform-Classes: true
      EOF

      ${JAVA_HOME}/bin/javac --release 9 -d classes-9 Foo.java
      ${JAVA_HOME}/bin/javac -d classes Main.java Agent.java Transformer.java
      ${JAVA_HOME}/bin/jar --create --file main.jar --manifest MANIFEST.MF -C classes . --release 9 -C classes-9 .
      ${JAVA_HOME}/bin/jar tvf main.jar

      echo "Foo" > classes.txt

      ${JAVA_HOME}/bin/java -cp main.jar \
        -Xshare:dump \
        -XX:SharedClassListFile=classes.txt \
        -XX:SharedArchiveFile=archive.jsa

      ${JAVA_HOME}/bin/java \
        -cp main.jar \
        -javaagent:main.jar \
        -Xshare:on \
        -XX:SharedArchiveFile=archive.jsa \
        Main

      This simpler script uses Java Flight Recorder instead of a class file transformer, but only JDK 17 is affected:

      #!/bin/bash

      set -eux -o pipefail

      cd "$(mktemp -d)"

      # Downloaded from this page:
      # https://www.oracle.com/java/technologies/downloads/#java17
      JAVA_HOME=$HOME/jdk-17.0.13
      $JAVA_HOME/bin/java -version

      cat > Foo.java << EOF
      class Foo {}
      EOF

      cat > Main.java << EOF
      public class Main {
        public static void main(String[] args) throws Exception {
          System.out.println(Class.forName("Foo"));
        }
      }
      EOF

      ${JAVA_HOME}/bin/javac --release 9 -d classes-9 Foo.java
      ${JAVA_HOME}/bin/javac -d classes Main.java
      ${JAVA_HOME}/bin/jar --create --file main.jar -C classes . --release 9 -C classes-9 .
      ${JAVA_HOME}/bin/jar tvf main.jar

      echo "Foo" > classes.txt

      ${JAVA_HOME}/bin/java -cp main.jar \
        -Xshare:dump \
        -XX:SharedClassListFile=classes.txt \
        -XX:SharedArchiveFile=archive.jsa

      ${JAVA_HOME}/bin/java \
        -cp main.jar \
        -Xshare:on \
        -XX:SharedArchiveFile=archive.jsa \
        -XX:StartFlightRecording:filename=recording.jfr \
        Main
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Don't use multi-release JARs, or exclude classes under paths like META-INF/versions/xyz when dumping the AppCDS archive.

      FREQUENCY : always


            ccheung Calvin Cheung
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            7 Start watching this issue

              Created:
              Updated:
              Resolved: