diff --git a/make/GenerateClasses.gmk b/make/GenerateClasses.gmk index 4738d830afdc280cead615b3dee06811f3181f7d..7bbb3a2a12cfe1d45cad8e671a475708c4b4b2ec 100644 --- a/make/GenerateClasses.gmk +++ b/make/GenerateClasses.gmk @@ -78,13 +78,6 @@ $(eval $(call SetupRMICompilation,RMI_12, \ RUN_V12 := true)) GENCLASSES += $(RMI_12) -$(eval $(call SetupRMICompilation,RMI_11, \ - CLASSES := sun.rmi.registry.RegistryImpl, \ - CLASSES_DIR := $(CLASSES_DIR), \ - STUB_CLASSES_DIR := $(STUB_CLASSES_DIR), \ - RUN_V11 := true)) -GENCLASSES += $(RMI_11) - # For RMI/IIOP call rmic a second time with -standardPackage option # so that *_tie classes are generated in package without the prefix # org.omg.stub (6375696) @@ -111,7 +104,7 @@ GENCLASSES += $(filter %.java, $(RMI_SRC)) ########################################################################################## -$(RMI_12) $(RMI_11) $(RMI_IIOP) $(RMI_SRC): $(BUILD_BOOTSTRAP_RMIC) +$(RMI_12) $(RMI_IIOP) $(RMI_SRC): $(BUILD_BOOTSTRAP_RMIC) $(RMIC_GENSRC_DIR)/_the.classes.removed: $(GENCLASSES) $(FIND) $(RMIC_GENSRC_DIR) -name "*.class" $(FIND_DELETE) diff --git a/src/share/classes/sun/management/jmxremote/SingleEntryRegistry.java b/src/share/classes/sun/management/jmxremote/SingleEntryRegistry.java index 998b00ab14e654c75ade9f3e3340284b4dd9eb91..b3b5281358d9a4a77d1f4e3bb061252b77799fd4 100644 --- a/src/share/classes/sun/management/jmxremote/SingleEntryRegistry.java +++ b/src/share/classes/sun/management/jmxremote/SingleEntryRegistry.java @@ -32,6 +32,7 @@ package sun.management.jmxremote; +import sun.misc.ObjectInputFilter; import java.rmi.AccessException; import java.rmi.NotBoundException; import java.rmi.Remote; @@ -56,7 +57,7 @@ public class SingleEntryRegistry extends RegistryImpl { String name, Remote object) throws RemoteException { - super(port, csf, ssf); + super(port, csf, ssf, SingleEntryRegistry::singleRegistryFilter); this.name = name; this.object = object; } @@ -84,6 +85,23 @@ public class SingleEntryRegistry extends RegistryImpl { throw new AccessException("Cannot modify this registry"); } + /** + * ObjectInputFilter to check parameters to SingleEntryRegistry. + * Since it is a read-only Registry, no classes are accepted. + * String arguments are accepted without passing them to the serialFilter. + * + * @param info a reference to the serialization filter information + * @return Status.REJECTED if parameters are out of range + */ + private static ObjectInputFilter.Status singleRegistryFilter(ObjectInputFilter.FilterInfo info) { + return (info.serialClass() != null || + info.depth() > 2 || + info.references() > 4 || + info.arrayLength() >= 0) + ? ObjectInputFilter.Status.REJECTED + : ObjectInputFilter.Status.ALLOWED; + } + private final String name; private final Remote object; diff --git a/src/share/classes/sun/rmi/registry/RegistryImpl.java b/src/share/classes/sun/rmi/registry/RegistryImpl.java index e23379db3b96961e2970f62d86882ace22e0dc35..2f36e6d808eb1b3783cc30cf6b58fa13fe9c3fd3 100644 --- a/src/share/classes/sun/rmi/registry/RegistryImpl.java +++ b/src/share/classes/sun/rmi/registry/RegistryImpl.java @@ -69,6 +69,10 @@ import sun.rmi.transport.LiveRef; * registry. * * The LocateRegistry class is used to obtain registry for different hosts. + *

