I've been examining the code relating to discovery, feel free to clarify if I've missed some important detail.

River currently has two discovery protocols,

Discovery V2 and V1.

V1 dates from Jini 1.0, during the discovery process, registrar proxy's are stored in java.rmi.MarshalledObject and transferred over the network in serialized form.

Discovery V1 utilised MarshalledObject, I haven't figured out a way prevent Denial of service attacks during unmarshalling of the registrar proxy for V1.

It is important to remember there are two occasions when we're susceptible to DOS attack, firstly during deserialization of MarshalledObject and secondly when we unmarshal the registrar proxy from the MarshalledObject.

The question is of course what to do with Discovery V1 going forward? Should we deprecate it? If so, how should we ensure that V2 is used by default. DiscoveryConstraints?

Discovery V2 (Unicast), uses MarshalledInstance serial form, there's a lot more we can do with V2, it can utilise multiple implementations via DiscoveryFormatProvider.

Discovery V2 supports constraints and has a number of pluggable implementations. It has SSL and Kerberos implementations.

In LookupDiscovery, Multicast is used first to discover a registrar and create a MulticastAnnouncement which contains the following fields:

public class MulticastAnnouncement {

   /** The announcement sequence number. */
   protected long sequenceNumber;
   /** The lookup service host. */
   protected String host;
   /** The lookup service listen port. */
   protected int port;
   /** The lookup service member groups. */
   protected String[] groups;
   /** The lookup service ID. */
   protected ServiceID serviceID;


The information gathered during Multicasting is then used to for Unicast to get the Registrar proxy (from a serialized MarshalledInstance).

DNS-SD could be used to provide the same information as MulticastAnnouncement prior to performing Unicast discovery, this will allow services to be dynamically discovered over the internet proper.

Multicast uses datagram packets with a recommended limit of 512 bytes, I can't think of much opportunity for an attack on the client, it's a different story for the Join protocol and the registrar but since multicast is only utilised on private networks this shouldn't be a problem. DNS-SD allows a range of addresses and ports to be utilised dynamically, multiple registrars for a group might assist to protect against packet storms, remembering that Multicast packets can't be utilised in a packet storm over the internet, because of TTL routers etc.

We will have to take DOS into account when designing a DNS-SD equivalent to Multicast.

Currently Unicast can be subjected to DOS, Notice in the Discovery V2 Unicast implementation methods below, we're reading more than just the MarshalledInstance. Since Unicast is not limited by packet size, an attacker can feed an unending stream of bytes (very large group string arrays etc) to protect against Denial of Service we need to run Unicast Discovery within an Executor thread that handles StackOverflowError's, until we can return a UnicastResponse.



Excerpt from com.sun.jini.discovery.internal.PlainText:

   /**
    * Reads unicast response according to the net.jini.discovery.plaintext
    * format.
    */
   public static UnicastResponse readUnicastResponse(
                       InputStream in,
                       ClassLoader defaultLoader,
                       boolean verifyCodebaseIntegrity,
                       ClassLoader verifierLoader,
                       Collection context)
   throws IOException, ClassNotFoundException
   {
   try {
       DataInput din = new DataInputStream(in);

       // read LUS host
       String host = din.readUTF();

       // read LUS port
       int port = din.readUnsignedShort();

       // read LUS member groups
       String[] groups = new String[din.readInt()];
       for (int i = 0; i < groups.length; i++) {
       groups[i] = din.readUTF();
       }

       // read LUS proxy
       MarshalledInstance mi =
       (MarshalledInstance) new ObjectInputStream(in).readObject();
       /* We have the opportunity to protect against unmarshalling
        * attacks, this is the place to do it.
        */
       ServiceRegistrar reg = (ServiceRegistrar) mi.get(
       defaultLoader,
       verifyCodebaseIntegrity,
       verifierLoader,
       context);

       return new UnicastResponse(host, port, groups, reg);

   } catch (RuntimeException e) {
       throw new DiscoveryProtocolException(null, e);
   }
   }


Excerpt from com.sun.jini.discovery.plaintext.Client:


