river-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Peter Firmstone <j...@zeus.net.au>
Subject Non Blocking java.security.Policy - synchronized method is superclass.
Date Sun, 08 Jan 2012 10:08:41 GMT
Appended is a new java.security.Policy implementation, it fully supports 
the existing java policy syntax and accepts alternate PolicyParser's.

All state is immutable, except for 2 volatile references, referents 
replaced, not mutated, when the policy is updated.  One referent is an 
array containing PermissionGrant's (interface for immutable object 
representing a grant statement in a policy), the second a 
PermissionCollection containing the Policy Permissions.  The array is 
never mutated after creation, a reference to the array is copied before 
accessing the array or any array methods.

The policy creates PermissionCollection's on demand for checking, 
Permission's are ordered using a PermissionComparator to ensure that for 
example, wildcard SocketPermission's are checked first, to avoid 
unnecessary DNS lookups.  Only the permission being checked and any 
UnresolvedPermission's are added to the PermissionCollection, limiting 
the size of the objects created.

In existing policy implementations PermissionCollection's perform 
blocking operations.

Also, after parsing policy files, PermissionGrant implementations avoid 
the need to open files or network connections to confirm URL's, eg 
CodeSource.implies is not called, but instead reimplemented using URI.

Will this scale?  There but one smell:

ProtectionDomain uses a synchronized method Policy.getPolicyNoCheck(), 
but this only retrieves a reference on 99% of occasions.

For every permission check, the stack access control context is 
retrieved, every ProtectionDomain on the stack must be checked, 
ProtectionDomain's must call getPolicyNoCheck() to call 
Policy.implies(ProtectionDomain domain, Permission permission).

