Skip to main content

Securing the RMI registry

Posted by emcmanus on December 29, 2006 at 9:39 AM PST

If you've had occasion to use the RMI registry seriously, you
may have encountered some of its shortcomings. Chief of these
is that anybody on the local machine can modify the registry.
There are only a few things you can do about that, of which the
craziest is to reimplement enough of RMI to code your own
compatible version of the registry. I did that, and write
about it in a later
entry
, but then I discovered a simpler alternative, due
to Peter Jones.

We encountered this problem when defining the JMX Remote API.
The standard connector defined by this API is the RMI connector,
and the obvious way for it to work would have been to interpret a
URL like service:jmx:rmi://somehost:8888/bar as meaning
"an href="http://java.sun.com/javase/6/docs/api/javax/management/remote/rmi/RMIServer.html">RMIServer
object called bar in the RMI registry at port 8888 on
host somehost. We wouldn't have needed to support any other
address types. The same address would work on the client and on
the server.

Well, if you have a look at href="http://java.sun.com/javase/6/docs/api/javax/management/remote/rmi/package-summary.html#package_description">the
documentation, you'll see that things are quite a bit more
complicated than that. Addresses come in two forms, of which
the first is this rather ugly one:

service:jmx:rmi:///jndi/rmi://somehost:8888/bar

That is as nothing to the ugliness of the second form, though:

service:jmx:rmi://somehost/stub/rO0ABXNyAC5qYXZheC5tYW5hZ2VtZW50LnJlbW90ZS5ybWk
uUk1JU2VydmVySW1wbF9TdHViAAAAAAAAAAICAAB4cgAaamF2YS5ybWkuc2VydmVyLlJlbW90ZVN0dW
Lp/tzJi+FlGgIAAHhyABxqYXZhLnJtaS5zZXJ2ZXIuUmVtb3RlT2JqZWN002G0kQxhMx4DAAB4cHc5A
AtVbmljYXN0UmVmMgAADjEyOS4xNTcuMjAzLjM5AACauPDsdTz1J8m+BsXsHAAAAQ/Ow/uqgAEAeA==

Blechh.

As Spec Lead for JSR 160, I bear considerable responsibility
for this ugliness, and you can believe that I didn't accept it
gladly.

Why we perpetrated these address forms

There were two problems that led us to exclude the simple
approach with the RMI registry. The first was that a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4267864">longstanding
bug in the JDK meant that you couldn't have more than one RMI
registry in the same VM. So there was great potential for
interference between different modules each needing to create a
registry. This bug was fixed in JDK 1.5 but JSR 160 was
supported all the way back to 1.3.

The second problem was the one I mentioned above, namely that
the only access check on changes to the RMI registry is that the
client making the change must be connecting from the same machine
that the registry is running on. This means that if you store
your JMX Connector Server in an RMI registry on a multiuser
machine, some other unscrupulous user can come along and replace
it with their own RMI object. Users would then connect to the
rogue object without realising it, and send their password or
other credentials to authenticate.

Securing the RMI registry

We've addressed this problem in a couple of ways. First of
all, you can protect your registry using SSL client
authentication. Luis-Miguel Alventosa has explained this in
detail in his href="http://blogs.sun.com/lmalventosa/entry/secure_management_agent">blog.
The big advantage of using SSL is that clients can be sure they
are connecting to the server they are expecting, and not a
server being run by Dr Evil on the same port. But that's
server authentication. Using SSL client
authentication seems redundant given that the client can
authenticate to the JMX Connector Server with a password or
other credentials. You only really need the SSL part to protect
the registry from rogue modifications. And setting up the
necessary SSL client certificates is a non-trivial amount of
work.

The second possibility is to access implementation-specific
sun.* classes to define a custom registry. We did
this in the JDK code that handles the href="http://java.sun.com/j2se/1.5.0/docs/guide/management/agent.html#properties">JMX
command-line properties,
-Dcom.sun.management.jmxremote etc. When you
specify those properties, we create a special read-only RMI
registry with a single entry "jmxrmi". See
sun.management.jmxremote.SingleEntryRegistry in the
JDK sources, which does its work by subclassing
sun.rmi.registry.RegistryImpl.

We can get away with this because this code is part of the same
JDK as the implementation-dependent classes it is accessing. I
strongly discourage anyone from doing this sort of thing outside
the JDK.
The sun.* classes are subject to change
without notice, and indeed as of JDK 6 the compiler will produce
warnings for code that references them.

A portable (if hacky) solution

A viable alternative is described in the Work Around section of
href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5029435">RFE
5029435.

The access check to see if the caller is on the same machine
can be faked so that it always fails, even if the caller
is on the same machine. The idea is that the caller's
address comes from href="http://java.sun.com/javase/6/docs/api/java/net/Socket.html#getInetAddress()">Socket.getInetAddress().
By imaginative use of RMI socket factories and Socket subclasses,
we can arrange for this method never to return a local
address.

