A note on ProtectionDomain's,
There can be only one! ProtectionDomain's are only ever compared with
==, they don't override equals() or hashCode(). ProtectionDomain's are
immutable in respect to the ClassLoader, CodeSource and Principals which
they encapsulate. They may be made immutable in respect of Permissions
at construction time, however when dynamic, they consult the policy.
JAAS, uses Subject's to represents users, roles, etc, the
SubjectDomainCombiner replaces the ProtectionDomains on the stack, with
new ProtectionDomains, merging the Principals of the executing Subject,
with those of the existing ProtectionDomain.
The missing equals() and hashCode() methods in ProtectionDomain, means
that caching of results for AccessControlContext's, containing
ProtectionDomain's created by SubjectDomainCombiner's even with
identical Principal[]'s, will not be equal and thus, the result cannot
be optimised much, even though the originating ProtectionDomain's and
Subject's may be identical.
jini.net.security.Security
It's used for most of the existing Jini security decisions, similar to
AccessController, so it's a logical place to put an optimised
checkPermissions(Collection<Permission>) call, that obtains and calls
the ECM from the RevokeableDynamicPolicy, if in force, it also provides
the opportunity to remove the ExecutionContextManager interface from the
public API if necessary.
By batching the Permission's together we only call
AccessController.getContext() once, this is useful for PermissionGrant
processing where we have to batch check GrantPermission's
the new checkPermissions(Collection<Permission>) call will execute
faster than AccessController.checkPermission(Permission) and provide a
performance boost to any existing security check, not just those
proposed so far.
Then we can get down to business, testing, will update SVN shortly so
you can all review the code.
N.B. I decided NOT to implement an Event for Security Delegates to be
advised of permission revocation, since a delegate
cannot know the execution context without checking it, it must alway
check EVERY method invocation, the ECM has been designed to skip as much
as possible and return quickly, provided the execution context hasn't
changed or permission revoked, since last check. I've added the method
checkPermission(Collection<Permission>) to net.jini.security.Security
along with some preliminary documentation.
Cheers,
Peter.
Here's an interesting security Bug:
*Bug ID:* 4857197 *
Votes* 0 *
Synopsis* switch back from Security.doPrivileged to
AccessController.doPrivileged
*Category* jini:other *
Reported Against* 2.0beta
*Release Fixed* 2.0
*State* 10-Fix Delivered, request for enhancement
*Priority:* 3-Medium *
Related Bugs* *Submit Date* 01-MAY-2003 *
Description*
Security.doPrivileged is very expensive. Much of our code was converted to use it from a purity of
security model perspective, but the performance hit from doing that appears to be unacceptable.
For at least performance-critical components, if preserving the Subject isn't semantically significant
(most particularly, there is no call out to user code), we should switch back to using
AccessController.doPrivileged. As such, security policy files will have to live with granting
various permissions to codesources rather than codesource+principals.
*Work Around*
N/A
*Evaluation*
Yep.
Peter Firmstone wrote:
Performance costs of ECM access checks?
How much will delegates impact performance in comparison to existing
SocketPermission guards?
Some interesting links:
www.rewerse.net/pulications/download/REWERSE-RP-2005-141.pdf
www.ida.liu.se/~almhe
There's some interesting performance tests available for the above
paper at:
http://www.ida.liu.se/~almhe/publications/sm-testdata/
I suspect we'll be able to achieve respectable performance figures,
here are some tips in the paper we can learn from:
1. Existing SocketPermission's try to perform a DNS-lookup. Bug ID's
related to SocketPermission problems 4155463, 4320895, 4414825,
4975882, 5004073
2. AccessController.getStackAccessControlContext was identified as an
expensive native method, can't avoid that one!
3. Performance worsens with increasing numbers of CodeSources.
Some initial observations:
In comparison to the existing path for security checks, we can cache
the result of the AccessControlContext.checkPermission(Permission)
calls, until a revocation occurs, removing multiple calls to
ProtectionDomain's contained in the AccessControlContext.
Each ProtectionDomain.implies(Permission) check is typically followed
by Policy.getPolicyNoCheck, PolicyFile.implies(ProtectionDomain,
Permission), SynchronizedMap.get(WeakHashMap.get(ProtectionDomain)),
Permissions.implies(Permission), Permissions.getPermissionCollection,
HashMap.get(Class<Permission>), PermissionCollection.implies(Permission).
Synchronization creates significant points of contention, although
that has recently been solved with DynamicConcurrentPolicyProvider,
however we still need to performance test it.
AccessControlContext has reasonable equals() and hashCode()
implementations, it will perform well in concurrent cache
collections. Typically Permission equals and hashCode implementations
are also reasonable. The first call from an AccessControlContext will
be the most expensive, however in between revocations, repeated calls
will be cheap, apart from the native call getStackAccessControlContext.
I don't want to be calling DNS!
Cheers,
Peter.
Peter Firmstone wrote:
Clarification (I overlooked something): The
checkPermission(Collection<Permission> perms) in the finally block
would work only for intercepting a method return, but it doesn't work
for InputStream read() / OutputStream write(), since we wanted to
stop method execution, if Permission is no longer granted.
For example if a socket delegate returns an OutputStream delegate,
that wraps an underlying OutputStream, then during the write(), if a
revocation occurs, we probably want to close the OutputStream, if it
applies.
The OutputStream write() would still need a reaper, to close the
OutputStream causing an IOException to be thrown, although as yet I
haven't determined how to add debug information for an intervening
revocation.
public void write( byte[] b) {
ecm.begin(reaper)
try{
ecm.checkPermission(writePermission);
out.write( byte[] b);
} finally {
end();
}
}
Is it as critical to stop a read() operation?
Since the array passed into the read() operation is written to, we
can close this as well, causing an IOException, however the user will
still get some bytes written into the byte array, which it can access
if it catches the IOException.
public int read(byte[] b) {
ecm.begin(reaper)
try{
ecm.checkPermission(readPermission);
return in.read( byte[] b);
} finally {
end();
}
}
But we could provide the optimised single invocation
checkPermission(Collection<Permission> perms) method as well, for
calls that don't need to be intercepted.
Perhaps we could be less pedantic about read operations, provided all
write operations are intercepted?
public int read(byte[] b) {
try {
ecm.checkPermission(readPermission);
} catch (AccessControlException e) {
this.close();
throw new IOException(e.toString());
}
return in.read( byte[] b);
}
This checkPermission would be very fast, with only a small amount of
cpu overhead, because it caches the result for the AccessControlContext.
We could do this for write() as well, if the code was trusted at the
time of the write() call, then we might assume that the information
contained within, wasn't borne from an operation of malicious intent,
we trusted it, so the call can be allowed to complete. This has a
much smaller performance penalty, any following calls will cause an
AccessControlExeption to be thrown, if Permission is revoked.
Then write might simply be:
public void write(byte[] b) {
try {
ecm.checkPermission(writePermission);
} catch (AccessControlException e) {
this.close();
throw new IOException(e.toString());
}
out.write(b);
}
We might consider what the uses are for Permission revocation? I'm
starting to think interception might hamper performance too greatly.
The major uses I see for Revocation:
* Reusing ClassLoader's, for proxy's with common code source, by
removing trust gained by the previous proxy.
* Limit the time period for which trust is granted to a third
party. A Trust lease? Trust is only granted while contact is
maintained.
* If a proxy has lost contact with it's Server, which was trusted
and we want to resume (without loosing state) we can revoke
permission, retry connecting with the Server, verify trust, then
re grant trust (I'm thinking about the neuromancer remote
reference problem).
Obviously the feature got your interest, I'm wondering what other
uses there might have been for permission revocation? This might
help me better understand the use case scenarios.
Peter Firmstone wrote:
Fred Oliver wrote:
To at least partially answer my own question, I think that the answer
is that the socket can't be closed (at least in all cases).
If the delegate for a resource has multiple users (codebases,
principals, etc.) then closing the resource because permission for one
of the users has been revoked denies access to the resource by the
remaining valid users. (This allows a denial of service attack.) And
then, if you can't close the resource (socket), you can't kick loose
the no longer trusted thread blocked reading from it. You may be able
to determine the special case of no user retaining access (everyone's
access was revoked) and can then close the resource.
In the standard security model, there are no checks for
reading/writing sockets. Allowing revocation means that checks must be
added for each individual read or write call on the socket streams. Is
that level of performance satisfactory?
I've just spotted a possible performance improvement.
We can eliminate the end() call in the finally block, put all the
checkPermission calls there instead. This substantially reduces the
caching operations. Perhaps we can manage the Socket some other
way, we just block unauthorised access using the delegate?
try {
// Do some processing
return something;
} finally {
ECM.checkPermission(Collection<Permission> permissions);
}
The current expense for making a Permission call via ECM
(remembering the first is always going to be expensive), currently
requires the three ECM calls in succession:
begin();
checkPermission(perm);
end();
begin():
1. Associate the thread with the reaper.
checkPermission(perm):
1. Obtain the read lock;
2. Add the current thread to the execution cache.
3. Associate the current AccessControlContext with the current thread
4. Check if the AccessControlContext has been checked for this
Permission previously.
5. If it has been checked before, return, else do
AccessController.checkPermission(perm), cache the result if it
succeeds.
Each end():
1. Obtain the read lock;
2. Remove the thread and reaper from the cache.
3. Remove the thread association with the AccessControlContext.
4. Remove the thread from the execution cache.
This could be simplified to (no reaper or thread caching):
checkPermission(perms)
1. Obtain the read lock;
2. For each Permission, check if the current AccessControlContext has
been checked for this Permission previously, if so return.
(HashMap lookup - fast).
3. If the AccessControlContext hasn't been checked previously, do
AccessController.checkPermission(perm), cache the result if it
succeeds, then return.
So for performance reasons, it looks like we need to be considering
some other way to manage the Socket, the ECM would serve us better
if we kept it simple?
I think your earlier suggestion of an event notification might be
more useful, if provided as a parameter in the checkPermission call,
in the event of failure, an event would be generated, the delegate
could then decide what action to take. If it only had one user,
then it knows to close the Socket, if it has multiple users, it
might just keep score.
This stops the transmission of data from unauthorised code, but it
doesn't close the socket.
Cheers,
Peter.
If there is untrusted code on a thread's stack, and if the thread
called a delegate, and if the delegate is in the between ECM.begin()
and ECM.end(), then the delegate's reaper will be called, and it can
close the socket.
If the revocation happened elsewhere (e.g. the untrusted code is not
on any stack, or is occupied with some other task), how does the
socket get closed?
Fred