You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@river.apache.org by Peter Firmstone <ji...@zeus.net.au> on 2011/02/05 11:35:42 UTC
The internet - Unicast Discovery Denial of Service Attack
The following class reads in the Unicast V2 discovery response from a
lookup service, the intent of this class is to isolate this part of
discovery on its own thread, to guard against an attackers carefully
crafted input stream, when attempting to discover a lookup service on
the internet.
The process of unmarshalling is isolated to a single thread, an
Exception handler captures any Errors and terminates the thread.
This protects the application thread from Errors, like StackOverflowError.
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.river.impl.security.dos;
import com.sun.jini.discovery.DiscoveryProtocolException;
import com.sun.jini.discovery.UnicastResponse;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.util.Collection;
import java.util.concurrent.Callable;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.io.MarshalledInstance;
class DiscoveryV2ReadUncastResponseTask implements
Callable<UnicastResponse> {
private final InputStream in;
private final ClassLoader defaultLoader;
private final boolean verifyCodebaseIntegrity;
private final ClassLoader verifierLoader;
private final Collection context;
DiscoveryV2ReadUncastResponseTask(InputStream in,
ClassLoader defaultLoader,
boolean verifyCodebaseIntegrity,
ClassLoader verifierLoader,
Collection context)
{
this.in = in;
this.defaultLoader = defaultLoader;
this.verifyCodebaseIntegrity = verifyCodebaseIntegrity;
this.verifierLoader = verifierLoader;
this.context = context;
}
public UnicastResponse call() throws Exception {
try {
DataInput din = new DataInputStream(in);
String host = din.readUTF();
// read LUS port
int port = din.readUnsignedShort();
String[] groups = new String[din.readInt()];
for (int i = 0; i < groups.length; i++) {
groups[i] = din.readUTF();
}
MarshalledInstance mi = (MarshalledInstance) new
ObjectInputStream(in).readObject();
ServiceRegistrar reg =
(ServiceRegistrar) mi.get(defaultLoader,
verifyCodebaseIntegrity, verifierLoader, context);
return new UnicastResponse(host, port, groups, reg);
} catch (RuntimeException e) {
throw new DiscoveryProtocolException(null, e);
}
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.river.impl.security.dos;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Performs Callable tasks in an isolated thread, which is terminated
* if any Errors are experienced. This may take some time to terminate
the thread
* as it is not forcibly stopped. The thread priority is minimal and a
daemon
* thread so it would take a number of unterminated threads to cause
problems.
*
* The caller can give up on the execution of the task by setting a timeout.
*
* @param T
* @author peter
*/
public class Isolate<T> {
private volatile ExecutorService isolateExecutor;
private volatile Throwable thrown;
public Isolate()
{
thrown = null;
isolateExecutor = Executors.newSingleThreadExecutor(new Factory(this));
}
/**
* Process Callable tasks in isolation.
* If an ExecutionException has been thrown, the task should be
abandoned.
* If an IsolationException has been thrown, an Error has occurred with
* the Isolate and a new object should be created.
*
* @param task
* @param timeout
* @param timeUnit
* @return
* @throws org.apache.river.impl.security.dos.Isolate.IsolationException
* @throws java.util.concurrent.ExecutionException
*/
public T process(Callable<T> task, long timeout, TimeUnit timeUnit)
throws
IsolationException, ExecutionException, InterruptedException,
TimeoutException {
if ( thrown != null){
throw new IsolationException ("Isolate has" +
"experienced an error during processing and " +
"cannot perform further " +
"tasks", thrown);
}
return isolateExecutor.submit(task).get(timeout, timeUnit);
}
private void terminate(Throwable e){
thrown = e;
isolateExecutor.shutdownNow();
}
private static class Factory implements ThreadFactory{
private static final ThreadGroup tg = new ThreadGroup("Isolated");
{
tg.setDaemon(true);
tg.setMaxPriority(4);
}
private final Isolate isolate;
Factory(Isolate uri){
isolate = uri;
}
public Thread newThread(Runnable r) {
Thread t = new Thread(tg, r);
t.setUncaughtExceptionHandler(new ExceptionHandler(isolate));
return t;
}
}
private static class ExceptionHandler implements
Thread.UncaughtExceptionHandler{
private final Isolate isolate;
ExceptionHandler(Isolate uri){
isolate = uri;
}
public void uncaughtException(Thread t, Throwable e) {
// For all other Exceptions we let the ExecutorService handle it.
if ( e instanceof Error){
// Do we want to take different actions based on the error?
isolate.terminate(e);
}
}
}
public static class IsolationException extends Exception {
private static final long serialVersionUID = 1L;
public IsolationException() {
super();
}
public IsolationException(String message) {
super(message);
}
public IsolationException(String message, Throwable cause) {
super(message, cause);
}
public IsolationException(Throwable cause) {
super(cause);
}
}
}
The internet - MarshalledInstance Denial of Service Attack
Posted by Peter Firmstone <ji...@zeus.net.au>.
This is a draft for MarshalledInstance, to prevent codebase download
DOS, it includes a reflective proxy verifier with MarshalledInstance to
verify the codebase annotation.
Please suggest improvements.
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.jini.io;
import com.sun.jini.proxy.BasicProxyTrustVerifier;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.ObjectStreamException;
import java.io.OutputStream;
import java.io.Serializable;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.ExportException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.constraint.BasicMethodConstraints;
import net.jini.core.constraint.Confidentiality;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.export.Exporter;
import net.jini.io.context.IntegrityEnforcement;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.ProxyTrustILFactory;
import net.jini.jeri.ssl.ConfidentialityStrength;
import net.jini.jeri.ssl.SslServerEndpoint;
import net.jini.security.BasicProxyPreparer;
import net.jini.security.ProxyPreparer;
import net.jini.security.ProxyPreparer;
import net.jini.security.TrustVerifier;
import net.jini.security.proxytrust.ServerProxyTrust;
import org.apache.river.impl.util.ConcurrentWeakMap;
/*
* Implementation note: This class uses the helper class
* MarshalledObject that is in this package. To avoid confusion
* with java.rmi.MarshalledObject the fully qualified class names
* are used for both classes.
*/
/**
* A <code>MarshalledInstance</code> contains an object in serialized
* form. The contained object can be deserialized on demand when
* explicitly requested. This allows an object to be sent from one VM
* to another in a way that allows the receiver to control when and if
* the object is deserialized.
* <p>
* The contained object is specified at construction time and can
* either be provided in unserialized or serialized form. If provided
* in unserialized form it will be serialized during construction
* with the serialization semantics defined by
* <code>MarshalOutputStream</code>. In particular, classes are annotated
* with a codebase URL from which the class can be loaded (if available).
* <p>
* If the <code>MarshalledInstance</code> needs to deserialize the
* contained object then the contained object will be deserialized with the
* deserialization semantics defined by <code>MarshalInputStream</code>.
* In particular, the codebase annotations associated with the contained
* object may be used to load classes referenced by the contained object.
* <p>
* <code>MarshalledInstance</code> provides functionality similar to
* <code>java.rmi.MarshalledObject</code>, but additionally provides
* for the verification of codebase integrity. Unlike
* <code>java.rmi.MarshalledObject</code>, it does not perform remote
* object-to-stub replacement.
*
* @author Sun Microsystems, Inc.
* @since 2.0
*/
public class MarshalledInstance implements Serializable {
/* Change this later to be read from a configuration.
*/
private static volatile boolean verifyState = true;
/* Hold references to a constructed MarshalledInstance until the object
* key is garbage collected. It may need to hold duplicates if
* more than one marshalled instance is created with different
annotations.
* MarshalledInstance references are typically thrown away soon after
* serialization, so this pool is here to retain a reference to
* the deserialization verifier, while the object it holds hasn't been
* garbage collected.
* DGC cannot be used since it would pose a Denial Of Service Risk.
*/
private static final ConcurrentMap<Object, Cup> pool = new
ConcurrentWeakMap<Object, Cup>();
/* Add an Object key, MarshalledInstance value to the pool if it
* doesn't already exist, if it does, return the value in the pool
* this will be used to avoid duplicating the exported MD.
* Duplicates may exist where the MarshalledInstance is equal but not
* fullyEqual. This prevents an explosion of MarshalledInstance
* objects when they are created often.
*/
private static MarshalledInstance addToPool(Object o,
MarshalledInstance i) {
Cup c = pool.get(o);
if (c == null) {
c = new Cup();
Cup existed = pool.putIfAbsent(o, c);
if (existed != null) {
c = existed;
}
}
MarshalledInstance existed = c.putIfAbsent(i);
return existed;
}
/**
* @serial Bytes of serialized representation. If
<code>objBytes</code> is
* <code>null</code> then the object marshalled was a <code>null</code>
* reference.
*/
private byte[] objBytes = null;
/**
* @serial Bytes of location annotations, which are ignored by
* <code>equals</code>. If <code>locBytes</code> is null, there were no
* non-<code>null</code> annotations during marshalling.
*/
private byte[] locBytes = null;
/**
* @serial Stored hash code of contained object.
*
* @see #hashCode
*/
private int hash;
/**
* @serial MarshalledInstance deserialization authentication and
* MessageDigest based verifier, may be null. The serialized form
* is a remote proxy. The creating JVM has the original remote
* object used for authentication and verification.
*/
/* I think the implementation needs a static weak map referencing the
* original object, that holds a reference to a MarshalledInstance to
* prevent unwanted garbage collection. When the original object goes
* away, so does the MarshalledInstance.
*/
private AuthCheckMDIntegrity authDeserializationMDIntegrityCheck = null;
static final long serialVersionUID = -5187033771082433496L;
/**
* Creates a new <code>MarshalledInstance</code> that contains the
* marshalled representation of the current state of the supplied
* object. The object is serialized with the semantics defined by
* <code>MarshalOutputStream</code>. The output stream used to
marshal the
* object implements {@link ObjectStreamContext} and returns an empty
* collection from its {@link ObjectStreamContext#getObjectStreamContext
* getObjectStreamContext} method.
*
* @param obj The Object to be contained in the new
* <code>MarshalledInstance</code>
* @throws IOException if the object cannot be serialized
*/
public MarshalledInstance(Object obj) throws IOException {
this(obj, Collections.EMPTY_SET);
}
/**
* Creates a new <code>MarshalledInstance</code> that contains the
* marshalled representation of the current state of the supplied
* object. The object is serialized with the semantics defined by
* <code>MarshalOutputStream</code>. The output stream used to
marshal the
* object implements {@link ObjectStreamContext} and returns the given
* collection from its {@link ObjectStreamContext#getObjectStreamContext
* getObjectStreamContext} method.
*
* @param obj The Object to be contained in the new
* <code>MarshalledInstance</code>
* @param context the collection of context information objects
* @throws IOException if the object cannot be serialized
* @throws NullPointerException if <code>context</code> is
<code>null</code>
*/
public MarshalledInstance(Object obj, Collection context)
throws IOException {
if (context == null) {
throw new NullPointerException();
}
if (obj == null) {
hash = 13; // null hash for java.rmi.MarshalledObject
return;
}
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ByteArrayOutputStream lout = new ByteArrayOutputStream();
MarshalledInstanceOutputStream out =
new MarshalledInstanceOutputStream(bout, lout, context);
out.writeObject(obj);
out.flush();
objBytes = bout.toByteArray();
// locBytes is null if no annotations
locBytes = (out.hadAnnotations() ? lout.toByteArray() : null);
// Calculate hash from the marshalled representation of object
// so the hashcode will be comparable when sent between VMs.
//
// Note: This calculation must match the calculation in
// java.rmi.MarshalledObject since we use this hash
// in the converted MarshalledObject. The reverse is
// also true in that we use the MarshalledObject's
// hash for our hash. (see the MarshalledInstance(
// MarshalledObject) constructor)
//
int h = 0;
for (int i = 0; i < objBytes.length; i++) {
h = 31 * h + objBytes[i];
}
hash = h;
createDeserializationIntegrityCheck();
MarshalledInstance exists = addToPool(obj, this);
if (exists != null) {
// we just created a duplicate marshalled instance best we share
// the deserialization check so we don't have to retain too many
// unnecessary strong references.
authDeserializationMDIntegrityCheck =
exists.authDeserializationMDIntegrityCheck;
}
}
private void createDeserializationIntegrityCheck() {
// Should we bomb out, or just leave it without a verifier?
try {
authDeserializationMDIntegrityCheck = new
IntegrityCheck(objBytes, locBytes, hash, "SHA");
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(MarshalledInstance.class.getName()).log(Level.SEVERE,
null, ex);
} catch (ExportException ex) {
Logger.getLogger(MarshalledInstance.class.getName()).log(Level.SEVERE,
null, ex);
}
}
/**
* Creates a new <code>MarshalledInstance</code> from an
* existing <code>MarshalledObject</code>. An object equivalent
* to the object contained in the passed <code>MarshalledObject</code>
* will be contained in the new <code>MarshalledInstance</code>.
* <p>
* The object contained in the passed <code>MarshalledObject</code>
* will not be unmarshalled as part of this call.
*
* @param mo The <code>MarshalledObject</code> that contains
* the object the new <code>MarshalledInstance</code> should
* contain
* @throws NullPointerException if <code>mo</code> is <code>null</code>
*/
public MarshalledInstance(java.rmi.MarshalledObject mo) {
if (mo == null) {
throw new NullPointerException();
}
// To extract the java.rmi.MarshalledObject's fields we
// convert the mo into a net.jini.io.MarshalledObject.
// (See resolveClass() in FromMOInputStream) The private
// version of MarshalledObject allows access to the needed
// fields.
//
net.jini.io.MarshalledObject privateMO = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(mo);
oos.flush();
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new FromMOInputStream(bais);
privateMO =
(net.jini.io.MarshalledObject) ois.readObject();
} catch (IOException ioe) {
throw new AssertionError(ioe);
} catch (ClassNotFoundException cnfe) {
throw new AssertionError(cnfe);
}
objBytes = privateMO.objBytes;
locBytes = privateMO.locBytes;
hash = privateMO.hash;
// We shouldn't create a verifier proxy for MarshalledInstance
// created from a MarshalledObject, since the actual object may
// not be local, how can we vouch for it if we don't have the
// original object?
// createDeserializationIntegrityCheck();
}
/**
* Creates a new <code>MarshalledObject</code> that will
* contain an object equivalent to the object contained
* in this <code>MarshalledInstance</code> object.
* <p>
* The object contained in this <code>MarshalledInstance</code>
* object will not be unmarshalled as part of this call.
* @return A new <code>MarshalledObject</code> which
* contains an object equivalent to the object
* contained in this <code>MarshalledInstance</code>
*/
public java.rmi.MarshalledObject convertToMarshalledObject() {
// To create a java.rmi.MarshalledObject with previously
// serialized data we first create a private
// net.jini.io.MarshalledObject with the
// data and then convert it to the final object by changing
// the class during readObject(). (See resolveClass() in
// ToMOInputStream)
//
net.jini.io.MarshalledObject privateMO =
new net.jini.io.MarshalledObject();
privateMO.objBytes = objBytes;
privateMO.locBytes = locBytes;
privateMO.hash = hash;
java.rmi.MarshalledObject mo = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(privateMO);
oos.flush();
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ToMOInputStream(bais);
mo = (java.rmi.MarshalledObject) ois.readObject();
} catch (IOException ioe) {
throw new AssertionError(ioe);
} catch (ClassNotFoundException cnfe) {
throw new AssertionError(cnfe);
}
return mo;
}
/**
* Returns a new copy of the contained object. Deserialization is
* performed with the semantics defined by
<code>MarshalInputStream</code>.
* The input stream used to unmarshal the object implements {@link
* ObjectStreamContext} and returns a collection from its {@link
* ObjectStreamContext#getObjectStreamContext getObjectStreamContext}
* method which contains a single element of type {@link
* IntegrityEnforcement}; the {@link
IntegrityEnforcement#integrityEnforced
* integrityEnforced} method of this element returns the specified
* <code>verifyCodebaseIntegrity</code> value.
* <p>MarshalledInstance</code> implements this method by calling
* <code>{@link #get(ClassLoader, boolean, ClassLoader, Collection)
* get}(null, verifyCodebaseIntegrity, null, null)</code>.
*
* @param verifyCodebaseIntegrity if <code>true</code> then
* codebase integrity is verified, otherwise code base
* integrity is not verified
* @return a new copy of the contained object
* @throws IOException if an
* <code>IOException</code> occurs while deserializing the
* object from its internal representation
* @throws ClassNotFoundException if any classes necessary
* for reconstructing the contained object can not
* be found or if <code>verifyCodebaseIntegrity</code>
* is <code>true</code> and the integrity of the
* contained object's codebase cannot be confirmed
*/
public Object get(final boolean verifyCodebaseIntegrity)
throws IOException, ClassNotFoundException {
return get(null, verifyCodebaseIntegrity, null, null);
}
/**
* Returns a new copy of the contained object. Deserialization is
* performed with the semantics defined by
<code>MarshalInputStream</code>.
* If <code>context</code> is not <code>null</code>
* the input stream used to unmarshal the object implements {@link
* ObjectStreamContext} and returns the given collection from its {@link
* ObjectStreamContext#getObjectStreamContext getObjectStreamContext}
* method.
* <p>If <code>context</code> is <code>null</code>
* the input stream used to unmarshal the object implements {@link
* ObjectStreamContext} and returns a collection from its {@link
* ObjectStreamContext#getObjectStreamContext getObjectStreamContext}
* method which contains a single element of type {@link
* IntegrityEnforcement}; the {@link
IntegrityEnforcement#integrityEnforced
* integrityEnforced} method of this element returns the specified
* <code>verifyCodebaseIntegrity</code> value.
*
* @param defaultLoader the class loader value (possibly
* <code>null</code>) to pass as the <code>defaultLoader</code>
* argument to <code>RMIClassLoader</code> methods
* @param verifyCodebaseIntegrity if <code>true</code> then
* codebase integrity is verified, otherwise code base
* integrity is not verified
* @param verifierLoader the class loader value (possibly
* <code>null</code>) to pass to {@link
* net.jini.security.Security#verifyCodebaseIntegrity
* Security.verifyCodebaseIntegrity}, if
* <code>verifyCodebaseIntegrity</code> is <code>true</code>
* @param context the collection of context information objects or
* <code>null</code>
* @return a new copy of the contained object
* @throws IOException if an
* <code>IOException</code> occurs while deserializing the
* object from its internal representation
* @throws ClassNotFoundException if any classes necessary
* for reconstructing the contained object can not
* be found or if <code>verifyCodebaseIntegrity</code>
* is <code>true</code> and the integrity of the
* contained object's codebase cannot be confirmed
*/
public Object get(ClassLoader defaultLoader,
final boolean verifyCodebaseIntegrity,
ClassLoader verifierLoader,
Collection context)
throws IOException, ClassNotFoundException {
if (objBytes == null) // must have been a null object
{
return null;
}
if (context == null) {
context = Collections.singleton(
new IntegrityEnforcement() {
public boolean integrityEnforced() {
return verifyCodebaseIntegrity;
}
});
}
ByteArrayInputStream bin = new ByteArrayInputStream(objBytes);
// locBytes is null if no annotations
ByteArrayInputStream lin =
(locBytes == null ? null : new
ByteArrayInputStream(locBytes));
MarshalledInstanceInputStream in =
new MarshalledInstanceInputStream(bin, lin,
defaultLoader,
verifyCodebaseIntegrity,
verifierLoader,
context);
in.useCodebaseAnnotations();
Object obj = in.readObject();
in.close();
return obj;
}
/**
* Compares this <code>MarshalledInstance</code> to another
* object. Returns true if and only if the argument refers to an
instance
* of <code>MarshalledInstance</code> that contains exactly the same
* serialized form for its contained object as this object does and
* has the same class codebase annotations.
*
* @param obj the object to compare with this
* <code>MarshalledInstance</code>
* @return <code>true</code> if the argument contains an object
* with an equivalent serialized form and codebase;
* otherwise returns <code>false</code>
*/
public boolean fullyEquals(Object obj) {
if (equals(obj)) {
MarshalledInstance other = (MarshalledInstance) obj;
return Arrays.equals(locBytes, other.locBytes);
}
return false;
}
/**
* Compares this <code>MarshalledInstance</code> to another
* object. Returns true if and only if the argument refers to an
instance
* of <code>MarshalledInstance</code> that contains exactly the same
* serialized form for its contained object as this object does. The
* comparison ignores any class codebase annotations, so that
* two objects can be equivalent if they have the same serialized
* representation, except for the codebase of each class in the
* serialized representation.
* @param obj the object to compare with this
* <code>MarshalledInstance</code>
* @return <code>true</code> if the argument contains an object
* with an equivalent serialized form; otherwise returns
* <code>false</code>
*/
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof MarshalledInstance) {
MarshalledInstance other = (MarshalledInstance) obj;
if (hash != other.hash) {
return false;
}
return Arrays.equals(objBytes, other.objBytes);
}
return false;
}
/**
* Returns the hash code for this <code>MarshalledInstance</code>.
* The hash code is calculated only from the serialized form
* of the contained object.
* @return The hash code for this object
*/
public int hashCode() {
return hash;
}
/**
* Verify the case of null contained object.
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
// This allows backward compatible evolution of serialized form.
in.defaultReadObject();
// If contained object is null, then hash and locBytes must be
// proper
//
if ((objBytes == null) && ((hash != 13) || (locBytes != null))) {
throw new InvalidObjectException("Bad hash or annotation");
}
// Defensive copy mutable arrays.
objBytes = objBytes.clone();
locBytes = locBytes.clone();
// During deserialization we verify the MarshalledInstance using
// the proxy, for now until we get some proper tests up and
// running, I'm not going to require authentication.
// Later this will require authentication and a server
// minimum principal.
if (authDeserializationMDIntegrityCheck == null && verifyState
== true) {
throw new IOException("MarshalledInstance deserialization
aborted" +
"unable to verify state, uable to " +
"authenticate or prevent codebase download denial of " +
"service, missing deserialization integrity check
proxy");
}
if (authDeserializationMDIntegrityCheck != null) {
ProxyPreparer preparer = new BasicProxyPreparer();
// Don't overwrite proxy reference we might want to
serialize it again.
AuthCheckMDIntegrity proxy = (AuthCheckMDIntegrity)
preparer.prepareProxy(authDeserializationMDIntegrityCheck);
// Verify state has been transferred successfully.
String algorithm = proxy.getAlgorithm();
MessageDigest md = null;
try {
md = MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException ex) {
if (verifyState == true) {
throw new IOException("Unable to verify state", ex);
} else {
Logger.getLogger(MarshalledInstance.class.getName()).log(
Level.SEVERE, null, ex);
}
}
if (md != null) {
md.update(objBytes);
byte[] objMD = md.digest();
md.reset();
md.update(locBytes);
byte[] locMD = md.digest();
if (Arrays.equals(proxy.getLocMD(), locMD) &&
Arrays.equals(proxy.getObjMD(), objMD) && hash == proxy.getHashCode()) {
// It would now be safe, if we have authenticated
the proxy
// with the minimum server principal and confirmed the
// Codebase annotation is correct, to grant
DownloadPermission.
// WE COULD ASSUME THAT DownloadPermission MUST BE
GRANTED
// IF DESERIALIZATION HAS BEEN SUCCESSFUL.
// Granting DownloadPermission prevents a trusted
service
// from returning another proxy that we don't trust, and
// having it's codebase downloaded automatically.
return;
} else {
throw new IOException("MarshalledInstance serial
form is corrupt");
}
}
}
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
}
/**
* Protect against missing superclass.
*/
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("Bad class hierarchy");
}
/**
* This class is used to marshal objects for
* <code>MarshalledInstance</code>. It places the location annotations
* to one side so that two <code>MarshalledInstance</code>s can be
* compared for equality if they differ only in location
* annotations. Objects written using this stream should be read back
* from a <code>MarshalledInstanceInputStream</code>.
*
* @see MarshalledInstanceInputStream
*/
private static class MarshalledInstanceOutputStream
extends MarshalOutputStream {
/** The stream on which location objects are written. */
private ObjectOutputStream locOut;
/** <code>true</code> if non-<code>null</code> annotations are
* written.
*/
private boolean hadAnnotations;
/**
* Creates a new <code>MarshalledObjectOutputStream</code> whose
* non-location bytes will be written to <code>objOut</code> and
whose
* location annotations (if any) will be written to
* <code>locOut</code>.
*/
public MarshalledInstanceOutputStream(OutputStream objOut,
OutputStream locOut,
Collection context)
throws IOException {
super(objOut, context);
this.locOut = new ObjectOutputStream(locOut);
hadAnnotations = false;
}
/**
* Returns <code>true</code> if any non-<code>null</code> location
* annotations have been written to this stream.
*/
public boolean hadAnnotations() {
return hadAnnotations;
}
/**
* Overrides <code>MarshalOutputStream.writeAnnotation</code>
* implementation to write annotations to the location stream.
*/
protected void writeAnnotation(String loc) throws IOException {
hadAnnotations |= (loc != null);
locOut.writeObject(loc);
}
public void flush() throws IOException {
super.flush();
locOut.flush();
}
}
/**
* The counterpart to <code>MarshalledInstanceOutputStream</code>.
*
* @see MarshalledInstanceOutputStream
*/
private static class MarshalledInstanceInputStream
extends MarshalInputStream {
/**
* The stream from which annotations will be read. If this is
* <code>null</code>, then all annotations were <code>null</code>.
*/
private ObjectInputStream locIn;
/**
* Creates a new <code>MarshalledObjectInputStream</code> that
* reads its objects from <code>objIn</code> and annotations
* from <code>locIn</code>. If <code>locIn</code> is
* <code>null</code>, then all annotations will be
* <code>null</code>.
*/
MarshalledInstanceInputStream(InputStream objIn,
InputStream locIn,
ClassLoader defaultLoader,
boolean verifyCodebaseIntegrity,
ClassLoader verifierLoader,
Collection context)
throws IOException {
super(objIn,
defaultLoader,
verifyCodebaseIntegrity,
verifierLoader,
context);
this.locIn = (locIn == null ? null : new
ObjectInputStream(locIn));
}
/**
* Overrides <code>MarshalInputStream.readAnnotation</code> to
* return locations from the stream we were given, or
<code>null</code>
* if we were given a <code>null</code> location stream.
*/
protected String readAnnotation()
throws IOException, ClassNotFoundException {
return (locIn == null ? null : (String) locIn.readObject());
}
}
/**
* Input stream to convert <code>java.rmi.MarshalledObject</code>
* into <code>net.jini.io.MarshalledObject</code>.
*/
private static class FromMOInputStream extends ObjectInputStream {
public FromMOInputStream(InputStream in) throws IOException {
super(in);
}
/**
* Overrides <code>ObjectInputStream.resolveClass</code> to change
* an occurence of class <code>java.rmi.MarshalledObject</code> to
* class <code>net.jini.io.MarshalledObject</code>.
*/
protected Class resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
if (desc.getName().equals("java.rmi.MarshalledObject")) {
return net.jini.io.MarshalledObject.class;
}
return super.resolveClass(desc);
}
}
/**
* Input stream to convert
* <code>net.jini.io.MarshalledObject</code> into
* <code>java.rmi.MarshalledObject</code>.
*/
private static class ToMOInputStream extends ObjectInputStream {
public ToMOInputStream(InputStream in) throws IOException {
super(in);
}
/**
* Overrides <code>ObjectInputStream.resolveClass</code>
* to change an occurence of class
* <code>net.jini.io.MarshalledObject</code>
* to class <code>java.rmi.MarshalledObject</code>.
*/
protected Class resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
if (desc.getName().equals("net.jini.io.MarshalledObject")) {
return java.rmi.MarshalledObject.class;
}
return super.resolveClass(desc);
}
}
interface AuthCheckMDIntegrity {
byte[] getLocMD() throws IOException;
byte[] getObjMD() throws IOException;
String getAlgorithm() throws IOException;
int getHashCode() throws IOException;
}
private static class IntegrityCheck implements AuthCheckMDIntegrity,
ServerProxyTrust, Remote, Serializable {
private static final long serialVersionUID = 1L;
private final byte[] objMD;
private final byte[] locMD;
private final int hash;
private final String algorithm;
private final Object proxy;
IntegrityCheck(byte[] object, byte[] annotation, int hashCode,
String algorithm)
throws NoSuchAlgorithmException, ExportException {
hash = hashCode;
MessageDigest md = MessageDigest.getInstance(algorithm);
md.update(object);
objMD = md.digest();
md.reset();
md.update(annotation);
locMD = md.digest();
this.algorithm = algorithm;
/* We could get the exporter from a configuration, but that
would
* risk inadequate Confidentiality, worse, it may keep the vm
* alive while the exporter remains exported, or worse still the
* server may be subject to DOS attacks if DGC is enabled,
* which is what we're trying to avoid in the first place,
* since the proxy is used to allow the client to Authenticate
* the server to prevent DOS attacks in the client, the fix
would be
* worse than the original bug.
*
* I'll add a configuration item later to allow for different
* secure ServerEndpoint's
*
* This exported object only remains exported while a strong
* reference is kept to the enclosing MarshalledInstance. Since
* it is the registrar proxy (reggie) that creates a
* MarshalledInstance, a reference would need to be
maintained in the
* client.
*/
Exporter exporter =
new BasicJeriExporter(
SslServerEndpoint.getInstance(0),
new ProxyTrustILFactory(
new BasicMethodConstraints(
new InvocationConstraints(
new InvocationConstraint[]{
Confidentiality.YES,
ConfidentialityStrength.STRONG
}, null) //End InvocationConstraints constructor
)//End BasicMethodConstraints constructor
, null)//End ProxyTrustILFactory constructor
, false, false);//End BasiceJeriExporter constructor
proxy = exporter.export(this);
}
public TrustVerifier getProxyVerifier() throws RemoteException {
return new BasicProxyTrustVerifier(proxy);
//return new ProxyTrustVerifier();
}
public byte[] getLocMD() throws RemoteException {
return locMD;
}
public byte[] getObjMD() throws RemoteException {
return objMD;
}
public String getAlgorithm() throws RemoteException {
return algorithm;
}
public int getHashCode() throws IOException {
return hash;
}
private Object writeReplace() {
return proxy;
}
// Prevent an attacker fabricating an instance.
private void readObject(ObjectInputStream in)
throws InvalidObjectException {
throw new InvalidObjectException("Proxy Required");
}
}
/* Stores unique marshalled Instance references, as defined by
fullyEquals()
* duplicates are only added if they are not fullyEqual.
*/
private static class Cup {
List<MarshalledInstance> items = new
ArrayList<MarshalledInstance>(2);
MarshalledInstance putIfAbsent(MarshalledInstance t) {
synchronized (items) {
Iterator<MarshalledInstance> it = items.iterator();
while (it.hasNext()) {
MarshalledInstance i = it.next();
if (t.fullyEquals(i)) {
return i;
}
}
items.add(t);
return null;
}
}
}
}