Dan Creswell wrote:
On 9 January 2012 10:03, Peter Firmstone <[email protected]> wrote:
The really odd part is, I've removed the policy caches, instead of slowing
down like I expected, the policies run faster, I've stripped the cache from
DynamicPolicyProvider too, making it far simpler, will commit soon.
The array of PermissionGrant's are much smaller than the cache was, a
PermissionGrant can apply to multiple ProtectionDomains, while the
original cache was a Map containing ProtectionDomains as keys, and
java.security.Permissions as values, each Permission was contained in a
PermissionCollection, referenced by another Map, containing the
Permission classes as keys.
It must have been done like that since the original CodeSource.implies
would have been slow, it required file and network access (File, URL &
SocketPermission), while URIGrant (implements PermissionGrant) uses URI
and reimplements equivalent semantics to CodeSource.implies, but doesn't
ask the DNS server, a spoofed DNS server in this case would be more
dangerous, than it is with SocketPermission, eg: grant AllPermission by
matching a privileged CodeSource URL, with an IP address.
The cache still had a lot of processing to do, with
PermissionCollection.implies calls and blocking while in use.
Its also possible to grant the same set of Permissions in java policy
files (an undocumented feature I had to implement when a number of the
qa tests failed):
grant codebase "file:${{some.urls}}" {
permission java.security.AllPermission "", "";
};
Where the Property might be:
"some.urls" =>
"${river.home}/lib/jsk-platform.jar:${river.home}/lib/start.jar"
Caches are not free exacting a price against the garbage collector,
reducing memory for other operations plus CPU cycles just to maintain
them. So like much else they bring a price and if that outweighs the
benefits you'll see a speed-up when you remove them.
That sounds right, that large cache would need to find it's way to the
permgen heap, wouldn't fit in cache etc. Instead of caching
Permissions (PermissionCollection) it's now generated on demand, but
only containing Permission objects that are instances of the checked
Permission and UnresolvedPermissions, (except for GrantPermission, in
which case all Permission objects are added for Umbrella grants). The
Permission objects are also sorted prior to being added to Permissions,
wild card permissions are added last, so they get checked first.
PermissionGrant.isPrivileged() returns true if the grant contains
AllPermission, so in that case the Permission's don't even need to be
retrieved.
I would be interested in understanding just how big that cache needs
to be or how often it needs to be consulted for it to bring advantage.
It could be it's only beneficial for e.g. App Servers that have
ridiculous amounts of security load because of the way they're
designed (and the associated assumptions).
I struggled with creating ConcurrentPermissions to replace
java.security.Permissions, there's just too much blocking in all the
synchronized PermissionCollection's, it looks like implementing a cache
was the wrong decision now. Instead, I just minimise the size of
Permissions created, so the internal Map's and List stay small, then
discarded it after use (should fit in the L2 cache, never written to ram).
But then my system has 4 sockets (not multi core), so shared state and
cache misses are expensive.
DelegateCombinerSecurityManager has a cache, however it only caches
results for each AccessControlContext (not as fine grained as the
policy) and uses a flat ConcurrentMap, that references
ConcurrentSkipListSet's containing Permission objects, identified using
PermissionComparator (appended).
/*
* 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.security;
import java.io.Serializable;
import java.security.Permission;
import java.security.UnresolvedPermission;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.Comparator;
/**
* A Comparator for Permission that avoids using equals and hashCode() on
* Permission implementations.
*
* This comparator orders the Permission first by Class, then Name, followed
* by Actions.
*
* Class is sorted by class hashcode.
*
* Name is sorted using Unicode character order, so wildcards "*" and
* characters will preceed numbers, which will preceed letters.
*
* The comparator must be as fast as possible, the common case is not equal,
* so that must be very fast.
*
* Note that for SocketPermissionCollection that the desired order to
add to
* SocketPermission's is in , with the most likely permissions added last.
*
* HINT: Use a NavigableMap to return a reverse order iterator for
* PermissionCollection's like SocketPermissionCollection and
* FilePermissionCollection.
*
* @author Peter Firmstone.
*/
public class PermissionComparator implements Comparator<Permission>,
Serializable {
private static final long serialVersionUID = 1L;
private static final char wildcard = "*".charAt(0);
public int compare(Permission o1, Permission o2) {
if (o1 == o2) return 0;
if ( o1 == null ){
if (o2 == null) return 0;
return -1; // o1 is less
}
if ( o2 == null ) return 1; // o1 is greater
int hash1, hash2, comparison;
// Permission not equal if Class hashCode not equal.
Class c1 = o1.getClass();
Class c2 = o2.getClass();
hash1 = c1.hashCode();
hash2 = c2.hashCode();
if (hash1 < hash2) return -1;
if (hash1 > hash2) return 1;
//hashcodes equal.
if (o1 instanceof UnresolvedPermission && o2 instanceof
UnresolvedPermission){
// Special case
UnresolvedPermission u1 = (UnresolvedPermission) o1, u2 =
(UnresolvedPermission) o2;
String type1 = u1.getUnresolvedType(), type2 =
u2.getUnresolvedType();
if ( type1 == null ){
if (type2 == null) return 0;
return -1; // o1 is less
}
if ( type2 == null ) return 1; // o1 is greater
comparison = type1.compareTo(type2);
if ( comparison != 0 ) return comparison;
// types equal.
String name1 = u1.getUnresolvedName(), name2 =
u2.getUnresolvedName();
if ( name1 == null ){
if (name2 == null) return 0;
return -1; // o1 is less
}
if ( name2 == null ) return 1; // o1 is greater
comparison = name1.compareTo(name2);
if ( comparison != 0 ) return comparison;
// names equal.
String action1 = u1.getUnresolvedName(), action2 =
u2.getUnresolvedName();
if ( action1 == null ){
if (action2 == null) return 0;
return -1; // o1 is less
}
if ( action2 == null ) return 1; // o1 is greater
comparison = action1.compareTo(action2);
if ( comparison != 0 ) return comparison;
// actions equal.
Certificate[] cert1 = u1.getUnresolvedCerts(), cert2 =
u2.getUnresolvedCerts();
if ( cert1 == null ){
if (cert2 == null) return 0;
return -1; // o1 is less
}
if ( cert2 == null ) return 1; // o1 is greater
int l1 = cert1.length, l2 = cert2.length;
if (l1 < l2 ) return -1;
if (l1 > l2 ) return 1;
// Same length cert arrays.
if (Arrays.asList(cert1).containsAll(Arrays.asList(cert2)))
return 0;
// compare each until they don't match don't be fussy
they're not equal
// but they're the same length.
for (int i = 0; i < l1; i++){
int c = cert1[i].toString().compareTo(cert2[i].toString());
if (c != 0) return c;
}
return -1;
}
String name1 = o1.getName();
String name2 = o2.getName();
if ( name1 == null ){
if (name2 == null) return 0;
return -1; // o1 is less
}
if ( name2 == null ) return 1; // o1 is greater
comparison = name1.compareTo(name2);
if ( comparison != 0 ) return comparison;
// names equal.
String actions1 = o1.getActions();
String actions2 = o2.getActions();
if ( actions1 == null ){
if (actions2 == null) return 0;
return -1; // o1 is less
}
if ( actions2 == null ) return 1; // o1 is greater
comparison = actions1.compareTo(actions2);
if ( comparison != 0 ) return comparison;
// actions equal.
// Now we must be careful that these Permission's are truly equal.
// Check they have same class
if ( c1.equals(c2)) return 0;
// if we get to here, someone might be trying to substitute
return -1;
}
}