+ * The default RegistryImpl exported restricts access to clients on the local host + * for the methods {@link #bind}, {@link #rebind}, {@link #unbind} by checking + * the client host in the skeleton. * * @see java.rmi.registry.LocateRegistry */ @@ -136,6 +140,20 @@ public class RegistryImpl extends java.rmi.server.RemoteServer RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException + { + this(port, csf, ssf, RegistryImpl::registryFilter); + } + + + /** + * Construct a new RegistryImpl on the specified port with the + * given custom socket factory pair and ObjectInputFilter. + */ + public RegistryImpl(int port, + RMIClientSocketFactory csf, + RMIServerSocketFactory ssf, + ObjectInputFilter serialFilter) + throws RemoteException { if (port == Registry.REGISTRY_PORT && System.getSecurityManager() != null) { // grant permission for default port only. @@ -143,7 +161,7 @@ public class RegistryImpl extends java.rmi.server.RemoteServer AccessController.doPrivileged(new PrivilegedExceptionAction() { public Void run() throws RemoteException { LiveRef lref = new LiveRef(id, port, csf, ssf); - setup(new UnicastServerRef2(lref, RegistryImpl::registryFilter)); + setup(new UnicastServerRef2(lref, serialFilter)); return null; } }, null, new SocketPermission("localhost:"+port, "listen,accept")); @@ -219,7 +237,8 @@ public class RegistryImpl extends java.rmi.server.RemoteServer public void bind(String name, Remote obj) throws RemoteException, AlreadyBoundException, AccessException { - checkAccess("Registry.bind"); + // The access check preventing remote access is done in the skeleton + // and is not applicable to local access. synchronized (bindings) { Remote curr = bindings.get(name); if (curr != null) @@ -236,7 +255,8 @@ public class RegistryImpl extends java.rmi.server.RemoteServer public void unbind(String name) throws RemoteException, NotBoundException, AccessException { - checkAccess("Registry.unbind"); + // The access check preventing remote access is done in the skeleton + // and is not applicable to local access. synchronized (bindings) { Remote obj = bindings.get(name); if (obj == null) @@ -252,7 +272,8 @@ public class RegistryImpl extends java.rmi.server.RemoteServer public void rebind(String name, Remote obj) throws RemoteException, AccessException { - checkAccess("Registry.rebind"); + // The access check preventing remote access is done in the skeleton + // and is not applicable to local access. bindings.put(name, obj); } @@ -279,7 +300,6 @@ public class RegistryImpl extends java.rmi.server.RemoteServer * The client must be on same the same host as this server. */ public static void checkAccess(String op) throws AccessException { - try { /* * Get client host that this registry operation was made from. @@ -305,7 +325,7 @@ public class RegistryImpl extends java.rmi.server.RemoteServer if (clientHost.isAnyLocalAddress()) { throw new AccessException( - "Registry." + op + " disallowed; origin unknown"); + op + " disallowed; origin unknown"); } try { @@ -328,7 +348,7 @@ public class RegistryImpl extends java.rmi.server.RemoteServer // must have been an IOException throw new AccessException( - "Registry." + op + " disallowed; origin " + + op + " disallowed; origin " + clientHost + " is non-local host"); } } @@ -337,8 +357,7 @@ public class RegistryImpl extends java.rmi.server.RemoteServer * Local call from this VM: allow access. */ } catch (java.net.UnknownHostException ex) { - throw new AccessException("Registry." + op + - " disallowed; origin is unknown host"); + throw new AccessException(op + " disallowed; origin is unknown host"); } } diff --git a/src/share/classes/sun/rmi/registry/RegistryImpl_Skel.java b/src/share/classes/sun/rmi/registry/RegistryImpl_Skel.java new file mode 100644 index 0000000000000000000000000000000000000000..842d47719591b7e74c5894b513245a40d8b7608c --- /dev/null +++ b/src/share/classes/sun/rmi/registry/RegistryImpl_Skel.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2017, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + + +package sun.rmi.registry; + +import java.io.IOException; +import java.io.InputStream; +import java.rmi.AccessException; +import java.rmi.server.RemoteCall; + +import sun.rmi.transport.Connection; +import sun.rmi.transport.StreamRemoteCall; +import sun.rmi.transport.tcp.TCPConnection; + +/** + * Skeleton to dispatch RegistryImpl methods. + * Originally generated by RMIC but frozen to match the stubs. + */ +@SuppressWarnings({"deprecation", "serial"}) +public final class RegistryImpl_Skel + implements java.rmi.server.Skeleton { + private static final java.rmi.server.Operation[] operations = { + new java.rmi.server.Operation("void bind(java.lang.String, java.rmi.Remote)"), + new java.rmi.server.Operation("java.lang.String list()[]"), + new java.rmi.server.Operation("java.rmi.Remote lookup(java.lang.String)"), + new java.rmi.server.Operation("void rebind(java.lang.String, java.rmi.Remote)"), + new java.rmi.server.Operation("void unbind(java.lang.String)") + }; + + private static final long interfaceHash = 4905912898345647071L; + + public java.rmi.server.Operation[] getOperations() { + return operations.clone(); + } + + public void dispatch(java.rmi.Remote obj, java.rmi.server.RemoteCall call, int opnum, long hash) + throws java.lang.Exception { + if (hash != interfaceHash) + throw new java.rmi.server.SkeletonMismatchException("interface hash mismatch"); + + sun.rmi.registry.RegistryImpl server = (sun.rmi.registry.RegistryImpl) obj; + switch (opnum) { + case 0: // bind(String, Remote) + { + // Check access before reading the arguments + RegistryImpl.checkAccess("Registry.bind"); + + java.lang.String $param_String_1; + java.rmi.Remote $param_Remote_2; + try { + java.io.ObjectInput in = call.getInputStream(); + $param_String_1 = (java.lang.String) in.readObject(); + $param_Remote_2 = (java.rmi.Remote) in.readObject(); + } catch (java.io.IOException | java.lang.ClassNotFoundException e) { + throw new java.rmi.UnmarshalException("error unmarshalling arguments", e); + } finally { + call.releaseInputStream(); + } + server.bind($param_String_1, $param_Remote_2); + try { + call.getResultStream(true); + } catch (java.io.IOException e) { + throw new java.rmi.MarshalException("error marshalling return", e); + } + break; + } + + case 1: // list() + { + call.releaseInputStream(); + java.lang.String[] $result = server.list(); + try { + java.io.ObjectOutput out = call.getResultStream(true); + out.writeObject($result); + } catch (java.io.IOException e) { + throw new java.rmi.MarshalException("error marshalling return", e); + } + break; + } + + case 2: // lookup(String) + { + java.lang.String $param_String_1; + try { + java.io.ObjectInput in = call.getInputStream(); + $param_String_1 = (java.lang.String) in.readObject(); + } catch (java.io.IOException | java.lang.ClassNotFoundException e) { + throw new java.rmi.UnmarshalException("error unmarshalling arguments", e); + } finally { + call.releaseInputStream(); + } + java.rmi.Remote $result = server.lookup($param_String_1); + try { + java.io.ObjectOutput out = call.getResultStream(true); + out.writeObject($result); + } catch (java.io.IOException e) { + throw new java.rmi.MarshalException("error marshalling return", e); + } + break; + } + + case 3: // rebind(String, Remote) + { + // Check access before reading the arguments + RegistryImpl.checkAccess("Registry.rebind"); + + java.lang.String $param_String_1; + java.rmi.Remote $param_Remote_2; + try { + java.io.ObjectInput in = call.getInputStream(); + $param_String_1 = (java.lang.String) in.readObject(); + $param_Remote_2 = (java.rmi.Remote) in.readObject(); + } catch (java.io.IOException | java.lang.ClassNotFoundException e) { + throw new java.rmi.UnmarshalException("error unmarshalling arguments", e); + } finally { + call.releaseInputStream(); + } + server.rebind($param_String_1, $param_Remote_2); + try { + call.getResultStream(true); + } catch (java.io.IOException e) { + throw new java.rmi.MarshalException("error marshalling return", e); + } + break; + } + + case 4: // unbind(String) + { + // Check access before reading the arguments + RegistryImpl.checkAccess("Registry.unbind"); + + java.lang.String $param_String_1; + try { + java.io.ObjectInput in = call.getInputStream(); + $param_String_1 = (java.lang.String) in.readObject(); + } catch (java.io.IOException | java.lang.ClassNotFoundException e) { + throw new java.rmi.UnmarshalException("error unmarshalling arguments", e); + } finally { + call.releaseInputStream(); + } + server.unbind($param_String_1); + try { + call.getResultStream(true); + } catch (java.io.IOException e) { + throw new java.rmi.MarshalException("error marshalling return", e); + } + break; + } + + default: + throw new java.rmi.UnmarshalException("invalid method number"); + } + } +} diff --git a/src/share/classes/sun/rmi/registry/RegistryImpl_Stub.java b/src/share/classes/sun/rmi/registry/RegistryImpl_Stub.java new file mode 100644 index 0000000000000000000000000000000000000000..f8574869147855dc78e2a70bdaa3e7603aba9d94 --- /dev/null +++ b/src/share/classes/sun/rmi/registry/RegistryImpl_Stub.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2017, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package sun.rmi.registry; +/** + * Stubs to invoke RegistryImpl remote methods. + * Originally generated from RMIC but frozen to match RegistryImpl_Skel. + */ +@SuppressWarnings({"deprecation", "serial"}) +public final class RegistryImpl_Stub + extends java.rmi.server.RemoteStub + implements java.rmi.registry.Registry, java.rmi.Remote { + private static final java.rmi.server.Operation[] operations = { + new java.rmi.server.Operation("void bind(java.lang.String, java.rmi.Remote)"), + new java.rmi.server.Operation("java.lang.String list()[]"), + new java.rmi.server.Operation("java.rmi.Remote lookup(java.lang.String)"), + new java.rmi.server.Operation("void rebind(java.lang.String, java.rmi.Remote)"), + new java.rmi.server.Operation("void unbind(java.lang.String)") + }; + + private static final long interfaceHash = 4905912898345647071L; + + // constructors + public RegistryImpl_Stub() { + super(); + } + + public RegistryImpl_Stub(java.rmi.server.RemoteRef ref) { + super(ref); + } + + // methods from remote interfaces + + // implementation of bind(String, Remote) + public void bind(java.lang.String $param_String_1, java.rmi.Remote $param_Remote_2) + throws java.rmi.AccessException, java.rmi.AlreadyBoundException, java.rmi.RemoteException { + try { + java.rmi.server.RemoteCall call = ref.newCall((java.rmi.server.RemoteObject) this, operations, 0, interfaceHash); + try { + java.io.ObjectOutput out = call.getOutputStream(); + out.writeObject($param_String_1); + out.writeObject($param_Remote_2); + } catch (java.io.IOException e) { + throw new java.rmi.MarshalException("error marshalling arguments", e); + } + ref.invoke(call); + ref.done(call); + } catch (java.lang.RuntimeException e) { + throw e; + } catch (java.rmi.RemoteException e) { + throw e; + } catch (java.rmi.AlreadyBoundException e) { + throw e; + } catch (java.lang.Exception e) { + throw new java.rmi.UnexpectedException("undeclared checked exception", e); + } + } + + // implementation of list() + public java.lang.String[] list() + throws java.rmi.AccessException, java.rmi.RemoteException { + try { + java.rmi.server.RemoteCall call = ref.newCall((java.rmi.server.RemoteObject) this, operations, 1, interfaceHash); + ref.invoke(call); + java.lang.String[] $result; + try { + java.io.ObjectInput in = call.getInputStream(); + $result = (java.lang.String[]) in.readObject(); + } catch (java.io.IOException e) { + throw new java.rmi.UnmarshalException("error unmarshalling return", e); + } catch (java.lang.ClassNotFoundException e) { + throw new java.rmi.UnmarshalException("error unmarshalling return", e); + } finally { + ref.done(call); + } + return $result; + } catch (java.lang.RuntimeException e) { + throw e; + } catch (java.rmi.RemoteException e) { + throw e; + } catch (java.lang.Exception e) { + throw new java.rmi.UnexpectedException("undeclared checked exception", e); + } + } + + // implementation of lookup(String) + public java.rmi.Remote lookup(java.lang.String $param_String_1) + throws java.rmi.AccessException, java.rmi.NotBoundException, java.rmi.RemoteException { + try { + java.rmi.server.RemoteCall call = ref.newCall((java.rmi.server.RemoteObject) this, operations, 2, interfaceHash); + try { + java.io.ObjectOutput out = call.getOutputStream(); + out.writeObject($param_String_1); + } catch (java.io.IOException e) { + throw new java.rmi.MarshalException("error marshalling arguments", e); + } + ref.invoke(call); + java.rmi.Remote $result; + try { + java.io.ObjectInput in = call.getInputStream(); + $result = (java.rmi.Remote) in.readObject(); + } catch (java.io.IOException e) { + throw new java.rmi.UnmarshalException("error unmarshalling return", e); + } catch (java.lang.ClassNotFoundException e) { + throw new java.rmi.UnmarshalException("error unmarshalling return", e); + } finally { + ref.done(call); + } + return $result; + } catch (java.lang.RuntimeException e) { + throw e; + } catch (java.rmi.RemoteException e) { + throw e; + } catch (java.rmi.NotBoundException e) { + throw e; + } catch (java.lang.Exception e) { + throw new java.rmi.UnexpectedException("undeclared checked exception", e); + } + } + + // implementation of rebind(String, Remote) + public void rebind(java.lang.String $param_String_1, java.rmi.Remote $param_Remote_2) + throws java.rmi.AccessException, java.rmi.RemoteException { + try { + java.rmi.server.RemoteCall call = ref.newCall((java.rmi.server.RemoteObject) this, operations, 3, interfaceHash); + try { + java.io.ObjectOutput out = call.getOutputStream(); + out.writeObject($param_String_1); + out.writeObject($param_Remote_2); + } catch (java.io.IOException e) { + throw new java.rmi.MarshalException("error marshalling arguments", e); + } + ref.invoke(call); + ref.done(call); + } catch (java.lang.RuntimeException e) { + throw e; + } catch (java.rmi.RemoteException e) { + throw e; + } catch (java.lang.Exception e) { + throw new java.rmi.UnexpectedException("undeclared checked exception", e); + } + } + + // implementation of unbind(String) + public void unbind(java.lang.String $param_String_1) + throws java.rmi.AccessException, java.rmi.NotBoundException, java.rmi.RemoteException { + try { + java.rmi.server.RemoteCall call = ref.newCall((java.rmi.server.RemoteObject) this, operations, 4, interfaceHash); + try { + java.io.ObjectOutput out = call.getOutputStream(); + out.writeObject($param_String_1); + } catch (java.io.IOException e) { + throw new java.rmi.MarshalException("error marshalling arguments", e); + } + ref.invoke(call); + ref.done(call); + } catch (java.lang.RuntimeException e) { + throw e; + } catch (java.rmi.RemoteException e) { + throw e; + } catch (java.rmi.NotBoundException e) { + throw e; + } catch (java.lang.Exception e) { + throw new java.rmi.UnexpectedException("undeclared checked exception", e); + } + } +} diff --git a/src/share/classes/sun/rmi/server/Activation.java b/src/share/classes/sun/rmi/server/Activation.java index 928b1d549ec7b8f9b50585b6041f1e979d1e4dbe..cb36f2da7fc85ead55074b8826433e73560bb6ee 100644 --- a/src/share/classes/sun/rmi/server/Activation.java +++ b/src/share/classes/sun/rmi/server/Activation.java @@ -30,6 +30,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.OutputStream; import java.io.PrintStream; @@ -105,7 +106,6 @@ import sun.rmi.log.LogHandler; import sun.rmi.log.ReliableLog; import sun.rmi.registry.RegistryImpl; import sun.rmi.runtime.NewThreadAction; -import sun.rmi.server.UnicastServerRef; import sun.rmi.transport.LiveRef; import sun.security.action.GetBooleanAction; import sun.security.action.GetIntegerAction; @@ -345,6 +345,7 @@ public class Activation implements Serializable { throw new AccessException( "binding ActivationSystem is disallowed"); } else { + RegistryImpl.checkAccess("ActivationSystem.bind"); super.bind(name, obj); } } @@ -356,6 +357,7 @@ public class Activation implements Serializable { throw new AccessException( "unbinding ActivationSystem is disallowed"); } else { + RegistryImpl.checkAccess("ActivationSystem.unbind"); super.unbind(name); } } @@ -368,6 +370,7 @@ public class Activation implements Serializable { throw new AccessException( "binding ActivationSystem is disallowed"); } else { + RegistryImpl.checkAccess("ActivationSystem.rebind"); super.rebind(name, obj); } } @@ -458,6 +461,33 @@ public class Activation implements Serializable { } + /** + * SameHostOnlyServerRef checks that access is from a local client + * before the parameters are deserialized. The unmarshalCustomCallData + * hook is used to check the network address of the caller + * with RegistryImpl.checkAccess(). + * The kind of access is retained for an exception if one is thrown. + */ + static class SameHostOnlyServerRef extends UnicastServerRef { + private static final long serialVersionUID = 1234L; + private String accessKind; // an exception message + + /** + * Construct a new SameHostOnlyServerRef from a LiveRef. + * @param lref a LiveRef + */ + SameHostOnlyServerRef(LiveRef lref, String accessKind) { + super(lref); + this.accessKind = accessKind; + } + + @Override + protected void unmarshalCustomCallData(ObjectInput in) throws IOException, ClassNotFoundException { + RegistryImpl.checkAccess(accessKind); + super.unmarshalCustomCallData(in); + } + } + class ActivationSystemImpl extends RemoteServer implements ActivationSystem @@ -475,7 +505,8 @@ public class Activation implements Serializable { * 'this' can be exported. */ LiveRef lref = new LiveRef(new ObjID(4), port, null, ssf); - UnicastServerRef uref = new UnicastServerRef(lref); + UnicastServerRef uref = new SameHostOnlyServerRef(lref, + "ActivationSystem.nonLocalAccess"); ref = uref; uref.exportObject(this, null); } @@ -484,8 +515,8 @@ public class Activation implements Serializable { throws ActivationException, UnknownGroupException, RemoteException { checkShutdown(); - RegistryImpl.checkAccess("ActivationSystem.registerObject"); - + // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef + // during unmarshallCustomData and is not applicable to local access. ActivationGroupID groupID = desc.getGroupID(); ActivationID id = new ActivationID(activatorStub); getGroupEntry(groupID).registerObject(id, desc, true); @@ -496,15 +527,18 @@ public class Activation implements Serializable { throws ActivationException, UnknownObjectException, RemoteException { checkShutdown(); - RegistryImpl.checkAccess("ActivationSystem.unregisterObject"); + // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef + // during unmarshallCustomData and is not applicable to local access. getGroupEntry(id).unregisterObject(id, true); } public ActivationGroupID registerGroup(ActivationGroupDesc desc) throws ActivationException, RemoteException { + Thread.dumpStack(); checkShutdown(); - RegistryImpl.checkAccess("ActivationSystem.registerGroup"); + // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef + // during unmarshallCustomData and is not applicable to local access. checkArgs(desc, null); ActivationGroupID id = new ActivationGroupID(systemStub); @@ -521,7 +555,8 @@ public class Activation implements Serializable { throws ActivationException, UnknownGroupException, RemoteException { checkShutdown(); - RegistryImpl.checkAccess("ActivationSystem.activeGroup"); + // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef + // during unmarshallCustomData and is not applicable to local access. getGroupEntry(id).activeGroup(group, incarnation); return monitor; @@ -531,7 +566,8 @@ public class Activation implements Serializable { throws ActivationException, UnknownGroupException, RemoteException { checkShutdown(); - RegistryImpl.checkAccess("ActivationSystem.unregisterGroup"); + // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef + // during unmarshallCustomData and is not applicable to local access. // remove entry before unregister so state is updated before // logged @@ -543,7 +579,8 @@ public class Activation implements Serializable { throws ActivationException, UnknownObjectException, RemoteException { checkShutdown(); - RegistryImpl.checkAccess("ActivationSystem.setActivationDesc"); + // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef + // during unmarshallCustomData and is not applicable to local access. if (!getGroupID(id).equals(desc.getGroupID())) { throw new ActivationException( @@ -557,8 +594,8 @@ public class Activation implements Serializable { throws ActivationException, UnknownGroupException, RemoteException { checkShutdown(); - RegistryImpl.checkAccess( - "ActivationSystem.setActivationGroupDesc"); + // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef + // during unmarshallCustomData and is not applicable to local access. checkArgs(desc, null); return getGroupEntry(id).setActivationGroupDesc(id, desc, true); @@ -568,7 +605,8 @@ public class Activation implements Serializable { throws ActivationException, UnknownObjectException, RemoteException { checkShutdown(); - RegistryImpl.checkAccess("ActivationSystem.getActivationDesc"); + // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef + // during unmarshallCustomData and is not applicable to local access. return getGroupEntry(id).getActivationDesc(id); } @@ -577,8 +615,8 @@ public class Activation implements Serializable { throws ActivationException, UnknownGroupException, RemoteException { checkShutdown(); - RegistryImpl.checkAccess - ("ActivationSystem.getActivationGroupDesc"); + // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef + // during unmarshallCustomData and is not applicable to local access. return getGroupEntry(id).desc; } @@ -588,7 +626,8 @@ public class Activation implements Serializable { * the activation daemon and exits the activation daemon. */ public void shutdown() throws AccessException { - RegistryImpl.checkAccess("ActivationSystem.shutdown"); + // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef + // during unmarshallCustomData and is not applicable to local access. Object lock = startupLock; if (lock != null) { diff --git a/src/share/classes/sun/rmi/server/UnicastServerRef.java b/src/share/classes/sun/rmi/server/UnicastServerRef.java index cd2e20a5240d824138ae028269048dff881e94eb..285dba5e0716c21c24e33741bfc458b16e263eba 100644 --- a/src/share/classes/sun/rmi/server/UnicastServerRef.java +++ b/src/share/classes/sun/rmi/server/UnicastServerRef.java @@ -31,6 +31,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.rmi.AccessException; import java.rmi.MarshalException; import java.rmi.Remote; import java.rmi.RemoteException; @@ -289,20 +290,25 @@ public class UnicastServerRef extends UnicastRef try { in = call.getInputStream(); num = in.readInt(); - if (num >= 0) { - if (skel != null) { - oldDispatch(obj, call, num); - return; - } else { - throw new UnmarshalException( - "skeleton class not found but required " + - "for client version"); - } + } catch (Exception readEx) { + throw new UnmarshalException("error unmarshalling call header", + readEx); + } + if (num >= 0) { + if (skel != null) { + oldDispatch(obj, call, num); + return; + } else { + throw new UnmarshalException( + "skeleton class not found but required " + + "for client version"); } + } + try { op = in.readLong(); } catch (Exception readEx) { throw new UnmarshalException("error unmarshalling call header", - readEx); + readEx); } /* @@ -335,6 +341,11 @@ public class UnicastServerRef extends UnicastRef params[i] = unmarshalValue(types[i], in); } + } catch (AccessException aex) { + // For compatibility, AccessException is not wrapped in UnmarshalException + // disable saving any refs in the inputStream for GC + ((StreamRemoteCall) call).discardPendingRefs(); + throw aex; } catch (java.io.IOException | ClassNotFoundException e) { // disable saving any refs in the inputStream for GC ((StreamRemoteCall) call).discardPendingRefs(); @@ -371,6 +382,7 @@ public class UnicastServerRef extends UnicastRef */ } } catch (Throwable e) { + Throwable origEx = e; logCallException(e); ObjectOutput out = call.getResultStream(false); @@ -386,6 +398,12 @@ public class UnicastServerRef extends UnicastRef clearStackTraces(e); } out.writeObject(e); + + // AccessExceptions should cause Transport.serviceCall + // to flag the connection as unusable. + if (origEx instanceof AccessException) { + throw new IOException("Connection is not reusable", origEx); + } } finally { call.releaseInputStream(); // in case skeleton doesn't call.releaseOutputStream(); @@ -417,62 +435,41 @@ public class UnicastServerRef extends UnicastRef * Handle server-side dispatch using the RMI 1.1 stub/skeleton * protocol, given a non-negative operation number that has * already been read from the call stream. + * Exceptions are handled by the caller to be sent to the remote client. * * @param obj the target remote object for the call * @param call the "remote call" from which operation and * method arguments can be obtained. * @param op the operation number - * @exception IOException if unable to marshal return result or + * @throws Exception if unable to marshal return result or * release input or output streams */ - public void oldDispatch(Remote obj, RemoteCall call, int op) - throws IOException + private void oldDispatch(Remote obj, RemoteCall call, int op) + throws Exception { long hash; // hash for matching stub with skeleton + // read remote call header + ObjectInput in; + in = call.getInputStream(); try { - // read remote call header - ObjectInput in; - try { - in = call.getInputStream(); - try { - Class clazz = Class.forName("sun.rmi.transport.DGCImpl_Skel"); - if (clazz.isAssignableFrom(skel.getClass())) { - ((MarshalInputStream)in).useCodebaseOnly(); - } - } catch (ClassNotFoundException ignore) { } - hash = in.readLong(); - } catch (Exception readEx) { - throw new UnmarshalException("error unmarshalling call header", - readEx); + Class clazz = Class.forName("sun.rmi.transport.DGCImpl_Skel"); + if (clazz.isAssignableFrom(skel.getClass())) { + ((MarshalInputStream)in).useCodebaseOnly(); } + } catch (ClassNotFoundException ignore) { } - // if calls are being logged, write out object id and operation - logCall(obj, skel.getOperations()[op]); - unmarshalCustomCallData(in); - // dispatch to skeleton for remote object - skel.dispatch(obj, call, op, hash); - - } catch (Throwable e) { - logCallException(e); - - ObjectOutput out = call.getResultStream(false); - if (e instanceof Error) { - e = new ServerError( - "Error occurred in server thread", (Error) e); - } else if (e instanceof RemoteException) { - e = new ServerException( - "RemoteException occurred in server thread", - (Exception) e); - } - if (suppressStackTraces) { - clearStackTraces(e); - } - out.writeObject(e); - } finally { - call.releaseInputStream(); // in case skeleton doesn't - call.releaseOutputStream(); + try { + hash = in.readLong(); + } catch (Exception ioe) { + throw new UnmarshalException("error unmarshalling call header", ioe); } + + // if calls are being logged, write out object id and operation + logCall(obj, skel.getOperations()[op]); + unmarshalCustomCallData(in); + // dispatch to skeleton for remote object + skel.dispatch(obj, call, op, hash); } /** diff --git a/test/java/rmi/activation/nonLocalActivation/NonLocalActivationTest.java b/test/java/rmi/activation/nonLocalActivation/NonLocalActivationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..594148805be1f2a74985294df1c7817f40e33118 --- /dev/null +++ b/test/java/rmi/activation/nonLocalActivation/NonLocalActivationTest.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2017, 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 java.net.InetAddress; +import java.rmi.AccessException; +import java.rmi.activation.ActivationSystem; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.util.Set; +import java.util.HashSet; + +/* + * @test + * @bug 8174770 + * @summary Verify that ActivationSystem rejects non-local access. + * The test is manual because the (non-local) host running rmid must be supplied as a property. + * @run main/manual/othervm -Dactivation.host=rmid-host NonLocalActivationTest + */ + +/** + * Lookup the ActivationSystem on a different host and invoke its remote interface methods. + * They should all throw an exception, non-local access is prohibited. + * + * This test is a manual test and uses rmid running on a *different* host. + * The default port (1098) for the Activation System is ok and expected. + * Login or ssh to the different host and invoke {@code $JDK_HOME/bin/rmid}. + * It will not show any output. + * + * On the first host modify the @run command above to replace "rmid-host" + * with the hostname or IP address of the different host and run the test with jtreg. + */ +public class NonLocalActivationTest +{ + public static void main(String[] args) throws Exception { + + String host = System.getProperty("activation.host"); + if (host == null || host.isEmpty()) { + throw new RuntimeException("Specify host with system property: -Dactivation.host="); + } + + // Check if running the test on a local system; it only applies to remote + String myHostName = InetAddress.getLocalHost().getHostName(); + Set myAddrs = new HashSet<>(); + InetAddress[] myAddrsArr = InetAddress.getAllByName(myHostName); + for (InetAddress a : myAddrsArr) { + myAddrs.add(a); + } + Set hostAddrs = new HashSet<>(); + InetAddress[] hostAddrsArr = InetAddress.getAllByName(host); + for (InetAddress a : hostAddrsArr) { + hostAddrs.add(a); + } + if (hostAddrs.stream().anyMatch(i -> myAddrs.contains(i)) + || hostAddrs.stream().anyMatch(h -> h.isLoopbackAddress())) { + throw new RuntimeException("Error: property 'activation.host' must not be the local host%n"); + } + + // Locate the registry operated by the ActivationSystem + // Test SystemRegistryImpl + Registry registry = LocateRegistry.getRegistry(host, ActivationSystem.SYSTEM_PORT); + try { + // Verify it is an ActivationSystem registry + registry.lookup("java.rmi.activation.ActivationSystem"); + } catch (Exception nf) { + throw new RuntimeException("Not a ActivationSystem registry, does not contain java.rmi.activation.ActivationSystem", nf); + } + + try { + registry.bind("foo", null); + throw new RuntimeException("Remote access should not succeed for method: bind"); + } catch (Exception e) { + assertIsAccessException(e, "Registry.bind"); + } + + try { + registry.rebind("foo", null); + throw new RuntimeException("Remote access should not succeed for method: rebind"); + } catch (Exception e) { + assertIsAccessException(e, "Registry.rebind"); + } + + try { + registry.unbind("foo"); + throw new RuntimeException("Remote access should not succeed for method: unbind"); + } catch (Exception e) { + assertIsAccessException(e, "Registry.unbind"); + } + + + // Locate the ActivationSystem on the specified host and default port. + // Test each of the ActivationSystem methods + ActivationSystem as = (ActivationSystem) registry.lookup("java.rmi.activation.ActivationSystem"); + + // Argument is not material, access check is before arg processing + + try { + as.registerGroup(null); + } catch (Exception aex) { + assertIsAccessException(aex, "ActivationSystem.nonLocalAccess"); + } + + try { + as.getActivationDesc(null); + } catch (Exception aex) { + assertIsAccessException(aex, "ActivationSystem.nonLocalAccess"); + } + + try { + as.getActivationGroupDesc(null); + } catch (Exception aex) { + assertIsAccessException(aex, "ActivationSystem.nonLocalAccess"); + } + + try { + as.registerObject(null); + } catch (Exception aex) { + assertIsAccessException(aex, "ActivationSystem.nonLocalAccess"); + } + + try { + as.unregisterGroup(null); + } catch (Exception aex) { + assertIsAccessException(aex, "ActivationSystem.nonLocalAccess"); + } + + try { + as.unregisterObject(null); + } catch (Exception aex) { + assertIsAccessException(aex, "ActivationSystem.nonLocalAccess"); + } + + try { + as.setActivationDesc(null, null); + } catch (Exception aex) { + assertIsAccessException(aex, "ActivationSystem.nonLocalAccess"); + } + + try { + as.setActivationGroupDesc(null, null); + } catch (Exception aex) { + assertIsAccessException(aex, "ActivationSystem.nonLocalAccess"); + } + } + + /** + * Check the exception chain for the expected AccessException and message. + * @param ex the exception from the remote invocation. + */ + private static void assertIsAccessException(Exception ex, String msg1) { + Throwable t = ex; + System.out.println(); + while (!(t instanceof AccessException) && t.getCause() != null) { + t = t.getCause(); + } + if (t instanceof AccessException) { + String msg = t.getMessage(); + int asIndex = msg.indexOf(msg1); + int disallowIndex = msg.indexOf("disallowed"); + int nonLocalHostIndex = msg.indexOf("non-local host"); + if (asIndex < 0 || + disallowIndex < 0 || + nonLocalHostIndex < 0 ) { + throw new RuntimeException("exception message is malformed", t); + } + System.out.printf("Found expected AccessException: %s%n", t); + } else { + throw new RuntimeException("AccessException did not occur", ex); + } + } +} diff --git a/test/java/rmi/registry/nonLocalRegistry/NonLocalRegistryTest.java b/test/java/rmi/registry/nonLocalRegistry/NonLocalRegistryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..771a34a7dce7cfb5213f57e6621e325727bd16b4 --- /dev/null +++ b/test/java/rmi/registry/nonLocalRegistry/NonLocalRegistryTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2017, 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 java.net.InetAddress; +import java.rmi.AccessException; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.util.Set; +import java.util.HashSet; + +/* @test + * @bug 8174770 + * @summary Verify that Registry rejects non-local access for bind, unbind, rebind. + * The test is manual because the (non-local) host running rmiregistry must be supplied as a property. + * @run main/othervm/manual -Dregistry.host=rmi-registry-host NonLocalRegistryTest + */ + +/** + * Verify that access checks for Registry.bind(), .rebind(), and .unbind() + * are prevented on remote access to the registry. + * + * This test is a manual test and uses a standard rmiregistry running + * on a *different* host. + * The test verifies that the access check is performed *before* the object to be + * bound or rebound is deserialized. + * + * Login or ssh to the different host and invoke {@code $JDK_HOME/bin/rmiregistry}. + * It will not show any output. + * + * On the first host modify the @run command above to replace "rmi-registry-host" + * with the hostname or IP address of the different host and run the test with jtreg. + */ +public class NonLocalRegistryTest { + + public static void main(String[] args) throws Exception { + + String host = System.getProperty("registry.host"); + if (host == null || host.isEmpty()) { + throw new RuntimeException("Specify host with system property: -Dregistry.host="); + } + + // Check if running the test on a local system; it only applies to remote + String myHostName = InetAddress.getLocalHost().getHostName(); + Set myAddrs = new HashSet<>(); + InetAddress[] myAddrsArr = InetAddress.getAllByName(myHostName); + for (InetAddress a : myAddrsArr) { + myAddrs.add(a); + } + Set hostAddrs = new HashSet<>(); + InetAddress[] hostAddrsArr = InetAddress.getAllByName(host); + for (InetAddress a : hostAddrsArr) { + hostAddrs.add(a); + } + if (hostAddrs.stream().anyMatch(i -> myAddrs.contains(i)) + || hostAddrs.stream().anyMatch(h -> h.isLoopbackAddress())) { + throw new RuntimeException("Error: property 'registry.host' must not be the local host%n"); + } + + Registry registry = LocateRegistry.getRegistry(host, Registry.REGISTRY_PORT); + + try { + registry.bind("foo", null); + throw new RuntimeException("Remote access should not succeed for method: bind"); + } catch (Exception e) { + assertIsAccessException(e); + } + + try { + registry.rebind("foo", null); + throw new RuntimeException("Remote access should not succeed for method: rebind"); + } catch (Exception e) { + assertIsAccessException(e); + } + + try { + registry.unbind("foo"); + throw new RuntimeException("Remote access should not succeed for method: unbind"); + } catch (Exception e) { + assertIsAccessException(e); + } + } + + /** + * Check the exception chain for the expected AccessException and message. + * @param ex the exception from the remote invocation. + */ + private static void assertIsAccessException(Throwable ex) { + Throwable t = ex; + while (!(t instanceof AccessException) && t.getCause() != null) { + t = t.getCause(); + } + if (t instanceof AccessException) { + String msg = t.getMessage(); + int asIndex = msg.indexOf("Registry"); + int rrIndex = msg.indexOf("Registry.Registry"); // Obsolete error text + int disallowIndex = msg.indexOf("disallowed"); + int nonLocalHostIndex = msg.indexOf("non-local host"); + if (asIndex < 0 || + rrIndex != -1 || + disallowIndex < 0 || + nonLocalHostIndex < 0 ) { + throw new RuntimeException("exception message is malformed", t); + } + System.out.printf("Found expected AccessException: %s%n%n", t); + } else { + throw new RuntimeException("AccessException did not occur when expected", ex); + } + } +} diff --git a/test/javax/management/remote/nonLocalAccess/NonLocalJMXRemoteTest.java b/test/javax/management/remote/nonLocalAccess/NonLocalJMXRemoteTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c20e67a571bc3de0788ae42abb6e36ea70a6dd57 --- /dev/null +++ b/test/javax/management/remote/nonLocalAccess/NonLocalJMXRemoteTest.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2017, 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 java.net.InetAddress; +import java.rmi.AccessException; +import java.rmi.NotBoundException; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.util.Set; +import java.util.HashSet; + +/* @test + * @bug 8174770 + * @summary Verify that JMX Registry rejects non-local access for bind, unbind, rebind. + * The test is manual because the (non-local) host and port running JMX must be supplied as properties. + * @run main/othervm/manual -Djmx-registry.host=jmx-registry-host -Djmx-registry.port=jmx-registry-port NonLocalJMXRemoteTest + */ + +/** + * Verify that access checks for the Registry exported by JMX Registry.bind(), + * .rebind(), and .unbind() are prevented on remote access to the registry. + * The test verifies that the access check is performed *before* the object to be + * bound or rebound is deserialized. + * This tests the SingleEntryRegistry implemented by JMX. + * This test is a manual test and uses JMX running on a *different* host. + * JMX can be enabled in any Java runtime; for example: + * login or ssh to the different host and invoke rmiregistry with arguments below. + * It will not show any output. + * {@code $JDK_HOME/bin/rmiregistry \ + * -J-Dcom.sun.management.jmxremote.port=8888 \ + * -J-Dcom.sun.management.jmxremote.local.only=false \ + * -J-Dcom.sun.management.jmxremote.ssl=false \ + * -J-Dcom.sun.management.jmxremote.authenticate=false + * } + * On the first host modify the @run command above to replace "jmx-registry-host" + * with the hostname or IP address of the different host and run the test with jtreg. + */ +public class NonLocalJMXRemoteTest { + + public static void main(String[] args) throws Exception { + + String host = System.getProperty("jmx-registry.host"); + if (host == null || host.isEmpty()) { + throw new RuntimeException("Specify host with system property: -Djmx-registry.host="); + } + int port = Integer.getInteger("jmx-registry.port", -1); + if (port <= 0) { + throw new RuntimeException("Specify port with system property: -Djmx-registry.port="); + } + + // Check if running the test on a local system; it only applies to remote + String myHostName = InetAddress.getLocalHost().getHostName(); + Set myAddrs = new HashSet<>(); + InetAddress[] myAddrsArr = InetAddress.getAllByName(myHostName); + for (InetAddress a : myAddrsArr) { + myAddrs.add(a); + } + Set hostAddrs = new HashSet<>(); + InetAddress[] hostAddrsArr = InetAddress.getAllByName(host); + for (InetAddress a : hostAddrsArr) { + hostAddrs.add(a); + } + if (hostAddrs.stream().anyMatch(i -> myAddrs.contains(i)) + || hostAddrs.stream().anyMatch(h -> h.isLoopbackAddress())) { + throw new RuntimeException("Error: property 'jmx-registry.host' must not be the local host%n"); + } + + Registry registry = LocateRegistry.getRegistry(host, port); + try { + // Verify it is a JMX Registry + registry.lookup("jmxrmi"); + } catch (NotBoundException nf) { + throw new RuntimeException("Not a JMX registry, jmxrmi is not bound", nf); + } + + try { + registry.bind("foo", null); + throw new RuntimeException("Remote access should not succeed for method: bind"); + } catch (Exception e) { + assertIsAccessException(e); + } + + try { + registry.rebind("foo", null); + throw new RuntimeException("Remote access should not succeed for method: rebind"); + } catch (Exception e) { + assertIsAccessException(e); + } + + try { + registry.unbind("foo"); + throw new RuntimeException("Remote access should not succeed for method: unbind"); + } catch (Exception e) { + assertIsAccessException(e); + } + } + + /** + * Check the exception chain for the expected AccessException and message. + * @param ex the exception from the remote invocation. + */ + private static void assertIsAccessException(Throwable ex) { + Throwable t = ex; + while (!(t instanceof AccessException) && t.getCause() != null) { + t = t.getCause(); + } + if (t instanceof AccessException) { + String msg = t.getMessage(); + int asIndex = msg.indexOf("Registry"); + int disallowIndex = msg.indexOf("disallowed"); + int nonLocalHostIndex = msg.indexOf("non-local host"); + if (asIndex < 0 || + disallowIndex < 0 || + nonLocalHostIndex < 0 ) { + throw new RuntimeException("exception message is malformed", t); + } + System.out.printf("Found expected AccessException: %s%n%n", t); + } else { + throw new RuntimeException("AccessException did not occur when expected", ex); + } + } +}