From 4ce7b5f28063abca8391d5d7173a45210191e269 Mon Sep 17 00:00:00 2001 From: Roger Riggs Date: Wed, 3 May 2017 12:56:02 -0400 Subject: [PATCH] 8174770: Check registry registration location Reviewed-by: dfuchs, smarks, chegar --- jdk/make/rmic/Rmic-java.rmi.gmk | 8 +- .../sun/rmi/registry/RegistryImpl.java | 36 +++- .../sun/rmi/registry/RegistryImpl_Skel.java | 177 ++++++++++++++++ .../sun/rmi/registry/RegistryImpl_Stub.java | 189 ++++++++++++++++++ .../classes/sun/rmi/server/Activation.java | 68 +++++-- .../sun/rmi/server/UnicastServerRef.java | 101 +++++----- .../jmxremote/SingleEntryRegistry.java | 20 +- .../NonLocalActivationTest.java | 181 +++++++++++++++++ .../NonLocalRegistryTest.java | 120 +++++++++++ .../nonLocalAccess/NonLocalJMXRemoteTest.java | 133 ++++++++++++ 10 files changed, 950 insertions(+), 83 deletions(-) create mode 100644 jdk/src/java.rmi/share/classes/sun/rmi/registry/RegistryImpl_Skel.java create mode 100644 jdk/src/java.rmi/share/classes/sun/rmi/registry/RegistryImpl_Stub.java create mode 100644 jdk/test/java/rmi/activation/nonLocalActivation/NonLocalActivationTest.java create mode 100644 jdk/test/java/rmi/registry/nonLocalRegistry/NonLocalRegistryTest.java create mode 100644 jdk/test/javax/management/remote/nonLocalAccess/NonLocalJMXRemoteTest.java diff --git a/jdk/make/rmic/Rmic-java.rmi.gmk b/jdk/make/rmic/Rmic-java.rmi.gmk index 9e4b2542b3e..7a7a464e8e4 100644 --- a/jdk/make/rmic/Rmic-java.rmi.gmk +++ b/jdk/make/rmic/Rmic-java.rmi.gmk @@ -40,15 +40,9 @@ $(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)/java.rmi, \ - STUB_CLASSES_DIR := $(STUB_CLASSES_DIR)/java.rmi, \ - RUN_V11 := true)) -GENCLASSES += $(RMI_11) ################################################################################ -all: $(RMI_11) $(RMI_12) +all: $(RMI_12) .PHONY: all diff --git a/jdk/src/java.rmi/share/classes/sun/rmi/registry/RegistryImpl.java b/jdk/src/java.rmi/share/classes/sun/rmi/registry/RegistryImpl.java index 6b75a2a6ecb..af012faeb78 100644 --- a/jdk/src/java.rmi/share/classes/sun/rmi/registry/RegistryImpl.java +++ b/jdk/src/java.rmi/share/classes/sun/rmi/registry/RegistryImpl.java @@ -75,6 +75,10 @@ * 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 */ @@ -143,6 +147,20 @@ public RegistryImpl(int port, 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. @@ -150,7 +168,7 @@ public RegistryImpl(int port, 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")); @@ -226,7 +244,8 @@ public Remote lookup(String name) 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) @@ -243,7 +262,8 @@ public void bind(String name, Remote obj) 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) @@ -259,7 +279,8 @@ public void unbind(String name) 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); } @@ -312,7 +333,7 @@ public InetAddress run() if (clientHost.isAnyLocalAddress()) { throw new AccessException( - "Registry." + op + " disallowed; origin unknown"); + op + " disallowed; origin unknown"); } try { @@ -335,7 +356,7 @@ public Void run() throws java.io.IOException { // must have been an IOException throw new AccessException( - "Registry." + op + " disallowed; origin " + + op + " disallowed; origin " + clientHost + " is non-local host"); } } @@ -344,8 +365,7 @@ public Void run() throws java.io.IOException { * 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/jdk/src/java.rmi/share/classes/sun/rmi/registry/RegistryImpl_Skel.java b/jdk/src/java.rmi/share/classes/sun/rmi/registry/RegistryImpl_Skel.java new file mode 100644 index 00000000000..842d4771959 --- /dev/null +++ b/jdk/src/java.rmi/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/jdk/src/java.rmi/share/classes/sun/rmi/registry/RegistryImpl_Stub.java b/jdk/src/java.rmi/share/classes/sun/rmi/registry/RegistryImpl_Stub.java new file mode 100644 index 00000000000..f8574869147 --- /dev/null +++ b/jdk/src/java.rmi/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/jdk/src/java.rmi/share/classes/sun/rmi/server/Activation.java b/jdk/src/java.rmi/share/classes/sun/rmi/server/Activation.java index a6ebbca80c0..6c60f46c603 100644 --- a/jdk/src/java.rmi/share/classes/sun/rmi/server/Activation.java +++ b/jdk/src/java.rmi/share/classes/sun/rmi/server/Activation.java @@ -30,6 +30,7 @@ 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.ReliableLog; import sun.rmi.registry.RegistryImpl; import sun.rmi.runtime.NewThreadAction; -import sun.rmi.server.UnicastServerRef; import sun.rmi.transport.LiveRef; import sun.security.provider.PolicyFile; import com.sun.rmi.rmid.ExecPermission; @@ -375,6 +375,7 @@ public void bind(String name, Remote obj) throw new AccessException( "binding ActivationSystem is disallowed"); } else { + RegistryImpl.checkAccess("ActivationSystem.bind"); super.bind(name, obj); } } @@ -386,6 +387,7 @@ public void unbind(String name) throw new AccessException( "unbinding ActivationSystem is disallowed"); } else { + RegistryImpl.checkAccess("ActivationSystem.unbind"); super.unbind(name); } } @@ -398,6 +400,7 @@ public void rebind(String name, Remote obj) throw new AccessException( "binding ActivationSystem is disallowed"); } else { + RegistryImpl.checkAccess("ActivationSystem.rebind"); super.rebind(name, obj); } } @@ -488,6 +491,33 @@ public void inactiveGroup(ActivationGroupID id, } + /** + * 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 @@ -505,7 +535,8 @@ class ActivationSystemImpl * '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); } @@ -514,8 +545,8 @@ public ActivationID registerObject(ActivationDesc desc) 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); @@ -526,7 +557,8 @@ public void unregisterObject(ActivationID id) 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); } @@ -534,7 +566,8 @@ public ActivationGroupID registerGroup(ActivationGroupDesc desc) throws ActivationException, RemoteException { 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); @@ -551,7 +584,8 @@ public ActivationMonitor activeGroup(ActivationGroupID id, 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; @@ -561,7 +595,8 @@ public void unregisterGroup(ActivationGroupID id) 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 @@ -573,7 +608,8 @@ public ActivationDesc setActivationDesc(ActivationID id, 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( @@ -587,8 +623,8 @@ public ActivationGroupDesc setActivationGroupDesc(ActivationGroupID id, 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); @@ -598,7 +634,8 @@ public ActivationDesc getActivationDesc(ActivationID id) 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); } @@ -607,8 +644,8 @@ public ActivationGroupDesc getActivationGroupDesc(ActivationGroupID id) 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; } @@ -618,7 +655,8 @@ public ActivationGroupDesc getActivationGroupDesc(ActivationGroupID id) * 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/jdk/src/java.rmi/share/classes/sun/rmi/server/UnicastServerRef.java b/jdk/src/java.rmi/share/classes/sun/rmi/server/UnicastServerRef.java index e9ae36ae685..f0a4e0f4e00 100644 --- a/jdk/src/java.rmi/share/classes/sun/rmi/server/UnicastServerRef.java +++ b/jdk/src/java.rmi/share/classes/sun/rmi/server/UnicastServerRef.java @@ -33,6 +33,7 @@ import java.io.ObjectStreamClass; 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; @@ -288,20 +289,25 @@ public void dispatch(Remote obj, RemoteCall call) throws IOException { 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); } /* @@ -329,6 +335,11 @@ public void dispatch(Remote obj, RemoteCall call) throws IOException { try { unmarshalCustomCallData(in); params = unmarshalParameters(obj, method, marshalStream); + } 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(); @@ -365,6 +376,7 @@ public void dispatch(Remote obj, RemoteCall call) throws IOException { */ } } catch (Throwable e) { + Throwable origEx = e; logCallException(e); ObjectOutput out = call.getResultStream(false); @@ -380,6 +392,12 @@ public void dispatch(Remote obj, RemoteCall call) throws IOException { 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(); @@ -408,62 +426,41 @@ protected void unmarshalCustomCallData(ObjectInput in) * 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/jdk/src/jdk.management.agent/share/classes/sun/management/jmxremote/SingleEntryRegistry.java b/jdk/src/jdk.management.agent/share/classes/sun/management/jmxremote/SingleEntryRegistry.java index 998b00ab14e..c29df19c1a5 100644 --- a/jdk/src/jdk.management.agent/share/classes/sun/management/jmxremote/SingleEntryRegistry.java +++ b/jdk/src/jdk.management.agent/share/classes/sun/management/jmxremote/SingleEntryRegistry.java @@ -32,6 +32,7 @@ package sun.management.jmxremote; +import java.io.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 void unbind(String name) throws AccessException { 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/jdk/test/java/rmi/activation/nonLocalActivation/NonLocalActivationTest.java b/jdk/test/java/rmi/activation/nonLocalActivation/NonLocalActivationTest.java new file mode 100644 index 00000000000..55cea96a0d2 --- /dev/null +++ b/jdk/test/java/rmi/activation/nonLocalActivation/NonLocalActivationTest.java @@ -0,0 +1,181 @@ +/* + * 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; + +/* + * @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 = Set.of(InetAddress.getAllByName(myHostName)); + Set hostAddrs = Set.of(InetAddress.getAllByName(host)); + 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/jdk/test/java/rmi/registry/nonLocalRegistry/NonLocalRegistryTest.java b/jdk/test/java/rmi/registry/nonLocalRegistry/NonLocalRegistryTest.java new file mode 100644 index 00000000000..7dd14768239 --- /dev/null +++ b/jdk/test/java/rmi/registry/nonLocalRegistry/NonLocalRegistryTest.java @@ -0,0 +1,120 @@ +/* + * 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; + +/* @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 = Set.of(InetAddress.getAllByName(myHostName)); + Set hostAddrs = Set.of(InetAddress.getAllByName(host)); + 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/jdk/test/javax/management/remote/nonLocalAccess/NonLocalJMXRemoteTest.java b/jdk/test/javax/management/remote/nonLocalAccess/NonLocalJMXRemoteTest.java new file mode 100644 index 00000000000..d05aff18f5c --- /dev/null +++ b/jdk/test/javax/management/remote/nonLocalAccess/NonLocalJMXRemoteTest.java @@ -0,0 +1,133 @@ +/* + * 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; + +/* @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 = Set.of(InetAddress.getAllByName(myHostName)); + Set hostAddrs = Set.of(InetAddress.getAllByName(host)); + 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); + } + } +}