8003881: Prevent lambda implementing inner classes from allowing the creation of new instances
Summary: Lambda implementing inner classes now has private constructor (thanks Kumar)
Reviewed-by: ksrini
--- a/jdk/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java Thu Dec 06 15:51:44 2012 -0500
+++ b/jdk/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java Thu Dec 06 21:55:55 2012 -0800
@@ -25,15 +25,15 @@
package java.lang.invoke;
-import java.io.FileOutputStream;
-import java.io.IOException;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.concurrent.atomic.AtomicInteger;
-import sun.util.logging.PlatformLogger;
import jdk.internal.org.objectweb.asm.*;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
import sun.misc.Unsafe;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
/**
* InnerClassLambdaMetafactory
@@ -120,13 +120,34 @@
*
* @return a CallSite, which, when invoked, will return an instance of the
* functional interface
- * @throws ReflectiveOperationException
+ * @throws ReflectiveOperationException, LambdaConversionException
*/
@Override
CallSite buildCallSite() throws ReflectiveOperationException, LambdaConversionException {
final Class<?> innerClass = spinInnerClass();
if (invokedType.parameterCount() == 0) {
- return new ConstantCallSite(MethodHandles.constant(samBase, innerClass.newInstance()));
+ final Constructor[] ctrs = AccessController.doPrivileged(
+ new PrivilegedAction<Constructor[]>() {
+ @Override
+ public Constructor[] run() {
+ return innerClass.getDeclaredConstructors();
+ }
+ });
+ if (ctrs.length != 1) {
+ throw new ReflectiveOperationException("Expected one lambda constructor for "
+ + innerClass.getCanonicalName() + ", got " + ctrs.length);
+ }
+ // The lambda implementing inner class constructor is private, set
+ // it accessible (by us) before creating the constant sole instance
+ AccessController.doPrivileged(new PrivilegedAction<Void>() {
+ @Override
+ public Void run() {
+ ctrs[0].setAccessible(true);
+ return null;
+ }
+ });
+ Object inst = ctrs[0].newInstance();
+ return new ConstantCallSite(MethodHandles.constant(samBase, inst));
} else {
return new ConstantCallSite(
MethodHandles.Lookup.IMPL_LOOKUP
@@ -144,7 +165,7 @@
private <T> Class<? extends T> spinInnerClass() throws LambdaConversionException {
String samName = samBase.getName().replace('.', '/');
- cw.visit(CLASSFILE_VERSION, ACC_PUBLIC + ACC_SUPER, lambdaClassName, null, NAME_MAGIC_ACCESSOR_IMPL,
+ cw.visit(CLASSFILE_VERSION, ACC_SUPER, lambdaClassName, null, NAME_MAGIC_ACCESSOR_IMPL,
isSerializable ? new String[]{samName, NAME_SERIALIZABLE} : new String[]{samName});
// Generate final fields to be filled in by constructor
@@ -186,17 +207,27 @@
final byte[] classBytes = cw.toByteArray();
- if (System.getProperty("debug.dump.generated") != null) {
+ /*** Uncomment to dump the generated file
System.out.printf("Loaded: %s (%d bytes) %n", lambdaClassName, classBytes.length);
try (FileOutputStream fos = new FileOutputStream(lambdaClassName.replace('/', '.') + ".class")) {
fos.write(classBytes);
} catch (IOException ex) {
- PlatformLogger.getLogger(InnerClassLambdaMetafactory.class.getName()).severe(ex.getMessage(), ex);
+ Logger.getLogger(InnerClassLambdaMetafactory.class.getName()).log(Level.SEVERE, null, ex);
}
- }
+ ***/
ClassLoader loader = targetClass.getClassLoader();
- ProtectionDomain pd = (loader == null) ? null : targetClass.getProtectionDomain();
+ ProtectionDomain pd = (loader == null)
+ ? null
+ : AccessController.doPrivileged(
+ new PrivilegedAction<ProtectionDomain>() {
+ @Override
+ public ProtectionDomain run() {
+ return targetClass.getProtectionDomain();
+ }
+ }
+ );
+
return (Class<? extends T>) Unsafe.getUnsafe().defineClass(lambdaClassName, classBytes, 0, classBytes.length, loader, pd);
}
@@ -205,7 +236,7 @@
*/
private void generateConstructor() {
// Generate constructor
- MethodVisitor ctor = cw.visitMethod(ACC_PUBLIC, NAME_CTOR, constructorDesc, null, null);
+ MethodVisitor ctor = cw.visitMethod(ACC_PRIVATE, NAME_CTOR, constructorDesc, null, null);
ctor.visitCode();
ctor.visitVarInsn(ALOAD, 0);
ctor.visitMethodInsn(INVOKESPECIAL, NAME_MAGIC_ACCESSOR_IMPL, NAME_CTOR, METHOD_DESCRIPTOR_VOID);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/invoke/lambda/LambdaAccessControlDoPrivilegedTest.java Thu Dec 06 21:55:55 2012 -0800
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2012, 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 8003881
+ * @summary tests DoPrivileged action (implemented as lambda expressions) by
+ * inserting them into the BootClassPath.
+ * @compile -XDignore.symbol.file LambdaAccessControlDoPrivilegedTest.java
+ * @run main/othervm LambdaAccessControlDoPrivilegedTest
+ */
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class LambdaAccessControlDoPrivilegedTest extends LUtils {
+ public static void main(String... args) {
+ final List<String> scratch = new ArrayList();
+ scratch.clear();
+ scratch.add("import java.security.*;");
+ scratch.add("public class DoPriv {");
+ scratch.add("public static void main(String... args) {");
+ scratch.add("String prop = AccessController.doPrivileged((PrivilegedAction<String>) () -> {");
+ scratch.add("return System.getProperty(\"user.home\");");
+ scratch.add("});");
+ scratch.add("}");
+ scratch.add("}");
+ File doprivJava = new File("DoPriv.java");
+ File doprivClass = getClassFile(doprivJava);
+ createFile(doprivJava, scratch);
+
+ scratch.clear();
+ scratch.add("public class Bar {");
+ scratch.add("public static void main(String... args) {");
+ scratch.add("System.out.println(\"sun.boot.class.path\" + \"=\" +");
+ scratch.add(" System.getProperty(\"sun.boot.class.path\", \"\"));");
+ scratch.add("System.setSecurityManager(new SecurityManager());");
+ scratch.add("DoPriv.main();");
+ scratch.add("}");
+ scratch.add("}");
+
+ File barJava = new File("Bar.java");
+ File barClass = getClassFile(barJava);
+ createFile(barJava, scratch);
+
+ String[] javacArgs = {barJava.getName(), doprivJava.getName()};
+ compile(javacArgs);
+ File jarFile = new File("foo.jar");
+ String[] jargs = {"cvf", jarFile.getName(), doprivClass.getName()};
+ jarTool.run(jargs);
+ doprivJava.delete();
+ doprivClass.delete();
+ TestResult tr = doExec(JAVA_CMD.getAbsolutePath(),
+ "-Xbootclasspath/p:foo.jar",
+ "-cp", ".", "Bar");
+ tr.assertZero("testDoPrivileged fails");
+ barJava.delete();
+ barClass.delete();
+ jarFile.delete();
+ }
+}
+
+/*
+ * support infrastructure to invoke a java class from the command line
+ */
+class LUtils {
+ static final sun.tools.jar.Main jarTool =
+ new sun.tools.jar.Main(System.out, System.err, "jar-tool");
+ static final com.sun.tools.javac.Main javac =
+ new com.sun.tools.javac.Main();
+ static final File cwd = new File(".").getAbsoluteFile();
+ static final String JAVAHOME = System.getProperty("java.home");
+ static final boolean isWindows =
+ System.getProperty("os.name", "unknown").startsWith("Windows");
+ //static final boolean isSDK = JAVAHOME.endsWith("jre");
+ static final File JAVA_BIN_FILE = new File(JAVAHOME, "bin");
+ static final File JAVA_CMD = new File(JAVA_BIN_FILE,
+ isWindows ? "java.exe" : "java");
+
+ protected LUtils() {
+ }
+
+ public static void compile(String... args) {
+ if (javac.compile(args) != 0) {
+ throw new RuntimeException("compilation fails");
+ }
+ }
+
+ static void createFile(File outFile, List<String> content) {
+ try {
+ Files.write(outFile.getAbsoluteFile().toPath(), content,
+ Charset.defaultCharset());
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ static File getClassFile(File javaFile) {
+ return javaFile.getName().endsWith(".java")
+ ? new File(javaFile.getName().replace(".java", ".class"))
+ : null;
+ }
+
+ static String getSimpleName(File inFile) {
+ String fname = inFile.getName();
+ return fname.substring(0, fname.indexOf("."));
+ }
+
+ static TestResult doExec(String... cmds) {
+ return doExec(null, null, cmds);
+ }
+
+ /*
+ * A method which executes a java cmd and returns the results in a container
+ */
+ static TestResult doExec(Map<String, String> envToSet,
+ java.util.Set<String> envToRemove, String... cmds) {
+ String cmdStr = "";
+ for (String x : cmds) {
+ cmdStr = cmdStr.concat(x + " ");
+ }
+ ProcessBuilder pb = new ProcessBuilder(cmds);
+ Map<String, String> env = pb.environment();
+ if (envToRemove != null) {
+ for (String key : envToRemove) {
+ env.remove(key);
+ }
+ }
+ if (envToSet != null) {
+ env.putAll(envToSet);
+ }
+ BufferedReader rdr = null;
+ try {
+ List<String> outputList = new ArrayList<>();
+ pb.redirectErrorStream(true);
+ Process p = pb.start();
+ rdr = new BufferedReader(new InputStreamReader(p.getInputStream()));
+ String in = rdr.readLine();
+ while (in != null) {
+ outputList.add(in);
+ in = rdr.readLine();
+ }
+ p.waitFor();
+ p.destroy();
+
+ return new TestResult(cmdStr, p.exitValue(), outputList,
+ env, new Throwable("current stack of the test"));
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ throw new RuntimeException(ex.getMessage());
+ }
+ }
+
+ static class TestResult {
+ String cmd;
+ int exitValue;
+ List<String> testOutput;
+ Map<String, String> env;
+ Throwable t;
+
+ public TestResult(String str, int rv, List<String> oList,
+ Map<String, String> env, Throwable t) {
+ cmd = str;
+ exitValue = rv;
+ testOutput = oList;
+ this.env = env;
+ this.t = t;
+ }
+
+ void assertZero(String message) {
+ if (exitValue != 0) {
+ System.err.println(this);
+ throw new RuntimeException(message);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringWriter sw = new StringWriter();
+ PrintWriter status = new PrintWriter(sw);
+ status.println("Cmd: " + cmd);
+ status.println("Return code: " + exitValue);
+ status.println("Environment variable:");
+ for (String x : env.keySet()) {
+ status.println("\t" + x + "=" + env.get(x));
+ }
+ status.println("Output:");
+ for (String x : testOutput) {
+ status.println("\t" + x);
+ }
+ status.println("Exception:");
+ status.println(t.getMessage());
+ t.printStackTrace(status);
+
+ return sw.getBuffer().toString();
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/invoke/lambda/LambdaAccessControlTest.java Thu Dec 06 21:55:55 2012 -0800
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2012, 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 8003881
+ * @summary tests Lambda expression with a a security manager at top level
+ * @compile -XDignore.symbol.file LambdaAccessControlTest.java
+ *
+ * @run main/othervm LambdaAccessControlTest
+ */
+
+public class LambdaAccessControlTest extends LUtils {
+ public static void main(String... args) {
+ System.setSecurityManager(new SecurityManager());
+ JJ<Integer> iii = (new CC())::impl;
+ System.out.printf(">>> %s\n", iii.foo(44));
+ iii = DD::impl;
+ System.out.printf(">>> %s\n", iii.foo(44));
+ return;
+ }
+}
+/*
+ * support classes for the test
+ */
+interface II<T> { Object foo(T x); }
+interface JJ<R extends Number> extends II<R> { }
+class CC { String impl(int i) { return "impl:"+i; }}
+class DD { static String impl(int i) { return "impl:"+i; }}