   // documentation inherited from UnicastDiscoveryClient
   public UnicastResponse doUnicastDiscovery(
                   Socket socket,
                   InvocationConstraints constraints,
                   ClassLoader defaultLoader,
                   ClassLoader verifierLoader,
                   Collection context,
                   ByteBuffer sent,
                   ByteBuffer received)
   throws IOException, ClassNotFoundException
   {
   Plaintext.checkConstraints(constraints);
   return Plaintext.readUnicastResponse(
          new BufferedInputStream(socket.getInputStream()),
          defaultLoader,
          false,
          null,
          context);
   }


Excerpt from com.sun.jini.discovery.internal.EndpointBasedClient:


/**
* Provides an abstract endpoint-based UnicastDiscoveryClient implementation,
* which serves as a superclass for client-side providers for the
* net.jini.discovery.ssl and net.jini.discovery.kerberos unicast discovery
* formats.
*/
public abstract class EndpointBasedClient
   extends EndpointBasedProvider implements UnicastDiscoveryClient
{
   /**
    * Constructs instance with the given format name and object providing
    * access to non-public endpoint operations.
    */
   protected EndpointBasedClient(String formatName,
                 EndpointInternals endpointInternals)
   {
   super(formatName, endpointInternals);
   }

   // documentation inherited from UnicastDiscoveryClient
   public void checkUnicastDiscoveryConstraints(
                   InvocationConstraints constraints)
   throws UnsupportedConstraintException
   {
   if (constraints == null) {
       constraints = InvocationConstraints.EMPTY;
   }
   ConnectionInfo ci = getConnectionInfo(null, constraints);
   checkIntegrity(endpointInternals.getUnfulfilledConstraints(ci.handle));
   }

   // documentation inherited from UnicastDiscoveryClient
   public UnicastResponse doUnicastDiscovery(
                   Socket socket,
                   InvocationConstraints constraints,
                   ClassLoader defaultLoader,
                   ClassLoader verifierLoader,
                   Collection context,
                   ByteBuffer sent,
                   ByteBuffer received)
   throws IOException, ClassNotFoundException
   {
   if (socket == null || sent == null || received == null) {
       throw new NullPointerException();
   }
   if (constraints == null) {
       constraints = InvocationConstraints.EMPTY;
   }
   ConnectionInfo ci = getConnectionInfo(socket, constraints);
   Connection conn = ci.endpoint.connect(ci.handle);
   try {
       boolean integrity =
       checkIntegrity(conn.getUnfulfilledConstraints(ci.handle));
       OutputStream out =
       new BufferedOutputStream(conn.getOutputStream());
       conn.writeRequestData(ci.handle, out);
       out.write(calcHandshakeHash(sent, received));
       out.flush();

       InputStream in = new BufferedInputStream(conn.getInputStream());
       IOException e = conn.readResponseData(ci.handle, in);
       if (e != null) {
       throw e;
       }
       return Plaintext.readUnicastResponse(
       in, defaultLoader, integrity, verifierLoader, context);
   } finally {
       conn.close();
   }
   }

It looks as though Kerberos and SSL Discovery provides guarantees for integrity and authentication. So is MarshalledInstance really subject to DOS attacks during unmarshalling in this case? Can we use DNS-SD with Secure protocols? For the registrar proxy, it would seem that it isn't subject to DOS attacks if Kerberos or SSL Discovery is used.

However if we're using the Registrar to lookup other services, we might not have the same trust relationship with them as we do the Registrar, so in that case it justifies requiring a service authenticate prior to unmarshalling it's proxy from MarshalledInstance.

There also seems to be a case still for Codebase services, we can determine codebase service trust prior to downloading a codebase from it. This wouldn't prevent Maven from being utilised for codebase provisioning, although we still need to determine a suitable way to utilise Maven.

If we're using Kerberos and SSL discovery, from the Jini Discovery and Join Specification:


     The net.jini.discovery.ssl Format

The net.jini.discovery.ssl format specifies an encoding in which unicast response data is sent across a TLS/SSL (Transport Layer Security/Secure Socket Layer) connection. Encryption, authentication, and/or integrity protection may be provided by the underlying TLS/SSL connection, depending on the selected cipher suite. This discovery format does not apply to multicast requests or announcements. The discovery format ID for this format is 1816474798606646324. TLS/SSL is specified in /RFC 2246 <http://tools.ietf.org/html/rfc2246>/.


       Unicast Responses

In the net.jini.discovery.ssl format, transmission of unicast response data involves three steps:

  1. The discovering entity establishes a TLS/SSL connection between
     itself and the lookup service on top of the TCP connection over
     which it sent the unicast request.
  2. The discovering entity transmits a hash of all of the data it has
     sent and received over the connection so far.
  3. The lookup service also computes a hash of all of the data it has
     received and sent over the connection so far. If this hash value
     matches that sent by the discovering entity, then the lookup
     service sends the unicast response data encoded as described later
     in this section. If the hash does not match, the lookup service
     terminates the connection.

The hash sent by the discovering entity to the lookup service is computed as follows: first, a sequence of bytes is assembled consisting of the entire unicast request immediately followed by the portion of the unicast response that is not discovery format data--the initial protocol version followed by the selected format ID, as specified in Section DJ.2.6.7, "Protocol Version 2 Response Format" <http://www.jini.org/wiki/Jini_Discovery_and_Join_Specification#Protocol_Version_2_Response_Format>. This sequence of bytes is then used as input to the SHA-1 hash function; the 160-bit result is sent in its entirety in big-endian order as the hash value. The SHA-1 hash function is specified in /Federal Information Processing Standards Publication (FIPS PUB) 180-1/.

The unicast response data sent by the lookup service, if the hashes match, consists of the following values encoded (on top of the secured TLS/SSL connection) according to the net.jini.discovery.plaintext discovery format for unicast responses, specified in Section DJ.3.1.3 <http://www.jini.org/wiki/Jini_Discovery_and_Join_Specification#Unicast_Responses>:

   * Unicast discovery host
   * Unicast discovery port
   * Member groups
   * Registrar proxy


     The net.jini.discovery.kerberos Format

The net.jini.discovery.kerberos format specifies an encoding in which unicast response data is sent across a connection secured using the Kerberos Version 5 GSS-API Mechanism, defined in /RFC 1964 <http://tools.ietf.org/html/rfc1964>/. Kerberos provides authentication; encryption and integrity protection of the transmitted data may also be provided depending on the GSS-API context in use for the connection. This discovery format does not apply to multicast requests or announcements. The discovery format ID for this format is 5724038453852586603. The Kerberos network authentication protocol is defined in /RFC 1510 <http://tools.ietf.org/html/rfc1510>/; the GSS-API is defined in /RFC 2743 <http://tools.ietf.org/html/rfc2743>/.


       Unicast Responses

Transmission of unicast response data in the net.jini.discovery.kerberos discovery format is similar to the net.jini.discovery.ssl format, except that the underlying connection is secured using the Kerberos Version 5 GSS-API Mechanism instead of TLS/SSL:

  1. The discovering entity establishes a GSS-API context for
     communicating with the lookup service, whose Kerberos principal it
     knows in advance. It then uses GSS-API tokens obtained from the
     established context to wrap all subsequent data sent to the lookup
     service (over the TCP connection on which the unicast request was
     originally transmitted). Also, all data subsequently received over
     the connection is unwrapped using the GSS-API.
  2. The discovering entity transmits a hash of all of the data it has
     sent and received over the connection so far. Multiple GSS-API
     tokens may be used to convey the hash. The hash is computed as
     specified in Section DJ.3.4.1
     
<http://www.jini.org/wiki/Jini_Discovery_and_Join_Specification#Unicast_Responses_2>

  3. The lookup service also computes a hash of all of the data it has
     received and sent over the connection so far. If this hash value
     matches that sent by the discovering entity, then the lookup
     service sends the unicast response data, encoded as specified in
     Section DJ.3.4.1
     
<http://www.jini.org/wiki/Jini_Discovery_and_Join_Specification#Unicast_Responses_2>,
     over the TCP connection to the discovering entity, using GSS-API
     tokens as described in step 1. Multiple GSS-API tokens may be used
     to convey the unicast response data.


Cheers,

Peter.

Reply via email to