I've got some thoughts about how to isolate a smart proxy, please tell
me yours:
1. Implement an InvocationHandler for java.lang.reflect.Proxy,
perhaps called SmartProxyIsolate
2. SmartProxyIsolate has it's own Thread with a custom
UncaughtExceptionHandler, it deals with any Errors the thread
might have, such as StackOverflowError. In the event of a
StackOverflowError, the smart proxy reference and thread reference
and method call queue, would be set to null immediately, leaving
the SmartProxyIsolate in a known state, all method in the isolate
would then return an IOException.
3. All method invocations are passed to the smart proxy contained by
the SmartProxyIsolate, only the SmartProxyIsolate holds a strong
reference to the smart proxy. The SmartProxyIsolate would expect
a MarshalledInstance of the proxy in it's constructor.
4. All methods on the smart proxy are only called from the
SmartProxyIsolate thread, trapping and handling any errors.
5. A java.lang.reflect.Proxy ( created with the SmartProxyIsolate
InvocationHandler ) is referenced by the client.
6. SmartProxyIsolate needs a method to return the ProtectionDomain,
to enable dynamic grants be granted to the smart proxy's PD by the
DynamicPolicy, rather than the PD of the SmartProxyIsolate.
7. To be completely sure, the Service API method returns shouldn't be
subclasses, unless the smart proxy is trusted, we could create a
new Permission class for this purpose. (lest a subclass be
returned containing a reference to the smart proxy).
This structure could be used to isolate any proxy that requires a
downloaded codebase, we would also need to place a limit on the size of
the downloaded codebase, to prevent DOS that way too (I'll explain later).
We might also provide a method that allows the calling class to set an
event handler, all method calls would return null immediately, and an
Event later generated containing the result of the method call. This
means the client thread isn't delayed in a wait state, but the default
behaviour would be for client threads to wait.
Note that this requires JAVA 5, since each thread must have it's own
UncaughtExceptionHandler that knows the specific SmartProxyIsolate.
The smart proxy would be isolated in it's own thread, any objects it
creates will be in a thread local stack, it would unlikely be able to
cause an OutOfMemoryError, unless the JVM is configured incorrectly.
By giving the thread a lower priority, it couldn't freeze a single core
client either.
Cheers,
Peter.
Peter Firmstone wrote:
I'm wondering, what can be done about DOS recursion with threads and
memory?
A Thread has it's own private memory stack, the downloaded proxy will
only have thread local objects, until they're passed to another thread.
What if we use a new thread to unmarshall and interact with the proxy?
A deliberate infinitely recursive loop in a Thread will throw a
StackOverflowError, it may also cause problems with the available
memory for all threads, so it needs to be tuned correctly with JVM
memory options.
>From java.lang.Thread (the discussion is interesting, not the
stackSize constructor), also note the UncaughtExceptionHandler method
that follows.
/**
* Allocates a new <code>Thread</code> object so that it has
* <code>target</code> as its run object, has the specified
* <code>name</code> as its name, belongs to the thread group
referred to
* by <code>group</code>, and has the specified <i>stack size</i>.
*
* <p>This constructor is identical to {...@link
* #Thread(ThreadGroup,Runnable,String)} with the exception of the
fact
* that it allows the thread stack size to be specified. The stack
size
* is the approximate number of bytes of address space that the
virtual
* machine is to allocate for this thread's stack. <b>The effect
of the
* <tt>stackSize</tt> parameter, if any, is highly platform
dependent.</b>
*
* <p>On some platforms, specifying a higher value for the
* <tt>stackSize</tt> parameter may allow a thread to achieve greater
* recursion depth before throwing a {...@link StackOverflowError}.
* Similarly, specifying a lower value may allow a greater number of
* threads to exist concurrently without throwing an {...@link
* OutOfMemoryError} (or other internal error). The details of
* the relationship between the value of the <tt>stackSize</tt>
parameter
* and the maximum recursion depth and concurrency level are
* platform-dependent. <b>On some platforms, the value of the
* <tt>stackSize</tt> parameter may have no effect whatsoever.</b>
*
* <p>The virtual machine is free to treat the <tt>stackSize</tt>
* parameter as a suggestion. If the specified value is
unreasonably low
* for the platform, the virtual machine may instead use some
* platform-specific minimum value; if the specified value is
unreasonably
* high, the virtual machine may instead use some platform-specific
* maximum. Likewise, the virtual machine is free to round the
specified
* value up or down as it sees fit (or to ignore it completely).
*
* <p>Specifying a value of zero for the <tt>stackSize</tt>
parameter will
* cause this constructor to behave exactly like the
* <tt>Thread(ThreadGroup, Runnable, String)</tt> constructor.
*
* <p><i>Due to the platform-dependent nature of the behavior of this
* constructor, extreme care should be exercised in its use.
* The thread stack size necessary to perform a given computation will
* likely vary from one JRE implementation to another. In light of
this
* variation, careful tuning of the stack size parameter may be
required,
* and the tuning may need to be repeated for each JRE
implementation on
* which an application is to run.</i>
*
* <p>Implementation note: Java platform implementers are
encouraged to
* document their implementation's behavior with respect to the
* <tt>stackSize parameter</tt>.
*
* @param group the thread group.
* @param target the object whose <code>run</code> method is
called.
* @param name the name of the new thread.
* @param stackSize the desired stack size for the new thread, or
* zero to indicate that this parameter is to be ignored.
* @exception SecurityException if the current thread cannot
create a
* thread in the specified thread group.
* @since 1.4
*/
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize)
/**
* Set the handler invoked when this thread abruptly terminates
* due to an uncaught exception.
* <p>A thread can take full control of how it responds to uncaught
* exceptions by having its uncaught exception handler explicitly set.
* If no such handler is set then the thread's <tt>ThreadGroup</tt>
* object acts as its handler.
* @param eh the object to use as this thread's uncaught exception
* handler. If <tt>null</tt> then this thread has no explicit handler.
* @throws SecurityException if the current thread is not allowed to
* modify this thread.
* @see #setDefaultUncaughtExceptionHandler
* @see ThreadGroup#uncaughtException
* @since 1.5
*/
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh)
If we catch the StackOverflowError, null out all references to the
proxy and terminate the thread, we free the memory, the trick is not
creating any new objects during recovery and making sure that the
Thread running out of local memory doesn't threaten the memory the
other threads have.
We can also set the proxy's unmarshalling thread with a low priority
on Single processor machines, so other higher priority threads get
some cpu time slices.
Once proxy unmarshalling is complete, it's still not safe to return
the proxy to the calling thread, the trouble is, the proxy's methods
could still contain infinitely recursive loops.
Any ideas for how to best invoke methods on the proxy, via the proxy
thread, originating from the application thread?
Cheers,
Peter.
Peter Firmstone wrote:
Perhaps Oracle might have the money to complete the MVM Isolates API
for Java SE and EE.
Isolates present the opportunity to restrict resources, which could
fix the issues with excess resource consumption as DOS.
Until then the remaining ways to combat the memory consumption problem.
Reboot, report and be careful who you trust.
N.B. They can't do this with a reflective proxy, so don't grant their
Principal DownloadPermission if you don't trust them.
Cheers,
Peter.
Christopher Dolan wrote:
Just for the record, Peter is right. I just looked at the JDK 1.5
source
code for java.lang.Thread. The security permission you need to create a
thread or a thread group is
SecurityConstants.MODIFY_THREADGROUP_PERMISSION, aka
java.lang.RuntimePermission("modifyThreadGroup"). I hadn't known about
that. The "modifyThread" permission does not affect one's ability to
create and start threads.
So, that would argue for a tightened security context during
deserialization. That would at least revert us to Peter's original
statement that only one core could be smacked with 100% load. The RAM
usage would not be controllable, though.
Chris
-----Original Message-----
From: Peter Firmstone [mailto:[email protected]] Sent: Thursday,
September 30, 2010 2:41 AM
To: [email protected]
Subject: Re: Towards Internet Jini Services (dos attacks)
Yes, it certainly can.
Regards,
Peter.
Christopher Dolan wrote:
private void readObject(ObjectInputStream in) {
new Runnable() {
public void run() {
while (true)
new Thread(this).start();
}
}.run();
}
At 1MB of stack RAM per thread, this will thrash most machines in no
time. Can a SecurityManager block thread creation?
Chris
-----Original Message-----
From: Peter Firmstone [mailto:[email protected]] Sent: Wednesday,
September 29, 2010 3:58 PM
To: [email protected]
Subject: Re: Towards Internet Jini Services (dos attacks)
Zoltan Juhasz wrote:
Sim,
I think the important danger in Jini is the use of objects. In simple
messaging communication (especially if non-binary), you don't have to
worry
about objects. In Jini, any method can take and object as a parameter
that
results in serialisation and unmarshalling at the receiver end. When
an
object has something nasty executing during within the readObject()
method,
it's too late to do anything.
This was a big problem in the days of single core, not as bad now.
Perhaps we need a software watchdog? Or an easy way to kill and
quarantine a misbehaving service? Or an unmarshalling executor thread
pool, which passes the object after it has been deserialized.