To make this worse, I've got a SecurityManager that divides the 
ProtectionDomain.implies() calls into tasks and submits them to an 
executor (if there are 4 or more PD's in a context).  The 
SecurityManager is also non blocking, at least it will be when I use the 
new ConcurrentHashMap for the checked permission cache (avoids repeated 
security checks), for now the cache is implemented using the existing 
ConcurrentHashMap, but is mostly read in any case.  (P.S. This is the 
cache I'm using the Reference Collection's for.)

How much can this one synchronized method spoil scalability?

Cheers & thanks in advance,

Peter.



/*
 *  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.
 */

 /**
  * Default Policy implementation taken from Apache Harmony, refactored for
  * concurrency.
  *
  * @author Alexey V. Varlamov
  * @author Peter Firmstone
  * @version $Revision$
  */

package net.jini.security.policy;

import java.io.File;
import java.net.URL;
import java.security.AccessController;
import java.security.AllPermission;
import java.security.CodeSource;
import java.security.Guard;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.security.SecurityPermission;
import java.security.UnresolvedPermission;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableSet;
import java.util.Properties;
import java.util.TreeSet;
import net.jini.security.PermissionComparator;
import org.apache.river.api.security.PermissionGrant;
import org.apache.river.impl.security.policy.util.DefaultPolicyParser;
import org.apache.river.impl.security.policy.util.PolicyParser;
import org.apache.river.impl.security.policy.util.PolicyUtils;


/**
 * Concurrent Policy implementation based on policy configuration files,
 * it is intended to provide concurrent implies() for greatly improved
 * throughput at the expense of single threaded performance.
 *
 * Set the following system properties to use this Policy instead of the
 * built in Java sun.security.provider.PolicyFile:
 * 
 * net.jini.security.policy.PolicyFileProvider.basePolicyClass =
 * org.apache.river.security.concurrent.ConcurrentPolicyFile
 *
 *
 * This
 * implementation recognizes text files, consisting of clauses with the
 * following syntax:
 *
 * <pre>
 * keystore &quot;some_keystore_url&quot; [, &quot;keystore_type&quot;];
 * </pre>
 <pre>
 * grant [SignedBy &quot;signer_names&quot;] [, CodeBase &quot;URL&quot;]
 *  [, Principal [principal_class_name] &quot;principal_name&quot;]
 *  [, Principal [principal_class_name] &quot;principal_name&quot;] ... {
 *  permission permission_class_name [ &quot;target_name&quot; ] [, 
&quot;action&quot;]
 *  [, SignedBy &quot;signer_names&quot;];
 *  permission ...
 *  };
 * 
 * </pre>
 *
 * The <i>keystore </i> clause specifies reference to a keystore, which is a
 * database of private keys and their associated digital certificates. The
 * keystore is used to look up the certificates of signers specified in the
 * <i>grant </i> entries of the file. The policy file can contain any 
number of
 * <i>keystore </i> entries which can appear at any ordinal position. 
However,
 * only the first successfully loaded keystore is used, others are 
ignored. The
 * keystore must be specified if some grant clause refers to a certificate's
 * alias. <br>
 * The <i>grant </i> clause associates a CodeSource (consisting of an 
URL and a
 * set of certificates) of some executable code with a set of 
Permissions which
 * should be granted to the code. So, the CodeSource is defined by values of
 * <i>CodeBase </i> and <i>SignedBy </i> fields. The <i>CodeBase
</i> 
value must
 * be in URL format, while <i>SignedBy </i> value is a (comma-separated 
list of)
 * alias(es) to keystore certificates. These fields can be omitted to 
denote any
 * codebase and any signers (including case of unsigned code), respectively.
 * <br>
 * Also, the code may be required to be executed on behalf of some 
Principals
 * (in other words, code's ProtectionDomain must have the array of 
Principals
 * associated) in order to possess the Permissions. This fact is 
indicated by
 * specifying one or more <i>Principal </i> fields in the <i>grant </i>

clause.
 * Each Principal is specified as class/name pair; name and class can be 
either
 * concrete value or wildcard <i>* </i>. As a special case, the class 
value may
 * be omitted and then the name is treated as an alias to X.509 
Certificate, and
 * the Principal is assumed to be javax.security.auth.x500.X500Principal 
with a
 * name of subject's distinguished name from the certificate. <br>
 * The order between the <i>CodeBase </i>, <i>SignedBy </i>, and 
<i>Principal
 * </i> fields does not matter. The policy file can contain any number 
of grant
 * clauses. <br>
 * Each <i>grant </i> clause must contain one or more <i>permission </i>

entry.
 * The permission entry consist of a fully qualified class name along with
 * optional <i>name </i>, <i>actions </i> and <i>signedby </i>
values. 
Name and
 * actions are arguments to the corresponding constructor of the permission
 * class. SignedBy value represents the keystore alias(es) to certificate(s)
 * used to sign the permission class. That is, this permission entry is
 * effective (i.e., access control permission will be granted based on this
 * entry) only if the bytecode implementation of permission class is 
verified to
 * be correctly signed by the said alias(es). <br>
 * <br>
 * The policy content may be parameterized via property expansion. Namely,
 * expressions like <i>${key} </i> are replaced by values of corresponding
 * system properties. Also, the special <i>slash </i> key (i.e. ${/}) is
 * supported, it is a shortcut to &quot;file.separator&quot; key. Property
 * expansion is performed anywhere a double quoted string is allowed in the
 * policy file. However, this feature is controlled by security 
properties and
 * should be turned on by setting &quot;policy.expandProperties&quot; 
property
 * to <i>true </i>. <br>
 * If property expansion fails (due to a missing key), a corresponding 
entry is
 * ignored. For fields of <i>keystore </i> and <i>grant </i> clauses,

the whole
 * clause is ignored, and for <i>permission </i> entry, only that entry is
 * ignored. <br>
 * <br>
 * The policy also supports generalized expansion in permissions names, of
 * expressions like <i>${{protocol:data}} </i>. Currently the following
 * protocols supported:
 * <dl>
 * <dt>self
 * <dd>Denotes substitution to a principal information of the parental Grant
 * entry. Replaced by a space-separated list of resolved Principals 
(including
 * wildcarded), each formatted as <i>class &quot;name&quot; </i>. If 
parental
 * Grant entry has no Principals, the permission is ignored.
 * <dt>alias: <i>name </i>
 * <dd>Denotes substitution of a KeyStore alias. Namely, if a KeyStore 
has an
 * X.509 certificate associated with the specified name, then replaced by
 * <i>javax.security.auth.x500.X500Principal &quot; <i>DN </i>&quot;
</i>
 * string, where <i>DN </i> is a certificate's subject distinguished name.
 * </dl>
 * <br>
 *
 */

public class ConcurrentPolicyFile extends Policy implements 
ConcurrentPolicy {

    /**
     * System property for dynamically added policy location.
     */
    private static final String JAVA_SECURITY_POLICY = 
"java.security.policy"; //$NON-NLS-1$

    /**
     * Prefix for numbered Policy locations specified in 
security.properties.
     */
    private static final String POLICY_URL_PREFIX = "policy.url."; 
//$NON-NLS-1$
   
    // Reference must be defensively copied before access, once 
published, never mutated.
    private volatile PermissionGrant [] grantArray;
   
    // A specific parser for a particular policy file format.
    private final PolicyParser parser;
   
    private static final Guard guard = new SecurityPermission("getPolicy");
   
    private final ProtectionDomain myDomain;
   
    // reference must be defensively copied before access, once 
published, never mutated.
    private volatile PermissionCollection myPermissions;
   
    /**
     * Default constructor, equivalent to
     * <code>ConcurrentPolicyFile(new DefaultPolicyParser())</code>.
     */
    public ConcurrentPolicyFile() throws PolicyInitializationException {
        this(new DefaultPolicyParser());
    }

    /**
     * Extension constructor for plugging-in a custom parser.
     * @param dpr
     */
    protected ConcurrentPolicyFile(PolicyParser dpr) throws 
PolicyInitializationException {
        guard.checkGuard(null);
        parser = dpr;
        myDomain = this.getClass().getProtectionDomain();
        /*
         * The bootstrap policy makes implies decisions until this 
constructor
         * has returned.  We don't need to lock.
         */
        try {
            // Bug 4911907, do we need to do anything more?
            // The permissions for this domain must be retrieved before
            // construction is complete and this policy takes over.
            initialize(); // Instantiates myPermissions.
        } catch (SecurityException e){
            throw e;
        } catch (Exception e){
            throw new 
PolicyInitializationException("PolicyInitialization failed", e);
        }
    }
   
    private PermissionCollection convert(NavigableSet<Permission> 
permissions){
        PermissionCollection pc = new Permissions();
        // The descending iterator is for SocketPermission.
        Iterator<Permission> it = permissions.descendingIterator();
        while (it.hasNext()) {
            pc.add(it.next());
        }
        return pc;
    }

    /**
     * Returns collection of permissions allowed for the domain
     * according to the policy. The evaluated characteristics of the
     * domain are it's codesource and principals; they are assumed
     * to be <code>null</code> if the domain is <code>null</code>.
     *
     * Each PermissionCollection returned is a unique instance.
     *
     * @param pd ProtectionDomain
     * @see ProtectionDomain
     */
    @Override
    public PermissionCollection getPermissions(ProtectionDomain pd) {
        NavigableSet<Permission> perms = new TreeSet<Permission>(new 
PermissionComparator());
        PermissionGrant [] grantRefCopy = grantArray;
        int l = grantRefCopy.length;
        for ( int j =0; j < l; j++ ){
            PermissionGrant ge = grantRefCopy[j];
            if (ge.implies(pd)){
                if (ge.isPrivileged()){// Don't stuff around finish 
early if you can.
                    PermissionCollection pc = new Permissions();
                    pc.add(new AllPermission());
                    return pc;
                }
                Collection<Permission> c = ge.getPermissions();
                Iterator<Permission> i = c.iterator();
                while (i.hasNext()){
                    Permission p = i.next();
                    perms.add(p);
                }
            }
        }
        // Don't forget to merge the static Permissions.
        PermissionCollection staticPC = null;
        if (pd != null) {
            staticPC = pd.getPermissions();
            if (staticPC != null){
                Enumeration<Permission> e = staticPC.elements();
                while (e.hasMoreElements()){
                    Permission p = e.nextElement();
                    if (p instanceof AllPermission) {
                        PermissionCollection pc = new Permissions();
                        pc.add(p);
                        return pc;
                    }
                    perms.add(p);
                }
            }
        }
        return convert(perms);
    }

    /**
     * Returns collection of permissions allowed for the codesource
     * according to the policy.
     * The evaluation assumes that current principals are undefined.
     *
     * This returns a java.security.Permissions collection, which allows
     * ProtectionDomain to optimise for the AllPermission case, which avoids
     * unnecessarily consulting the policy.
     *
     * If constructed with the four argument constructor, 
ProtectionDomain.implies
     * first consults the Policy, then it's own internal Permissions 
collection,
     * unless it has AllPermission, in which case it returns true without
     * consulting the policy.
     *
     * @param cs CodeSource
     * @see CodeSource
     */
    @Override
    public PermissionCollection getPermissions(CodeSource cs) {
        if (cs == null) throw new NullPointerException("CodeSource 
cannot be null");
        NavigableSet<Permission> perms = new TreeSet<Permission>(new 
PermissionComparator());
        // for ProtectionDomain AllPermission optimisation.
        PermissionGrant [] grantRefCopy = grantArray;
        int l = grantRefCopy.length;
        for ( int j =0; j < l; j++ ){
            PermissionGrant ge = grantRefCopy[j];
            if (ge.implies(cs, null)){ // No Principal's
                if (ge.isPrivileged()){// Don't stuff around finish 
early if you can.
                    PermissionCollection pc = new Permissions();
                    pc.add(new AllPermission());
                    return pc;
                }
                Collection<Permission> c = ge.getPermissions();
                Iterator<Permission> i = c.iterator();
                while (i.hasNext()){
                    Permission p = i.next();
                    perms.add(p);
                }
            }
        }
        return convert(perms);
    }
   
    @Override
    public boolean implies(ProtectionDomain domain, Permission permission) {
        if (permission == null) throw new 
NullPointerException("permission not allowed to be null");
        if (domain == myDomain) {
            PermissionCollection pc = myPermissions;
            return pc.implies(permission);
        }
        Class klass = permission.getClass();
        // Need to have a list of Permission's we can sort if permission 
is SocketPermission.
        NavigableSet<Permission> perms = new TreeSet<Permission>(new 
PermissionComparator());
        PermissionGrant [] grantRefCopy = grantArray;
        int l = grantRefCopy.length;
        for ( int j =0; j < l; j++ ){
            PermissionGrant ge = grantRefCopy[j];
            if (ge.implies(domain)){
                if (ge.isPrivileged()) return true; // Don't stuff 
around finish early if you can.
                Collection<Permission> c = ge.getPermissions();
                Iterator<Permission> i = c.iterator();
                while (i.hasNext()){
                    Permission p = i.next();
                    // Don't make it larger than necessary.
                    if (klass.isInstance(permission) || permission 
instanceof UnresolvedPermission){
                        perms.add(p);
                    }
                }
            }
        }
        // Don't forget to merge the static Permissions.
        PermissionCollection staticPC = null;
        if (domain != null) {
            staticPC =domain.getPermissions();
            if (staticPC != null){
                Enumeration<Permission> e = staticPC.elements();
                while (e.hasMoreElements()){
                    Permission p = e.nextElement();
                    // return early if possible.
                    if (p instanceof AllPermission ) return true;
                    // Don't make it larger than necessary, but don't 
worry about duplicates either.
                    if (klass.isInstance(permission) || permission 
instanceof UnresolvedPermission){
                        perms.add(p);
                    }
                }
            }
        }
        return convert(perms).implies(permission);
    }

    /**
     * Gets fresh list of locations and tries to load all of them in 
sequence;
     * failed loads are ignored. After processing all locations, old policy
     * settings are discarded and new ones come into force. <br>
     *
     * @see PolicyUtils#getPolicyURLs(Properties, String, String)
     */
    @Override
    public void refresh() {
        try {
            initialize();
        } catch (Exception ex) {
            System.err.println(ex);
        }
    }
   
    private void initialize() throws Exception{
        try {
            Collection<PermissionGrant> fresh = 
AccessController.doPrivileged(
                new 
PrivilegedExceptionAction<Collection<PermissionGrant>>(){
                    public Collection<PermissionGrant> run() throws 
SecurityException {
                        Collection<PermissionGrant> fresh = new 
ArrayList<PermissionGrant>(120);
                        Properties system = System.getProperties();
                        system.setProperty("/", File.separator); 
//$NON-NLS-1$
                        URL[] policyLocations = 
PolicyUtils.getPolicyURLs(system,
                                                          
JAVA_SECURITY_POLICY,
                                                          
POLICY_URL_PREFIX);
                        int l = policyLocations.length;
                        for (int i = 0; i < l; i++) {
                            //TODO debug log
//                                System.err.println("Parsing policy 
file: " + policyLocations[i]);
                            try {
                                Collection<PermissionGrant> pc = null;
                                pc = parser.parse(policyLocations[i], 
system);
                                fresh.addAll(pc);
                            } catch (Exception e){
                                // It's best to let a SecurityException 
bubble up
                                // in case there is a problem with our 
policy configuration
                                // or implementation.
                                if ( e instanceof SecurityException ) {
                                    e.printStackTrace(System.out);
                                    throw (SecurityException) e;
                                }
                                // ignore.
                            }
                        }
                        return fresh;
                    }
                }
            );
            // Volatile reference, publish after mutation complete.
            grantArray = fresh.toArray(new PermissionGrant[fresh.size()]);
            myPermissions = getPermissions(myDomain);
        }catch (PrivilegedActionException e){
            Throwable t = e.getCause();
            if ( t instanceof Exception ) throw (Exception) t;
            throw e;
        }
    }

    public boolean isConcurrent() {
        return true;
    }

    public PermissionGrant[] getPermissionGrants() {
        PermissionGrant [] grants = grantArray; // copy volatile 
reference target.
        return grants.clone();
    }
   
    public PermissionGrant[] getPermissionGrants(ProtectionDomain pd) {
        PermissionGrant [] grants = grantArray; // copy volatile 
reference target.
        int l = grants.length;
        List<PermissionGrant> applicable = new 
ArrayList<PermissionGrant>(l); // Always too large, never too small.
        for (int i =0; i < l; i++){
            if (grants[i].implies(pd)){
                applicable.add(grants[i]);
            }
        }
        return applicable.toArray(new PermissionGrant[applicable.size()]);
    }

}

Mime
View raw message