Skip to content
Permalink
Browse files

8234466: Class loading deadlock involving X509Factory#commitEvent()

Reviewed-by: alanb, chegar, dfuchs
  • Loading branch information
coffeys committed Jan 13, 2020
1 parent ab90653 commit 2c5167803a0abaa6568053798e0349c3e8aa9b70
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2020, 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
@@ -157,8 +157,10 @@
private boolean jvInitialized;
private boolean verify;
private final Runtime.Version version; // current version
private final int versionFeature; // version.feature()
private final int versionFeature; // version.feature()
private boolean isMultiRelease; // is jar multi-release?
static final ThreadLocal<Boolean> isInitializing =
ThreadLocal.withInitial(() -> Boolean.FALSE);

// indicates if Class-Path attribute present
private boolean hasClassPathAttribute;
@@ -1031,8 +1033,13 @@ synchronized void ensureInitialization() {
throw new RuntimeException(e);
}
if (jv != null && !jvInitialized) {
initializeVerifier();
jvInitialized = true;
isInitializing.set(Boolean.TRUE);
try {
initializeVerifier();
jvInitialized = true;
} finally {
isInitializing.set(Boolean.FALSE);
}
}
}

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 2020, 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
@@ -68,4 +68,8 @@ public Attributes getTrustedAttributes(Manifest man, String name) {
public void ensureInitialization(JarFile jar) {
jar.ensureInitialization();
}

public Boolean isInitializing() {
return JarFile.isInitializing.get();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 2020, 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
@@ -45,4 +45,5 @@
public List<Object> getManifestDigests(JarFile jar);
public Attributes getTrustedAttributes(Manifest man, String name);
public void ensureInitialization(JarFile jar);
public Boolean isInitializing();
}
@@ -25,6 +25,11 @@

package jdk.internal.event;

import jdk.internal.access.JavaUtilJarAccess;
import jdk.internal.access.SharedSecrets;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
@@ -37,42 +42,53 @@

public final class EventHelper {

private static final JavaUtilJarAccess JUJA = SharedSecrets.javaUtilJarAccess();
private static volatile boolean loggingSecurity;
private static volatile System.Logger securityLogger;
private static final VarHandle LOGGER_HANDLE;
static {
try {
LOGGER_HANDLE =
MethodHandles.lookup().findStaticVarHandle(
EventHelper.class, "securityLogger", System.Logger.class);
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
private static final System.Logger.Level LOG_LEVEL = System.Logger.Level.DEBUG;

// helper class used for logging security related events for now
private static final String SECURITY_LOGGER_NAME = "jdk.event.security";
private static final System.Logger SECURITY_LOGGER =
System.getLogger(SECURITY_LOGGER_NAME);
private static final boolean LOGGING_SECURITY =
SECURITY_LOGGER.isLoggable(LOG_LEVEL);


public static void logTLSHandshakeEvent(Instant start,
String peerHost,
int peerPort,
String cipherSuite,
String protocolVersion,
long peerCertId) {
assert securityLogger != null;
String prepend = getDurationString(start);
SECURITY_LOGGER.log(LOG_LEVEL, prepend +
securityLogger.log(LOG_LEVEL, prepend +
" TLSHandshake: {0}:{1,number,#}, {2}, {3}, {4,number,#}",
peerHost, peerPort, protocolVersion, cipherSuite, peerCertId);
}

public static void logSecurityPropertyEvent(String key,
String value) {

if (isLoggingSecurity()) {
SECURITY_LOGGER.log(LOG_LEVEL,
"SecurityPropertyModification: key:{0}, value:{1}", key, value);
}
assert securityLogger != null;
securityLogger.log(LOG_LEVEL,
"SecurityPropertyModification: key:{0}, value:{1}", key, value);
}

public static void logX509ValidationEvent(int anchorCertId,
int[] certIds) {
assert securityLogger != null;
String codes = IntStream.of(certIds)
.mapToObj(Integer::toString)
.collect(Collectors.joining(", "));
SECURITY_LOGGER.log(LOG_LEVEL,
securityLogger.log(LOG_LEVEL,
"ValidationChain: {0,number,#}, {1}", anchorCertId, codes);
}

@@ -85,7 +101,8 @@ public static void logX509CertificateEvent(String algId,
long certId,
long beginDate,
long endDate) {
SECURITY_LOGGER.log(LOG_LEVEL, "X509Certificate: Alg:{0}, Serial:{1}" +
assert securityLogger != null;
securityLogger.log(LOG_LEVEL, "X509Certificate: Alg:{0}, Serial:{1}" +
", Subject:{2}, Issuer:{3}, Key type:{4}, Length:{5,number,#}" +
", Cert Id:{6,number,#}, Valid from:{7}, Valid until:{8}",
algId, serialNum, subject, issuer, keyType, length,
@@ -124,6 +141,14 @@ private static String getDurationString(Instant start) {
* @return boolean indicating whether an event should be logged
*/
public static boolean isLoggingSecurity() {
return LOGGING_SECURITY;
// Avoid a bootstrap issue where the commitEvent attempts to
// trigger early loading of System Logger but where
// the verification process still has JarFiles locked
if (securityLogger == null && !JUJA.isInitializing()) {
LOGGER_HANDLE.compareAndSet( null, System.getLogger(SECURITY_LOGGER_NAME));
loggingSecurity = securityLogger.isLoggable(LOG_LEVEL);
}
return loggingSecurity;
}

}
@@ -0,0 +1,24 @@
/*
* Copyright (c) 2020, 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.
*
* 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.
*/

public abstract class FooService { }
@@ -0,0 +1,185 @@
/*
* Copyright (c) 2020, 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.
*
* 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.
*/

/*
* @test
* @bug 8234466
* @summary attempt to trigger class loading from the classloader
* during JAR file verification
* @library /test/lib
* @build jdk.test.lib.compiler.CompilerUtils
* jdk.test.lib.process.*
* jdk.test.lib.util.JarUtils
* jdk.test.lib.JDKToolLauncher
* MultiThreadLoad FooService
* @modules java.base/jdk.internal.access:+open
* @run main MultiProviderTest
* @run main MultiProviderTest sign
*/

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import jdk.test.lib.JDKToolFinder;
import jdk.test.lib.JDKToolLauncher;
import jdk.test.lib.Utils;
import jdk.test.lib.compiler.CompilerUtils;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import jdk.test.lib.util.JarUtils;

import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static java.util.Arrays.asList;

public class MultiProviderTest {

private static final String METAINFO = "META-INF/services/FooService";
private static String COMBO_CP = Utils.TEST_CLASS_PATH + File.pathSeparator;
private static String TEST_CLASS_PATH = System.getProperty("test.classes", ".");
private static boolean signJars = false;
static final int NUM_JARS = 5;


private static final String KEYSTORE = "keystore.jks";
private static final String ALIAS = "JavaTest";
private static final String STOREPASS = "changeit";
private static final String KEYPASS = "changeit";

public static void main(String[] args) throws Throwable {
signJars = args.length >=1 && args[0].equals("sign");
initialize();
List<String> cmds = new ArrayList<>();
cmds.add(JDKToolFinder.getJDKTool("java"));
cmds.addAll(asList(Utils.getTestJavaOpts()));
cmds.addAll(List.of(
"-cp",
COMBO_CP,
"--add-opens",
"java.base/jdk.internal.access=ALL-UNNAMED",
"-Djava.util.logging.config.file=" +
Path.of(System.getProperty("test.src", "."), "logging.properties").toString(),
"MultiThreadLoad",
TEST_CLASS_PATH));

try {
OutputAnalyzer outputAnalyzer = ProcessTools.executeCommand(cmds.stream()
.filter(t -> !t.isEmpty())
.toArray(String[]::new))
.shouldHaveExitValue(0);
System.out.println("Output:" + outputAnalyzer.getOutput());
} catch (Throwable t) {
throw new RuntimeException("Unexpected fail.", t);
}
}

public static void initialize() throws Throwable {
if (signJars) {
genKey();
}
for (int i = 0; i < NUM_JARS; i++) {
String p = "FooProvider" + i;
String jarName = "FooProvider" + i + ".jar";
Path javaPath = Path.of(p + ".java");
Path jarPath = Path.of(p + ".jar");
String contents = "public class FooProvider" + i + " extends FooService { }";
Files.write(javaPath, contents.getBytes());
CompilerUtils.compile(javaPath, Path.of(System.getProperty("test.classes")), "-cp", Utils.TEST_CLASS_PATH);
createJar(jarPath, p, List.of(p));
if (signJars) {
signJar(TEST_CLASS_PATH + File.separator + jarName);
}
COMBO_CP += TEST_CLASS_PATH + File.separator + jarName + File.pathSeparator;
}
}

private static void createProviderConfig(Path config, String providerName) throws Exception {
Files.createDirectories(config.getParent());
Files.write(config, providerName.getBytes(), CREATE);
}

private static void createJar(Path jar, String provider, List<String> files) throws Exception {
Path xdir = Path.of(provider);
createProviderConfig(xdir.resolve(METAINFO), provider);

for (String f : files) {
Path source = Path.of(Utils.TEST_CLASSES, f + ".class");
Path target = xdir.resolve(source.getFileName());
Files.copy(source, target, REPLACE_EXISTING);
}
JarUtils.createJarFile(Path.of(TEST_CLASS_PATH, jar.getFileName().toString()), xdir);
}

private static void genKey() throws Throwable {
String keytool = JDKToolFinder.getJDKTool("keytool");
Files.deleteIfExists(Paths.get(KEYSTORE));
ProcessTools.executeCommand(keytool,
"-J-Duser.language=en",
"-J-Duser.country=US",
"-genkey",
"-keyalg", "rsa",
"-alias", ALIAS,
"-keystore", KEYSTORE,
"-keypass", KEYPASS,
"-dname", "cn=sample",
"-storepass", STOREPASS
).shouldHaveExitValue(0);
}


private static OutputAnalyzer signJar(String jarName) throws Throwable {
List<String> args = new ArrayList<>();
args.add("-verbose");
args.add(jarName);
args.add(ALIAS);

return jarsigner(args);
}

private static OutputAnalyzer jarsigner(List<String> extra)
throws Throwable {
JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jarsigner")
.addVMArg("-Duser.language=en")
.addVMArg("-Duser.country=US")
.addToolArg("-keystore")
.addToolArg(KEYSTORE)
.addToolArg("-storepass")
.addToolArg(STOREPASS)
.addToolArg("-keypass")
.addToolArg(KEYPASS);
for (String s : extra) {
if (s.startsWith("-J")) {
launcher.addVMArg(s.substring(2));
} else {
launcher.addToolArg(s);
}
}
return ProcessTools.executeCommand(launcher.getCommand());
}

}

0 comments on commit 2c51678

Please sign in to comment.