river-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Peter Firmstone <j...@zeus.net.au>
Subject Re: Learnings from a RevokeableDynamicPolicy & A Future Roadmap
Date Tue, 17 Aug 2010 10:58:09 GMT
An input stream delegate, might use buffering, first obtaining the data, 
writing to a buffered byte array, then do a permission check(), if the 
check fails close the underlying input stream and throw an IOException.

If the check passes, the bytes would be copied from the buffer to the 
passed in byte array and the number of bytes returned.

The delegates would have to perform at least one optimised 
ecm.checkPermission call per method.  Writes and reads with array's 
larger than the buffered array size would do more than one permission 
check, a large buffer array would be most optimal.

By using a buffer, the permission checks are called after any potential 
blocking.  If revocation has not been performed at the time of the 
permission check, it is unlikely that a following revocation could 
complete prior to copying the buffer.

Revocation itself as a process, is not instantaneous, however the caller 
on the revoke method needs to know that trust has been revoked 
completely, once this policy method has returned.

I've also since realised that the Output stream delegate below, would 
not necessarily stop a write from completing, if revocation occurs after 
the permission check, some bytes would be written to the output stream, 
the reaper would still close the output stream, so it appears to hold 
little benefit over the simpler implementation.

public void write( byte[] b) {
   ecm.begin(reaper)
   try{
      ecm.checkPermission(writePermission);
      out.write( byte[] b);
   } finally {
      end();
   }
}

In any case it appears difficult to prevent a remaining write from 
occurring once it has passed the security check.

It appears that ECM should be simpler and not track the current thread.

To protect method returns we can use:

Object method()
    try {
       // do something
       return obj;
    } finally {
       ecm.checkPermission(perm);
    }
}

To protect state:

void replace(Object o) {
    ecm.checkPermission(perm);
    this.o = o;
}

To protect functions

void doSomething()
    ecm.checkPermission(perm);
    // do something
}

To protect Object's themselves, we need security delegates.

It seems each case needs to be checked on merit.

Perhaps the best way to protect from data loss is to be proactive, 
rather than reactive, by limiting the duration a privilege is granted, 
rather than to revoke it when it has become a matter of urgency.  So how 
would we determine when a Permission is no longer needed?

Currently the Permission's code requires is found by trial and error, by 
running the code, to see what security exceptions are thrown, instead I 
think services should have an Entry with the Permission's they require, 
a client might decide to grant these Permission's or to limit them to a 
subset, or choose another service.

Since a Smart proxy may require more than one jar file, the permission's 
required should specify the permission's each require.  I figure the 
Entry's can contain PermissionGrant builders, allowing the grantor 
(client) to make the final decision.

Once a client is finished with a service, those permission's granted 
should be revoked immediately.

Thoughts?

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
>>>>     
>>
>>
>>
>
>


Mime
View raw message