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
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
- links to
-
Review(master) openjdk/jdk/22262