From cee74f9e677e74deda72638bcc0a3e9307262938 Mon Sep 17 00:00:00 2001 From: Aleksei Efimov Date: Thu, 21 Nov 2024 20:55:02 +0000 Subject: [PATCH] 8338536: Permanently disable remote code downloading in JNDI Reviewed-by: dfuchs --- .../classes/com/sun/jndi/ldap/EventQueue.java | 2 +- .../sun/jndi/ldap/NamingEventNotifier.java | 2 +- .../share/classes/com/sun/jndi/ldap/Obj.java | 10 +- .../com/sun/jndi/ldap/VersionHelper.java | 63 +---- .../naming/internal/NamingManagerHelper.java | 35 +-- .../sun/naming/internal/VersionHelper.java | 159 +++---------- .../javax/naming/spi/NamingManager.java | 9 +- .../jndi/rmi/registry/RegistryContext.java | 34 +-- .../share/classes/module-info.java | 5 + .../ObjectFactoryBuilderCodebaseTest.java | 223 ++++++++++++++++++ .../objects/TestObjectFactoryBuilder.java | 74 ++++++ 11 files changed, 364 insertions(+), 252 deletions(-) create mode 100644 test/jdk/com/sun/jndi/rmi/registry/objects/ObjectFactoryBuilderCodebaseTest.java create mode 100644 test/jdk/com/sun/jndi/rmi/registry/objects/TestObjectFactoryBuilder.java diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/EventQueue.java b/src/java.naming/share/classes/com/sun/jndi/ldap/EventQueue.java index 29652dc5e6f..3d88e179894 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/EventQueue.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/EventQueue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2024, 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 diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/NamingEventNotifier.java b/src/java.naming/share/classes/com/sun/jndi/ldap/NamingEventNotifier.java index fd168b3ccb0..27b83b91515 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/NamingEventNotifier.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/NamingEventNotifier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2024, 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 diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java b/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java index 1e6b30cb1c5..0d28928559f 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2024, 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 @@ -236,7 +236,7 @@ final class Obj { if (!VersionHelper.isSerialDataAllowed()) { throw new NamingException("Object deserialization is not allowed"); } - ClassLoader cl = helper.getURLClassLoader(codebases); + ClassLoader cl = Thread.currentThread().getContextClassLoader(); return deserializeObject((byte[])attr.get(), cl); } else if ((attr = attrs.get(JAVA_ATTRIBUTES[REMOTE_LOC])) != null) { // javaRemoteLocation attribute (RMI stub will be created) @@ -246,7 +246,7 @@ final class Obj { // For backward compatibility only return decodeRmiObject( (String)attrs.get(JAVA_ATTRIBUTES[CLASSNAME]).get(), - (String)attr.get(), codebases); + (String)attr.get()); } attr = attrs.get(JAVA_ATTRIBUTES[OBJECT_CLASS]); @@ -368,7 +368,7 @@ final class Obj { * @deprecated For backward compatibility only */ private static Object decodeRmiObject(String className, - String rmiName, String[] codebases) throws NamingException { + String rmiName) throws NamingException { return new Reference(className, new StringRefAddr("URL", rmiName)); } @@ -410,7 +410,7 @@ final class Obj { int start, sep, posn; Base64.Decoder decoder = null; - ClassLoader cl = helper.getURLClassLoader(codebases); + ClassLoader cl = Thread.currentThread().getContextClassLoader(); /* * Temporary array for decoded RefAddr addresses - used to ensure diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/VersionHelper.java b/src/java.naming/share/classes/com/sun/jndi/ldap/VersionHelper.java index 53a20b99594..bb888a1457a 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/VersionHelper.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/VersionHelper.java @@ -25,22 +25,10 @@ package com.sun.jndi.ldap; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.security.AccessControlContext; -import java.security.AccessController; -import java.security.PrivilegedAction; - public final class VersionHelper { private static final VersionHelper helper = new VersionHelper(); - /** - * Determines whether classes may be loaded from an arbitrary URL code base. - */ - private static final boolean trustURLCodebase; - /** * Determines whether objects may be deserialized or reconstructed from a content of * 'javaSerializedData', 'javaRemoteLocation' or 'javaReferenceAddress' LDAP attributes. @@ -48,29 +36,13 @@ public final class VersionHelper { private static final boolean trustSerialData; static { - // System property to control whether classes may be loaded from an - // arbitrary URL code base - String trust = getPrivilegedProperty( - "com.sun.jndi.ldap.object.trustURLCodebase", "false"); - trustURLCodebase = "true".equalsIgnoreCase(trust); - // System property to control whether classes are allowed to be loaded from // 'javaSerializedData', 'javaRemoteLocation' or 'javaReferenceAddress' attributes. - String trustSerialDataSp = getPrivilegedProperty( + String trustSerialDataSp = System.getProperty( "com.sun.jndi.ldap.object.trustSerialData", "false"); trustSerialData = "true".equalsIgnoreCase(trustSerialDataSp); } - @SuppressWarnings("removal") - private static String getPrivilegedProperty(String propertyName, String defaultVal) { - PrivilegedAction action = () -> System.getProperty(propertyName, defaultVal); - if (System.getSecurityManager() == null) { - return action.run(); - } else { - return AccessController.doPrivileged(action); - } - } - private VersionHelper() { } @@ -89,41 +61,12 @@ public final class VersionHelper { return trustSerialData; } - ClassLoader getURLClassLoader(String[] url) throws MalformedURLException { - ClassLoader parent = getContextClassLoader(); - /* - * Classes may only be loaded from an arbitrary URL code base when - * the system property com.sun.jndi.ldap.object.trustURLCodebase - * has been set to "true". - */ - if (url != null && trustURLCodebase) { - return URLClassLoader.newInstance(getUrlArray(url), parent); - } else { - return parent; - } - } - Class loadClass(String className) throws ClassNotFoundException { - return Class.forName(className, true, getContextClassLoader()); + return Class.forName(className, true, + Thread.currentThread().getContextClassLoader()); } Thread createThread(Runnable r) { return new Thread(r); } - - @SuppressWarnings("removal") - private ClassLoader getContextClassLoader() { - PrivilegedAction act = - Thread.currentThread()::getContextClassLoader; - return AccessController.doPrivileged(act); - } - - @SuppressWarnings("deprecation") - private static URL[] getUrlArray(String[] url) throws MalformedURLException { - URL[] urlArray = new URL[url.length]; - for (int i = 0; i < urlArray.length; i++) { - urlArray[i] = new URL(url[i]); - } - return urlArray; - } } diff --git a/src/java.naming/share/classes/com/sun/naming/internal/NamingManagerHelper.java b/src/java.naming/share/classes/com/sun/naming/internal/NamingManagerHelper.java index 5270c4c916e..856a1b0a444 100644 --- a/src/java.naming/share/classes/com/sun/naming/internal/NamingManagerHelper.java +++ b/src/java.naming/share/classes/com/sun/naming/internal/NamingManagerHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, 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 @@ -181,9 +181,8 @@ public class NamingManagerHelper { static ObjectFactory getObjectFactoryFromReference( Reference ref, String factoryName, Predicate> filter) throws IllegalAccessException, - InstantiationException, - MalformedURLException { - Class clas = null; + InstantiationException { + Class clas; // Try to use current class loader try { @@ -193,27 +192,11 @@ public class NamingManagerHelper { return null; } } catch (ClassNotFoundException e) { - // ignore and continue - // e.printStackTrace(); + return null; } - // All other exceptions are passed up. - - // Not in class path; try to use codebase - String codebase; - if (clas == null && - (codebase = ref.getFactoryClassLocation()) != null) { - try { - clas = helper.loadClass(factoryName, codebase); - // Validate factory's class with the objects factory serial filter - if (clas == null || !filter.test(clas)) { - return null; - } - } catch (ClassNotFoundException e) { - } - } - + assert clas != null; @SuppressWarnings("deprecation") // Class.newInstance - ObjectFactory result = (clas != null) ? (ObjectFactory) clas.newInstance() : null; + ObjectFactory result = (ObjectFactory) clas.newInstance(); return result; } @@ -401,12 +384,6 @@ public class NamingManagerHelper { ObjectFactoryBuilder builder) throws NamingException { if (object_factory_builder != null) throw new IllegalStateException("ObjectFactoryBuilder already set"); - - @SuppressWarnings("removal") - SecurityManager security = System.getSecurityManager(); - if (security != null) { - security.checkSetFactory(); - } object_factory_builder = builder; } diff --git a/src/java.naming/share/classes/com/sun/naming/internal/VersionHelper.java b/src/java.naming/share/classes/com/sun/naming/internal/VersionHelper.java index 6dbdb6f420b..ea2a0f9c179 100644 --- a/src/java.naming/share/classes/com/sun/naming/internal/VersionHelper.java +++ b/src/java.naming/share/classes/com/sun/naming/internal/VersionHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2024, 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 @@ -28,15 +28,9 @@ package com.sun.naming.internal; import javax.naming.NamingEnumeration; import java.io.IOException; import java.io.InputStream; -import java.net.MalformedURLException; import java.net.URL; -import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; import java.util.*; /** @@ -53,21 +47,6 @@ import java.util.*; public final class VersionHelper { private static final VersionHelper helper = new VersionHelper(); - /** - * Determines whether classes may be loaded from an arbitrary URL code base. - */ - private static final boolean TRUST_URL_CODE_BASE; - - static { - // System property to control whether classes may be loaded from an - // arbitrary URL code base - PrivilegedAction act - = () -> System.getProperty("com.sun.jndi.ldap.object.trustURLCodebase", "false"); - @SuppressWarnings("removal") - String trust = AccessController.doPrivileged(act); - TRUST_URL_CODE_BASE = "true".equalsIgnoreCase(trust); - } - static final String[] PROPS = new String[]{ javax.naming.Context.INITIAL_CONTEXT_FACTORY, javax.naming.Context.OBJECT_FACTORIES, @@ -101,22 +80,6 @@ public final class VersionHelper { return loadClass(className, false, getContextClassLoader()); } - /** - * @param className A non-null fully qualified class name. - * @param codebase A non-null, space-separated list of URL strings. - */ - public Class loadClass(String className, String codebase) - throws ClassNotFoundException, MalformedURLException { - if (TRUST_URL_CODE_BASE) { - ClassLoader parent = getContextClassLoader(); - ClassLoader cl - = URLClassLoader.newInstance(getUrlArray(codebase), parent); - return loadClass(className, cl); - } else { - return null; - } - } - /** * Package private. *

@@ -136,37 +99,19 @@ public final class VersionHelper { /* * Returns a JNDI property from the system properties. Returns - * null if the property is not set, or if there is no permission - * to read it. + * null if the property is not set. */ - @SuppressWarnings("removal") String getJndiProperty(int i) { - PrivilegedAction act = () -> { - try { - return System.getProperty(PROPS[i]); - } catch (SecurityException e) { - return null; - } - }; - return AccessController.doPrivileged(act); + return System.getProperty(PROPS[i]); } /* * Reads each property in PROPS from the system properties, and * returns their values -- in order -- in an array. For each * unset property, the corresponding array element is set to null. - * Returns null if there is no permission to call System.getProperties(). */ String[] getJndiProperties() { - PrivilegedAction act = () -> { - try { - return System.getProperties(); - } catch (SecurityException e) { - return null; - } - }; - @SuppressWarnings("removal") - Properties sysProps = AccessController.doPrivileged(act); + Properties sysProps = System.getProperties(); if (sysProps == null) { return null; } @@ -199,16 +144,12 @@ public final class VersionHelper { * Returns the resource of a given name associated with a particular * class (never null), or null if none can be found. */ - @SuppressWarnings("removal") InputStream getResourceAsStream(Class c, String name) { - PrivilegedAction act = () -> { - try { - return c.getModule().getResourceAsStream(resolveName(c, name)); - } catch (IOException x) { - return null; - } - }; - return AccessController.doPrivileged(act); + try { + return c.getModule().getResourceAsStream(resolveName(c, name)); + } catch (IOException x) { + return null; + } } /* @@ -217,20 +158,16 @@ public final class VersionHelper { * * @param filename The file name, sans directory. */ - @SuppressWarnings("removal") InputStream getJavaHomeConfStream(String filename) { - PrivilegedAction act = () -> { - try { - String javahome = System.getProperty("java.home"); - if (javahome == null) { - return null; - } - return Files.newInputStream(Path.of(javahome, "conf", filename)); - } catch (Exception e) { + try { + String javahome = System.getProperty("java.home"); + if (javahome == null) { return null; } - }; - return AccessController.doPrivileged(act); + return Files.newInputStream(Path.of(javahome, "conf", filename)); + } catch (Exception e) { + return null; + } } /* @@ -239,19 +176,12 @@ public final class VersionHelper { * loader. Null represents the bootstrap class loader in some * Java implementations. */ - @SuppressWarnings("removal") NamingEnumeration getResources(ClassLoader cl, String name) throws IOException { Enumeration urls; - PrivilegedExceptionAction> act = () -> - (cl == null) - ? ClassLoader.getSystemResources(name) - : cl.getResources(name); - try { - urls = AccessController.doPrivileged(act); - } catch (PrivilegedActionException e) { - throw (IOException) e.getException(); - } + urls = (cl == null) + ? ClassLoader.getSystemResources(name) + : cl.getResources(name); return new InputStreamEnumeration(urls); } @@ -265,39 +195,18 @@ public final class VersionHelper { * Please don't expose this method as public. * @throws SecurityException if the class loader is not accessible */ - @SuppressWarnings("removal") ClassLoader getContextClassLoader() { - - PrivilegedAction act = () -> { - ClassLoader loader = Thread.currentThread().getContextClassLoader(); - if (loader == null) { - // Don't use bootstrap class loader directly! - loader = ClassLoader.getSystemClassLoader(); - } - return loader; - }; - return AccessController.doPrivileged(act); - } - - private static URL[] getUrlArray(String codebase) - throws MalformedURLException { - // Parse codebase into separate URLs - StringTokenizer parser = new StringTokenizer(codebase); - List list = new ArrayList<>(); - while (parser.hasMoreTokens()) { - @SuppressWarnings("deprecation") - var u = new URL(parser.nextToken()); - list.add(u); + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + if (loader == null) { + // Don't use bootstrap class loader directly! + loader = ClassLoader.getSystemClassLoader(); } - return list.toArray(new URL[0]); + return loader; } /** * Given an enumeration of URLs, an instance of this class represents - * an enumeration of their InputStreams. Each operation on the URL - * enumeration is performed within a doPrivileged block. - * This is used to enumerate the resources under a foreign codebase. - * This class is not MT-safe. + * an enumeration of their InputStreams. */ private class InputStreamEnumeration implements NamingEnumeration { @@ -314,19 +223,15 @@ public final class VersionHelper { * Returns the next InputStream, or null if there are no more. * An InputStream that cannot be opened is skipped. */ - @SuppressWarnings("removal") private InputStream getNextElement() { - PrivilegedAction act = () -> { - while (urls.hasMoreElements()) { - try { - return urls.nextElement().openStream(); - } catch (IOException e) { - // skip this URL - } + while (urls.hasMoreElements()) { + try { + return urls.nextElement().openStream(); + } catch (IOException e) { + // skip this URL } - return null; - }; - return AccessController.doPrivileged(act); + } + return null; } public boolean hasMore() { diff --git a/src/java.naming/share/classes/javax/naming/spi/NamingManager.java b/src/java.naming/share/classes/javax/naming/spi/NamingManager.java index c54080ba585..4d57b541ec1 100644 --- a/src/java.naming/share/classes/javax/naming/spi/NamingManager.java +++ b/src/java.naming/share/classes/javax/naming/spi/NamingManager.java @@ -123,9 +123,12 @@ public class NamingManager { * or {@code Referenceable} containing a factory class name, * use the named factory to create the object. * Return {@code refInfo} if the factory cannot be created. - * Under JDK 1.1, if the factory class must be loaded from a location - * specified in the reference, a {@code SecurityManager} must have - * been installed or the factory creation will fail. + * Downloading a factory class from a location specified in the reference + * can be supported by a custom implementation of {@link ObjectFactoryBuilder}. + * The {@linkplain Reference#getFactoryClassLocation() factory class + * location}, if present, is ignored. A custom {@link ObjectFactoryBuilder} + * {@linkplain #setObjectFactoryBuilder(ObjectFactoryBuilder) may be used} + * if a different policy is desired. * If an exception is encountered while creating the factory, * it is passed up to the caller. *

  • If {@code refInfo} is a {@code Reference} or diff --git a/src/jdk.naming.rmi/share/classes/com/sun/jndi/rmi/registry/RegistryContext.java b/src/jdk.naming.rmi/share/classes/com/sun/jndi/rmi/registry/RegistryContext.java index 3ca0039604e..5becceb8294 100644 --- a/src/jdk.naming.rmi/share/classes/com/sun/jndi/rmi/registry/RegistryContext.java +++ b/src/jdk.naming.rmi/share/classes/com/sun/jndi/rmi/registry/RegistryContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2024, 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 @@ -35,8 +35,6 @@ import java.rmi.*; import java.rmi.server.*; import java.rmi.registry.Registry; import java.rmi.registry.LocateRegistry; -import java.security.AccessController; -import java.security.PrivilegedAction; import javax.naming.*; import javax.naming.spi.NamingManager; @@ -57,19 +55,6 @@ public class RegistryContext implements Context, Referenceable { private int port; private static final NameParser nameParser = new AtomicNameParser(); private static final String SOCKET_FACTORY = "com.sun.jndi.rmi.factory.socket"; - /** - * Determines whether classes may be loaded from an arbitrary URL code base. - */ - static final boolean trustURLCodebase; - static { - // System property to control whether classes may be loaded from an - // arbitrary URL codebase - PrivilegedAction act = () -> System.getProperty( - "com.sun.jndi.rmi.object.trustURLCodebase", "false"); - @SuppressWarnings("removal") - String trust = AccessController.doPrivileged(act); - trustURLCodebase = "true".equalsIgnoreCase(trust); - } Reference reference = null; // ref used to create this context, if any @@ -481,12 +466,6 @@ public class RegistryContext implements Context, Referenceable { ? ((RemoteReference)r).getReference() : (Object)r; - /* - * Classes may only be loaded from an arbitrary URL codebase when - * the system property com.sun.jndi.rmi.object.trustURLCodebase - * has been set to "true". - */ - // Use reference if possible Reference ref = null; if (obj instanceof Reference) { @@ -495,11 +474,14 @@ public class RegistryContext implements Context, Referenceable { ref = ((Referenceable)(obj)).getReference(); } - if (ref != null && ref.getFactoryClassLocation() != null && - !trustURLCodebase) { + /* + * Downloading a factory class from a location specified in the reference + * can be supported by a custom implementation of "ObjectFactoryBuilder". + */ + if (NamingManagerHelper.getObjectFactoryBuilder() == null + && ref != null && ref.getFactoryClassLocation() != null) { throw new ConfigurationException( - "The object factory is untrusted. Set the system property" + - " 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'."); + "Remote object factories are not supported"); } return NamingManagerHelper.getObjectInstance(obj, name, this, environment, ObjectFactoriesFilter::checkRmiFilter); diff --git a/src/jdk.naming.rmi/share/classes/module-info.java b/src/jdk.naming.rmi/share/classes/module-info.java index 6afa13aa84d..f2ba8f7775f 100644 --- a/src/jdk.naming.rmi/share/classes/module-info.java +++ b/src/jdk.naming.rmi/share/classes/module-info.java @@ -58,6 +58,11 @@ * implementation. *
  • * + *

    Downloading a factory class from a {@linkplain javax.naming.Reference#getFactoryClassLocation() + * location} specified in the reference can be supported by a custom implementation of {@link + * javax.naming.spi.ObjectFactoryBuilder}. If a location is specified, then + * unless an {@link javax.naming.spi.ObjectFactoryBuilder} is installed a + * {@link javax.naming.ConfigurationException} is thrown. * @provides javax.naming.spi.InitialContextFactory * @moduleGraph * @since 9 diff --git a/test/jdk/com/sun/jndi/rmi/registry/objects/ObjectFactoryBuilderCodebaseTest.java b/test/jdk/com/sun/jndi/rmi/registry/objects/ObjectFactoryBuilderCodebaseTest.java new file mode 100644 index 00000000000..3f7fe91ee9f --- /dev/null +++ b/test/jdk/com/sun/jndi/rmi/registry/objects/ObjectFactoryBuilderCodebaseTest.java @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2024, 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. + */ + +import com.sun.net.httpserver.HttpServer; +import com.sun.net.httpserver.SimpleFileServer; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.naming.Reference; +import javax.naming.spi.NamingManager; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.rmi.RemoteException; +import java.rmi.registry.Registry; +import java.rmi.server.RMISocketFactory; +import java.util.Hashtable; +import java.util.Objects; + +import jdk.test.lib.net.URIBuilder; + + +/* + * @test + * @bug 8338536 + * @summary Check if an object factory builder can be used to reconstruct + * object factories from a code base specified in a + * @modules java.rmi/sun.rmi.registry + * java.rmi/sun.rmi.server + * java.rmi/sun.rmi.transport + * java.rmi/sun.rmi.transport.tcp + * @library /test/lib ../../../../../../java/rmi/testlibrary + * @build TestLibrary + * @compile TestFactory.java TestObjectFactoryBuilder.java + * + * @run main/othervm ObjectFactoryBuilderCodebaseTest setObjectFactoryBuilder + * @run main/othervm ObjectFactoryBuilderCodebaseTest default + */ +public class ObjectFactoryBuilderCodebaseTest { + + public static void main(String[] args) throws Exception { + setupRmiHostNameAndRmiSocketFactory(); + boolean useCustomObjectFactoryBuilder = + "setObjectFactoryBuilder".equals(args[0]); + + if (args.length > 0 && useCustomObjectFactoryBuilder) { + NamingManager.setObjectFactoryBuilder(new TestObjectFactoryBuilder()); + } + FileServer fileServer = configureAndLaunchFileServer(); + int registryPort; + try { + Registry registry = TestLibrary.createRegistryOnEphemeralPort(); + registryPort = TestLibrary.getRegistryPort(registry); + System.out.println("Registry port: " + registryPort); + } catch (RemoteException re) { + throw new RuntimeException("Failed to create registry", re); + } + + Context context = getInitialContext(registryPort); + // Bind the Reference object + String factoryURL = fileServer.factoryLocation(); + System.err.println("Setting Reference factory location: " + factoryURL); + Reference ref = new Reference("TestObject", "com.test.TestFactory", + factoryURL); + context.bind("objectTest", ref); + + // Try to load bound reference + try { + Object object = context.lookup("objectTest"); + if (!useCustomObjectFactoryBuilder) { + throw new RuntimeException("Lookup not expected to complete"); + } + System.err.println("Loaded object: " + object); + } catch (NamingException ne) { + if (useCustomObjectFactoryBuilder) { + throw new RuntimeException("Lookup expected to complete successfully", ne); + } + } + } + + private static Context getInitialContext(int port) throws NamingException { + Hashtable env = new Hashtable<>(); + + // Prepare registry URL + String providerURL = URIBuilder.newBuilder() + .loopback() + .port(port) + .scheme("rmi") + .buildUnchecked().toString(); + + env.put(Context.INITIAL_CONTEXT_FACTORY, + "com.sun.jndi.rmi.registry.RegistryContextFactory"); + env.put(Context.PROVIDER_URL, providerURL); + return new InitialContext(env); + } + + private record FileServer(Path rootPath, InetSocketAddress address, HttpServer httpServer) { + + public static FileServer newInstance(Path rootPath, InetSocketAddress address) { + Objects.requireNonNull(address); + Objects.requireNonNull(rootPath); + var httpServer = SimpleFileServer.createFileServer(address, rootPath, + SimpleFileServer.OutputLevel.VERBOSE); + return new FileServer(rootPath, address, httpServer); + } + + String factoryLocation() { + return URIBuilder.newBuilder() + .loopback() + .port(port()) + .scheme("http") + .path("/") + .buildUnchecked() + .toString(); + } + + int port() { + return httpServer.getAddress().getPort(); + } + + void start() { + httpServer.start(); + } + } + + // Prepares and launches the file server capable of serving TestFactory.class + private static FileServer configureAndLaunchFileServer() throws IOException { + + // Location of compiled classes with compiled MyFactory + Path factoryClassPath = Path.of(System.getProperty("test.classes", ".")) + .resolve(OBJ_FACTORY_PACKAGE_PATH) + .resolve(OBJ_FACTORY_CLASS_NAME); + + // File server content root directory + Path serverRoot = Paths.get("serverRoot").toAbsolutePath(); + Path packageDirInServerRoot = serverRoot.resolve(OBJ_FACTORY_PACKAGE_PATH); + Path factoryClassFileInServerRoot = packageDirInServerRoot.resolve(OBJ_FACTORY_CLASS_NAME); + + // Remove files from previous run + Files.deleteIfExists(factoryClassFileInServerRoot); + Files.deleteIfExists(packageDirInServerRoot); + Files.deleteIfExists(packageDirInServerRoot.getParent()); + Files.deleteIfExists(serverRoot); + + // Create server root and copy compiled object factory inside + Files.createDirectories(packageDirInServerRoot); + Files.copy(factoryClassPath, factoryClassFileInServerRoot); + + // Bind file server to loopback address + InetSocketAddress serverAddress = + new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); + FileServer fileServer = FileServer.newInstance(serverRoot, serverAddress); + + // Start the file server + fileServer.start(); + System.err.println("File server content root: " + serverRoot); + System.err.printf("File server running on %s:%d%n", + serverAddress.getAddress(), fileServer.port()); + return fileServer; + } + + + // Configure RMI to launch registry on a loopback address + private static void setupRmiHostNameAndRmiSocketFactory() throws IOException { + String rmiServerHostAddressString = InetAddress.getLoopbackAddress().getHostAddress(); + System.out.println("Setting 'java.rmi.server.hostname' to: " + rmiServerHostAddressString); + System.setProperty("java.rmi.server.hostname", rmiServerHostAddressString); + RMISocketFactory.setSocketFactory(new TestRmiSocketFactory()); + } + + private static class TestRmiSocketFactory extends RMISocketFactory { + public ServerSocket createServerSocket(int port) throws IOException { + var loopbackAddress = InetAddress.getLoopbackAddress(); + System.out.printf("Creating RMI server socket on %s:%d%n", loopbackAddress, port); + ServerSocket rmiServerSocket = new ServerSocket(); + rmiServerSocket.setOption(java.net.StandardSocketOptions.SO_REUSEADDR, false); + SocketAddress serverAddress = new InetSocketAddress(loopbackAddress, port); + rmiServerSocket.bind(serverAddress, BACKLOG_OF_5); + return rmiServerSocket; + } + + public Socket createSocket(String host, int port) throws IOException { + System.out.printf("Creating RMI client socket connected to %s:%d%n", host, port); + // just call the default client socket factory + return RMISocketFactory.getDefaultSocketFactory() + .createSocket(host, port); + } + } + + // File server backlog value + private static final int BACKLOG_OF_5 = 5; + // Test objects factory class filename + private static final String OBJ_FACTORY_CLASS_NAME = "TestFactory.class"; + // Package directory of the test's objects factory class + private static final Path OBJ_FACTORY_PACKAGE_PATH = Paths.get("com").resolve("test"); +} diff --git a/test/jdk/com/sun/jndi/rmi/registry/objects/TestObjectFactoryBuilder.java b/test/jdk/com/sun/jndi/rmi/registry/objects/TestObjectFactoryBuilder.java new file mode 100644 index 00000000000..60641e70d22 --- /dev/null +++ b/test/jdk/com/sun/jndi/rmi/registry/objects/TestObjectFactoryBuilder.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024, 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. + */ + +import javax.naming.ConfigurationException; +import javax.naming.NamingException; +import javax.naming.Reference; +import javax.naming.spi.ObjectFactory; +import javax.naming.spi.ObjectFactoryBuilder; +import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Hashtable; + +/** + * Test library class that implements {@code javax.naming.spi.ObjectFactoryBuilder} interface. + * Its implementation allows object factory class loading from any remote location. + */ +public class TestObjectFactoryBuilder implements ObjectFactoryBuilder { + + @Override + public ObjectFactory createObjectFactory(Object obj, Hashtable environment) throws NamingException { + System.err.println("TestObjectFactoryBuilder: Creating new object factory"); + System.err.println("Builder for object: " + obj); + System.err.println("And for environment: " + environment); + // Only objects of the Reference type are supported, others are rejected + if (obj instanceof Reference ref) { + String objectFactoryLocation = ref.getFactoryClassLocation(); + try { + URL factoryURL = new URL(objectFactoryLocation); + var cl = new URLClassLoader(new URL[]{factoryURL}); + Class factoryClass = cl.loadClass(ref.getFactoryClassName()); + System.err.println("Loaded object factory: " + factoryClass); + if (ObjectFactory.class.isAssignableFrom(factoryClass)) { + return (ObjectFactory) factoryClass + .getDeclaredConstructor().newInstance(); + } else { + throw new ConfigurationException("Test configuration error -" + + " loaded object factory of wrong type"); + } + } catch (MalformedURLException e) { + throw new ConfigurationException("Error constructing test object factory"); + } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | + IllegalAccessException | InvocationTargetException e) { + throw new ConfigurationException("Test configuration error: " + + "factory class cannot be loaded from the provided " + + "object factory location"); + } + } else { + throw new ConfigurationException("Test factory builder " + + "supports only Reference types"); + } + } +}