The access check is only
made when the registry is accessed via RMI. It is not made if
the Registry object returned by href="http://java.sun.com/javase/6/docs/api/java/rmi/registry/LocateRegistry.html#createRegistry(int,%20java.rmi.server.RMIClientSocketFactory,%20java.rmi.server.RMIServerSocketFactory)">
LocateRegistry.createRegistry
is accessed directly from within the VM where it was created.
So you can create the registry and set up its contents within
the same VM, but others, even on the same machine, cannot
modify it. Here's what it looks like in code:

    Registry r = LocateRegistry.createRegistry(
        8888, null, new NoLocalAddressServerSocketFactory());
    Foo fooObject = new Foo();
    Remote fooStub = UnicastRemoteObject.exportObject(fooObject, 0);
    r.bind("foo", fooStub);
    Bar barObject = new Bar();
    Remote barStub = UnicastRemoteObject.exportObject(barObject, 0);
    r.bind("bar", barObject);

The bind operations here will succeed because they
are accessing the registry object locally, not via RMI. But any
attempt to modify it via RMI, even from the same machine, will
fail, if we define NoLocalAddressServerSocketFactory
as follows. (Thanks to Tom Hawtin for the idea behind this
version, simpler than the one I originally posted.
)

public class NoLocalAddressServerSocketFactory
        implements RMIServerSocketFactory {

    public ServerSocket createServerSocket(int port) throws IOException {
        return new NoLocalAddressServerSocket(port);
    }
   
    private static class NoLocalAddressServerSocket extends ServerSocket {
        NoLocalAddressServerSocket(int port) throws IOException {
            super(port);
        }
       
        @Override
        public Socket accept() throws IOException {
            Socket s = new NoLocalAddressSocket();
            super.implAccept(s);
            return s;
        }
    }
   
    private static class NoLocalAddressSocket extends Socket {
        @Override
        public InetAddress getInetAddress() {
            return null;
        }
    }
}

It's a bit hacky, but it does the trick!

You're not limited to rejecting all modifications. For
example, suppose you wanted to allow all modifications,
whether or not the caller is remote, and security be damned. Then
you could simply return href="http://java.sun.com/javase/6/docs/api/java/net/InetAddress.html#getLocalHost()">
InetAddress.getLocalHost() instead of null. Or, if you've
exported the registry through SSL (see below), then you might
allow modifications only from clients that have been authenticated
through SSL, but you would allow lookups from all clients. So you
would return InetAddress.getLocalHost() if the client is
authenticated and null otherwise.

If you are already using a socket factory

Things are a bit more complicated if you are already using an
RMIServerSocketFactory, such as href="http://java.sun.com/javase/6/docs/api/javax/rmi/ssl/SslRMIServerSocketFactory.html">SslRMIServerSocketFactory.
In that case, you'll need to combine subclassing with delegation,
as outlined here:

public class NoLocalAddressServerSocketFactory2
        implements RMIServerSocketFactory {

    private final RMIServerSocketFactory factory;
   
    public NoLocalAddressServerSocketFactory2(RMIServerSocketFactory f) {
        if (f == null) {
            f = RMISocketFactory.getSocketFactory();
            if (f == null)
                f = RMISocketFactory.getDefaultSocketFactory();
        }
        this.factory = f;
    }

    public ServerSocket createServerSocket(int port) throws IOException {
        ServerSocket ss = factory.createServerSocket(port);
        return new NoLocalAddressServerSocket(ss);
    }
   
    private static class NoLocalAddressServerSocket extends ServerSocket {
        private final ServerSocket serverSocket;
       
        NoLocalAddressServerSocket(ServerSocket ss) throws IOException {
            this.serverSocket = ss;
        }
       
        @Override
        public Socket accept() throws IOException {
            return new NoLocalAddressSocket(serverSocket.accept());
        }

@Override
        public void close() throws IOException {
            serverSocket.close();
        }

        ...override 13 other ServerSocket methods in the same way as close()...
    }
   
    private static class NoLocalAddressSocket extends Socket {
        private final Socket socket;
       
        NoLocalAddressSocket(Socket s) {
            this.socket = s;
        }
       
        @Override
        public InetAddress getInetAddress() {
            return null;
        }

@Override
        public void close() throws IOException {
            socket.close();
        }
       
...override 38 more Socket methods in the same way as close()...
    }
}

The main drawback that I can see is that if a future version of
java.net.Socket adds more methods, they won't be
overridden by NoLocalAddressSocket, and that might
cause problems if the RMI internals use the new methods. (If
you're really worried about that, you might want to use href="http://cglib.sourceforge.net/">cglib to produce the
equivalent of NoLocalAddressSocket dynamically.)

Credit where it's due

The public bug database is anonymized but I can
reveal that this idea, like so many RMI ideas, is the work of
the discreet genius Peter Jones.

Related Topics >>