8338536: Permanently disable remote code downloading in JNDI

Reviewed-by: dfuchs
This commit is contained in:
Aleksei Efimov 2024-11-21 20:55:02 +00:00
parent 7709d435d0
commit cee74f9e67
11 changed files with 364 additions and 252 deletions

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -236,7 +236,7 @@ final class Obj {
if (!VersionHelper.isSerialDataAllowed()) { if (!VersionHelper.isSerialDataAllowed()) {
throw new NamingException("Object deserialization is not allowed"); throw new NamingException("Object deserialization is not allowed");
} }
ClassLoader cl = helper.getURLClassLoader(codebases); ClassLoader cl = Thread.currentThread().getContextClassLoader();
return deserializeObject((byte[])attr.get(), cl); return deserializeObject((byte[])attr.get(), cl);
} else if ((attr = attrs.get(JAVA_ATTRIBUTES[REMOTE_LOC])) != null) { } else if ((attr = attrs.get(JAVA_ATTRIBUTES[REMOTE_LOC])) != null) {
// javaRemoteLocation attribute (RMI stub will be created) // javaRemoteLocation attribute (RMI stub will be created)
@ -246,7 +246,7 @@ final class Obj {
// For backward compatibility only // For backward compatibility only
return decodeRmiObject( return decodeRmiObject(
(String)attrs.get(JAVA_ATTRIBUTES[CLASSNAME]).get(), (String)attrs.get(JAVA_ATTRIBUTES[CLASSNAME]).get(),
(String)attr.get(), codebases); (String)attr.get());
} }
attr = attrs.get(JAVA_ATTRIBUTES[OBJECT_CLASS]); attr = attrs.get(JAVA_ATTRIBUTES[OBJECT_CLASS]);
@ -368,7 +368,7 @@ final class Obj {
* @deprecated For backward compatibility only * @deprecated For backward compatibility only
*/ */
private static Object decodeRmiObject(String className, private static Object decodeRmiObject(String className,
String rmiName, String[] codebases) throws NamingException { String rmiName) throws NamingException {
return new Reference(className, new StringRefAddr("URL", rmiName)); return new Reference(className, new StringRefAddr("URL", rmiName));
} }
@ -410,7 +410,7 @@ final class Obj {
int start, sep, posn; int start, sep, posn;
Base64.Decoder decoder = null; Base64.Decoder decoder = null;
ClassLoader cl = helper.getURLClassLoader(codebases); ClassLoader cl = Thread.currentThread().getContextClassLoader();
/* /*
* Temporary array for decoded RefAddr addresses - used to ensure * Temporary array for decoded RefAddr addresses - used to ensure

View File

@ -25,22 +25,10 @@
package com.sun.jndi.ldap; 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 { public final class VersionHelper {
private static final VersionHelper helper = new 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 * Determines whether objects may be deserialized or reconstructed from a content of
* 'javaSerializedData', 'javaRemoteLocation' or 'javaReferenceAddress' LDAP attributes. * 'javaSerializedData', 'javaRemoteLocation' or 'javaReferenceAddress' LDAP attributes.
@ -48,29 +36,13 @@ public final class VersionHelper {
private static final boolean trustSerialData; private static final boolean trustSerialData;
static { 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 // System property to control whether classes are allowed to be loaded from
// 'javaSerializedData', 'javaRemoteLocation' or 'javaReferenceAddress' attributes. // 'javaSerializedData', 'javaRemoteLocation' or 'javaReferenceAddress' attributes.
String trustSerialDataSp = getPrivilegedProperty( String trustSerialDataSp = System.getProperty(
"com.sun.jndi.ldap.object.trustSerialData", "false"); "com.sun.jndi.ldap.object.trustSerialData", "false");
trustSerialData = "true".equalsIgnoreCase(trustSerialDataSp); trustSerialData = "true".equalsIgnoreCase(trustSerialDataSp);
} }
@SuppressWarnings("removal")
private static String getPrivilegedProperty(String propertyName, String defaultVal) {
PrivilegedAction<String> action = () -> System.getProperty(propertyName, defaultVal);
if (System.getSecurityManager() == null) {
return action.run();
} else {
return AccessController.doPrivileged(action);
}
}
private VersionHelper() { private VersionHelper() {
} }
@ -89,41 +61,12 @@ public final class VersionHelper {
return trustSerialData; 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 { Class<?> loadClass(String className) throws ClassNotFoundException {
return Class.forName(className, true, getContextClassLoader()); return Class.forName(className, true,
Thread.currentThread().getContextClassLoader());
} }
Thread createThread(Runnable r) { Thread createThread(Runnable r) {
return new Thread(r); return new Thread(r);
} }
@SuppressWarnings("removal")
private ClassLoader getContextClassLoader() {
PrivilegedAction<ClassLoader> 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;
}
} }

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -181,9 +181,8 @@ public class NamingManagerHelper {
static ObjectFactory getObjectFactoryFromReference( static ObjectFactory getObjectFactoryFromReference(
Reference ref, String factoryName, Predicate<Class<?>> filter) Reference ref, String factoryName, Predicate<Class<?>> filter)
throws IllegalAccessException, throws IllegalAccessException,
InstantiationException, InstantiationException {
MalformedURLException { Class<?> clas;
Class<?> clas = null;
// Try to use current class loader // Try to use current class loader
try { try {
@ -193,27 +192,11 @@ public class NamingManagerHelper {
return null; return null;
} }
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
// ignore and continue return null;
// e.printStackTrace();
} }
// All other exceptions are passed up. assert clas != null;
// 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) {
}
}
@SuppressWarnings("deprecation") // Class.newInstance @SuppressWarnings("deprecation") // Class.newInstance
ObjectFactory result = (clas != null) ? (ObjectFactory) clas.newInstance() : null; ObjectFactory result = (ObjectFactory) clas.newInstance();
return result; return result;
} }
@ -401,12 +384,6 @@ public class NamingManagerHelper {
ObjectFactoryBuilder builder) throws NamingException { ObjectFactoryBuilder builder) throws NamingException {
if (object_factory_builder != null) if (object_factory_builder != null)
throw new IllegalStateException("ObjectFactoryBuilder already set"); throw new IllegalStateException("ObjectFactoryBuilder already set");
@SuppressWarnings("removal")
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkSetFactory();
}
object_factory_builder = builder; object_factory_builder = builder;
} }

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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 javax.naming.NamingEnumeration;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.*; import java.util.*;
/** /**
@ -53,21 +47,6 @@ import java.util.*;
public final class VersionHelper { public final class VersionHelper {
private static final VersionHelper helper = new 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<String> 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[]{ static final String[] PROPS = new String[]{
javax.naming.Context.INITIAL_CONTEXT_FACTORY, javax.naming.Context.INITIAL_CONTEXT_FACTORY,
javax.naming.Context.OBJECT_FACTORIES, javax.naming.Context.OBJECT_FACTORIES,
@ -101,22 +80,6 @@ public final class VersionHelper {
return loadClass(className, false, getContextClassLoader()); 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. * Package private.
* <p> * <p>
@ -136,37 +99,19 @@ public final class VersionHelper {
/* /*
* Returns a JNDI property from the system properties. Returns * Returns a JNDI property from the system properties. Returns
* null if the property is not set, or if there is no permission * null if the property is not set.
* to read it.
*/ */
@SuppressWarnings("removal")
String getJndiProperty(int i) { String getJndiProperty(int i) {
PrivilegedAction<String> act = () -> { return System.getProperty(PROPS[i]);
try {
return System.getProperty(PROPS[i]);
} catch (SecurityException e) {
return null;
}
};
return AccessController.doPrivileged(act);
} }
/* /*
* Reads each property in PROPS from the system properties, and * Reads each property in PROPS from the system properties, and
* returns their values -- in order -- in an array. For each * returns their values -- in order -- in an array. For each
* unset property, the corresponding array element is set to null. * unset property, the corresponding array element is set to null.
* Returns null if there is no permission to call System.getProperties().
*/ */
String[] getJndiProperties() { String[] getJndiProperties() {
PrivilegedAction<Properties> act = () -> { Properties sysProps = System.getProperties();
try {
return System.getProperties();
} catch (SecurityException e) {
return null;
}
};
@SuppressWarnings("removal")
Properties sysProps = AccessController.doPrivileged(act);
if (sysProps == null) { if (sysProps == null) {
return null; return null;
} }
@ -199,16 +144,12 @@ public final class VersionHelper {
* Returns the resource of a given name associated with a particular * Returns the resource of a given name associated with a particular
* class (never null), or null if none can be found. * class (never null), or null if none can be found.
*/ */
@SuppressWarnings("removal")
InputStream getResourceAsStream(Class<?> c, String name) { InputStream getResourceAsStream(Class<?> c, String name) {
PrivilegedAction<InputStream> act = () -> { try {
try { return c.getModule().getResourceAsStream(resolveName(c, name));
return c.getModule().getResourceAsStream(resolveName(c, name)); } catch (IOException x) {
} catch (IOException x) { return null;
return null; }
}
};
return AccessController.doPrivileged(act);
} }
/* /*
@ -217,20 +158,16 @@ public final class VersionHelper {
* *
* @param filename The file name, sans directory. * @param filename The file name, sans directory.
*/ */
@SuppressWarnings("removal")
InputStream getJavaHomeConfStream(String filename) { InputStream getJavaHomeConfStream(String filename) {
PrivilegedAction<InputStream> act = () -> { try {
try { String javahome = System.getProperty("java.home");
String javahome = System.getProperty("java.home"); if (javahome == null) {
if (javahome == null) {
return null;
}
return Files.newInputStream(Path.of(javahome, "conf", filename));
} catch (Exception e) {
return null; return null;
} }
}; return Files.newInputStream(Path.of(javahome, "conf", filename));
return AccessController.doPrivileged(act); } catch (Exception e) {
return null;
}
} }
/* /*
@ -239,19 +176,12 @@ public final class VersionHelper {
* loader. Null represents the bootstrap class loader in some * loader. Null represents the bootstrap class loader in some
* Java implementations. * Java implementations.
*/ */
@SuppressWarnings("removal")
NamingEnumeration<InputStream> getResources(ClassLoader cl, NamingEnumeration<InputStream> getResources(ClassLoader cl,
String name) throws IOException { String name) throws IOException {
Enumeration<URL> urls; Enumeration<URL> urls;
PrivilegedExceptionAction<Enumeration<URL>> act = () -> urls = (cl == null)
(cl == null) ? ClassLoader.getSystemResources(name)
? ClassLoader.getSystemResources(name) : cl.getResources(name);
: cl.getResources(name);
try {
urls = AccessController.doPrivileged(act);
} catch (PrivilegedActionException e) {
throw (IOException) e.getException();
}
return new InputStreamEnumeration(urls); return new InputStreamEnumeration(urls);
} }
@ -265,39 +195,18 @@ public final class VersionHelper {
* Please don't expose this method as public. * Please don't expose this method as public.
* @throws SecurityException if the class loader is not accessible * @throws SecurityException if the class loader is not accessible
*/ */
@SuppressWarnings("removal")
ClassLoader getContextClassLoader() { ClassLoader getContextClassLoader() {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
PrivilegedAction<ClassLoader> act = () -> { if (loader == null) {
ClassLoader loader = Thread.currentThread().getContextClassLoader(); // Don't use bootstrap class loader directly!
if (loader == null) { loader = ClassLoader.getSystemClassLoader();
// 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<URL> list = new ArrayList<>();
while (parser.hasMoreTokens()) {
@SuppressWarnings("deprecation")
var u = new URL(parser.nextToken());
list.add(u);
} }
return list.toArray(new URL[0]); return loader;
} }
/** /**
* Given an enumeration of URLs, an instance of this class represents * Given an enumeration of URLs, an instance of this class represents
* an enumeration of their InputStreams. Each operation on the URL * an enumeration of their InputStreams.
* enumeration is performed within a doPrivileged block.
* This is used to enumerate the resources under a foreign codebase.
* This class is not MT-safe.
*/ */
private class InputStreamEnumeration implements private class InputStreamEnumeration implements
NamingEnumeration<InputStream> { NamingEnumeration<InputStream> {
@ -314,19 +223,15 @@ public final class VersionHelper {
* Returns the next InputStream, or null if there are no more. * Returns the next InputStream, or null if there are no more.
* An InputStream that cannot be opened is skipped. * An InputStream that cannot be opened is skipped.
*/ */
@SuppressWarnings("removal")
private InputStream getNextElement() { private InputStream getNextElement() {
PrivilegedAction<InputStream> act = () -> { while (urls.hasMoreElements()) {
while (urls.hasMoreElements()) { try {
try { return urls.nextElement().openStream();
return urls.nextElement().openStream(); } catch (IOException e) {
} catch (IOException e) { // skip this URL
// skip this URL
}
} }
return null; }
}; return null;
return AccessController.doPrivileged(act);
} }
public boolean hasMore() { public boolean hasMore() {

View File

@ -123,9 +123,12 @@ public class NamingManager {
* or {@code Referenceable} containing a factory class name, * or {@code Referenceable} containing a factory class name,
* use the named factory to create the object. * use the named factory to create the object.
* Return {@code refInfo} if the factory cannot be created. * Return {@code refInfo} if the factory cannot be created.
* Under JDK 1.1, if the factory class must be loaded from a location * Downloading a factory class from a location specified in the reference
* specified in the reference, a {@code SecurityManager} must have * can be supported by a custom implementation of {@link ObjectFactoryBuilder}.
* been installed or the factory creation will fail. * 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, * If an exception is encountered while creating the factory,
* it is passed up to the caller. * it is passed up to the caller.
* <li>If {@code refInfo} is a {@code Reference} or * <li>If {@code refInfo} is a {@code Reference} or

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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.server.*;
import java.rmi.registry.Registry; import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry; import java.rmi.registry.LocateRegistry;
import java.security.AccessController;
import java.security.PrivilegedAction;
import javax.naming.*; import javax.naming.*;
import javax.naming.spi.NamingManager; import javax.naming.spi.NamingManager;
@ -57,19 +55,6 @@ public class RegistryContext implements Context, Referenceable {
private int port; private int port;
private static final NameParser nameParser = new AtomicNameParser(); private static final NameParser nameParser = new AtomicNameParser();
private static final String SOCKET_FACTORY = "com.sun.jndi.rmi.factory.socket"; 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<String> 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 Reference reference = null; // ref used to create this context, if any
@ -481,12 +466,6 @@ public class RegistryContext implements Context, Referenceable {
? ((RemoteReference)r).getReference() ? ((RemoteReference)r).getReference()
: (Object)r; : (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 // Use reference if possible
Reference ref = null; Reference ref = null;
if (obj instanceof Reference) { if (obj instanceof Reference) {
@ -495,11 +474,14 @@ public class RegistryContext implements Context, Referenceable {
ref = ((Referenceable)(obj)).getReference(); 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( throw new ConfigurationException(
"The object factory is untrusted. Set the system property" + "Remote object factories are not supported");
" 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.");
} }
return NamingManagerHelper.getObjectInstance(obj, name, this, return NamingManagerHelper.getObjectInstance(obj, name, this,
environment, ObjectFactoriesFilter::checkRmiFilter); environment, ObjectFactoriesFilter::checkRmiFilter);

View File

@ -58,6 +58,11 @@
* implementation. * implementation.
* </li> * </li>
* </ul> * </ul>
* <p> 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 * @provides javax.naming.spi.InitialContextFactory
* @moduleGraph * @moduleGraph
* @since 9 * @since 9

View File

@ -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<String, String> 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");
}

View File

@ -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");
}
}
}