8014097: add doPrivileged methods with limited privilege scope
authorjdn
Tue, 04 Jun 2013 15:33:42 -0700
changeset 17946 7791613dcbfd
parent 17945 97634ef9dd1c
child 17947 e2fba2cede7e
8014097: add doPrivileged methods with limited privilege scope Reviewed-by: mchung
jdk/src/share/classes/java/security/AccessControlContext.java
jdk/src/share/classes/java/security/AccessController.java
jdk/test/java/security/AccessController/LimitedDoPrivileged.java
--- a/jdk/src/share/classes/java/security/AccessControlContext.java	Tue Jun 04 21:59:23 2013 +0100
+++ b/jdk/src/share/classes/java/security/AccessControlContext.java	Tue Jun 04 15:33:42 2013 -0700
@@ -85,6 +85,15 @@
 
     private DomainCombiner combiner = null;
 
+    // limited privilege scope
+    private Permission permissions[];
+    private AccessControlContext parent;
+    private boolean isWrapped;
+
+    // is constrained by limited privilege scope?
+    private boolean isLimited;
+    private ProtectionDomain limitedContext[];
+
     private static boolean debugInit = false;
     private static Debug debug = null;
 
@@ -178,14 +187,79 @@
 
     /**
      * package private for AccessController
+     *
+     * This "argument wrapper" context will be passed as the actual context
+     * parameter on an internal doPrivileged() call used in the implementation.
      */
-    AccessControlContext(ProtectionDomain context[], DomainCombiner combiner) {
+    AccessControlContext(ProtectionDomain caller, DomainCombiner combiner,
+        AccessControlContext parent, AccessControlContext context,
+        Permission[] perms)
+    {
+        /*
+         * Combine the domains from the doPrivileged() context into our
+         * wrapper context, if necessary.
+         */
+        ProtectionDomain[] callerPDs = null;
+        if (caller != null) {
+             callerPDs = new ProtectionDomain[] { caller };
+        }
         if (context != null) {
-            this.context = context.clone();
+            if (combiner != null) {
+                this.context = combiner.combine(callerPDs, context.context);
+            } else {
+                this.context = combine(callerPDs, context.context);
+            }
+        } else {
+            /*
+             * Call combiner even if there is seemingly nothing to combine.
+             */
+            if (combiner != null) {
+                this.context = combiner.combine(callerPDs, null);
+            } else {
+                this.context = combine(callerPDs, null);
+            }
         }
         this.combiner = combiner;
+
+        Permission[] tmp = null;
+        if (perms != null) {
+            tmp = new Permission[perms.length];
+            for (int i=0; i < perms.length; i++) {
+                if (perms[i] == null) {
+                    throw new NullPointerException("permission can't be null");
+                }
+
+                /*
+                 * An AllPermission argument is equivalent to calling
+                 * doPrivileged() without any limit permissions.
+                 */
+                if (perms[i].getClass() == AllPermission.class) {
+                    parent = null;
+                }
+                tmp[i] = perms[i];
+            }
+        }
+
+        /*
+         * For a doPrivileged() with limited privilege scope, initialize
+         * the relevant fields.
+         *
+         * The limitedContext field contains the union of all domains which
+         * are enclosed by this limited privilege scope. In other words,
+         * it contains all of the domains which could potentially be checked
+         * if none of the limiting permissions implied a requested permission.
+         */
+        if (parent != null) {
+            this.limitedContext = combine(parent.context, parent.limitedContext);
+            this.isLimited = true;
+            this.isWrapped = true;
+            this.permissions = tmp;
+            this.parent = parent;
+            this.privilegedContext = context; // used in checkPermission2()
+        }
     }
 
+
     /**
      * package private constructor for AccessController.getContext()
      */
@@ -260,6 +334,13 @@
         if (sm != null) {
             sm.checkPermission(SecurityConstants.GET_COMBINER_PERMISSION);
         }
+        return getCombiner();
+    }
+
+    /**
+     * package private for AccessController
+     */
+    DomainCombiner getCombiner() {
         return combiner;
     }
 
@@ -335,8 +416,10 @@
            or the first domain was a Privileged system domain. This
            is to make the common case for system code very fast */
 
-        if (context == null)
+        if (context == null) {
+            checkPermission2(perm);
             return;
+        }
 
         for (int i=0; i< context.length; i++) {
             if (context[i] != null &&  !context[i].implies(perm)) {
@@ -370,20 +453,108 @@
             debug.println("access allowed "+perm);
         }
 
-        return;
+        checkPermission2(perm);
+    }
+
+    /*
+     * Check the domains associated with the limited privilege scope.
+     */
+    private void checkPermission2(Permission perm) {
+        if (!isLimited) {
+            return;
+        }
+
+        /*
+         * Check the doPrivileged() context parameter, if present.
+         */
+        if (privilegedContext != null) {
+            privilegedContext.checkPermission2(perm);
+        }
+
+        /*
+         * Ignore the limited permissions and parent fields of a wrapper
+         * context since they were already carried down into the unwrapped
+         * context.
+         */
+        if (isWrapped) {
+            return;
+        }
+
+        /*
+         * Try to match any limited privilege scope.
+         */
+        if (permissions != null) {
+            Class<?> permClass = perm.getClass();
+            for (int i=0; i < permissions.length; i++) {
+                Permission limit = permissions[i];
+                if (limit.getClass().equals(permClass) && limit.implies(perm)) {
+                    return;
+                }
+            }
+        }
+
+        /*
+         * Check the limited privilege scope up the call stack or the inherited
+         * parent thread call stack of this ACC.
+         */
+        if (parent != null) {
+            /*
+             * As an optimization, if the parent context is the inherited call
+             * stack context from a parent thread then checking the protection
+             * domains of the parent context is redundant since they have
+             * already been merged into the child thread's context by
+             * optimize(). When parent is set to an inherited context this
+             * context was not directly created by a limited scope
+             * doPrivileged() and it does not have its own limited permissions.
+             */
+            if (permissions == null) {
+                parent.checkPermission2(perm);
+            } else {
+                parent.checkPermission(perm);
+            }
+        }
     }
 
     /**
      * Take the stack-based context (this) and combine it with the
-     * privileged or inherited context, if need be.
+     * privileged or inherited context, if need be. Any limited
+     * privilege scope is flagged regardless of whether the assigned
+     * context comes from an immediately enclosing limited doPrivileged().
+     * The limited privilege scope can indirectly flow from the inherited
+     * parent thread or an assigned context previously captured by getContext().
      */
     AccessControlContext optimize() {
         // the assigned (privileged or inherited) context
         AccessControlContext acc;
+        DomainCombiner combiner = null;
+        AccessControlContext parent = null;
+        Permission[] permissions = null;
+
         if (isPrivileged) {
             acc = privilegedContext;
+            if (acc != null) {
+                /*
+                 * If the context is from a limited scope doPrivileged() then
+                 * copy the permissions and parent fields out of the wrapper
+                 * context that was created to hold them.
+                 */
+                if (acc.isWrapped) {
+                    permissions = acc.permissions;
+                    parent = acc.parent;
+                }
+            }
         } else {
             acc = AccessController.getInheritedAccessControlContext();
+            if (acc != null) {
+                /*
+                 * If the inherited context is constrained by a limited scope
+                 * doPrivileged() then set it as our parent so we will process
+                 * the non-domain-related state.
+                 */
+                if (acc.isLimited) {
+                    parent = acc;
+                }
+            }
         }
 
         // this.context could be null if only system code is on the stack;
@@ -393,53 +564,98 @@
         // acc.context could be null if only system code was involved;
         // in that case, ignore the assigned context
         boolean skipAssigned = (acc == null || acc.context == null);
+        ProtectionDomain[] assigned = (skipAssigned) ? null : acc.context;
+        ProtectionDomain[] pd;
+
+        // if there is no enclosing limited privilege scope on the stack or
+        // inherited from a parent thread
+        boolean skipLimited = ((acc == null || !acc.isWrapped) && parent == null);
 
         if (acc != null && acc.combiner != null) {
             // let the assigned acc's combiner do its thing
-            return goCombiner(context, acc);
+            if (getDebug() != null) {
+                debug.println("AccessControlContext invoking the Combiner");
+            }
+
+            // No need to clone current and assigned.context
+            // combine() will not update them
+            combiner = acc.combiner;
+            pd = combiner.combine(context, assigned);
+        } else {
+            if (skipStack) {
+                if (skipAssigned) {
+                    calculateFields(acc, parent, permissions);
+                    return this;
+                } else if (skipLimited) {
+                    return acc;
+                }
+            } else if (assigned != null) {
+                if (skipLimited) {
+                    // optimization: if there is a single stack domain and
+                    // that domain is already in the assigned context; no
+                    // need to combine
+                    if (context.length == 1 && context[0] == assigned[0]) {
+                        return acc;
+                    }
+                }
+            }
+
+            pd = combine(context, assigned);
+            if (skipLimited && !skipAssigned && pd == assigned) {
+                return acc;
+            } else if (skipAssigned && pd == context) {
+                calculateFields(acc, parent, permissions);
+                return this;
+            }
         }
 
-        // optimization: if neither have contexts; return acc if possible
-        // rather than this, because acc might have a combiner
-        if (skipAssigned && skipStack) {
-            return this;
-        }
+        // Reuse existing ACC
+        this.context = pd;
+        this.combiner = combiner;
+        this.isPrivileged = false;
+
+        calculateFields(acc, parent, permissions);
+        return this;
+    }
+
 
-        // optimization: if there is no stack context; there is no reason
-        // to compress the assigned context, it already is compressed
-        if (skipStack) {
-            return acc;
-        }
+    /*
+     * Combine the current (stack) and assigned domains.
+     */
+    private static ProtectionDomain[] combine(ProtectionDomain[]current,
+        ProtectionDomain[] assigned) {
 
-        int slen = context.length;
+        // current could be null if only system code is on the stack;
+        // in that case, ignore the stack context
+        boolean skipStack = (current == null);
+
+        // assigned could be null if only system code was involved;
+        // in that case, ignore the assigned context
+        boolean skipAssigned = (assigned == null);
+
+        int slen = (skipStack) ? 0 : current.length;
 
         // optimization: if there is no assigned context and the stack length
         // is less then or equal to two; there is no reason to compress the
         // stack context, it already is
         if (skipAssigned && slen <= 2) {
-            return this;
+            return current;
         }
 
-        // optimization: if there is a single stack domain and that domain
-        // is already in the assigned context; no need to combine
-        if ((slen == 1) && (context[0] == acc.context[0])) {
-            return acc;
-        }
-
-        int n = (skipAssigned) ? 0 : acc.context.length;
+        int n = (skipAssigned) ? 0 : assigned.length;
 
         // now we combine both of them, and create a new context
         ProtectionDomain pd[] = new ProtectionDomain[slen + n];
 
         // first copy in the assigned context domains, no need to compress
         if (!skipAssigned) {
-            System.arraycopy(acc.context, 0, pd, 0, n);
+            System.arraycopy(assigned, 0, pd, 0, n);
         }
 
         // now add the stack context domains, discarding nulls and duplicates
     outer:
-        for (int i = 0; i < context.length; i++) {
-            ProtectionDomain sd = context[i];
+        for (int i = 0; i < slen; i++) {
+            ProtectionDomain sd = current[i];
             if (sd != null) {
                 for (int j = 0; j < n; j++) {
                     if (sd == pd[j]) {
@@ -453,53 +669,47 @@
         // if length isn't equal, we need to shorten the array
         if (n != pd.length) {
             // optimization: if we didn't really combine anything
-            if (!skipAssigned && n == acc.context.length) {
-                return acc;
+            if (!skipAssigned && n == assigned.length) {
+                return assigned;
             } else if (skipAssigned && n == slen) {
-                return this;
+                return current;
             }
             ProtectionDomain tmp[] = new ProtectionDomain[n];
             System.arraycopy(pd, 0, tmp, 0, n);
             pd = tmp;
         }
 
-        //      return new AccessControlContext(pd, false);
-
-        // Reuse existing ACC
-
-        this.context = pd;
-        this.combiner = null;
-        this.isPrivileged = false;
-
-        return this;
+        return pd;
     }
 
-    private AccessControlContext goCombiner(ProtectionDomain[] current,
-                                        AccessControlContext assigned) {
-
-        // the assigned ACC's combiner is not null --
-        // let the combiner do its thing
-
-        // XXX we could add optimizations to 'current' here ...
-
-        if (getDebug() != null) {
-            debug.println("AccessControlContext invoking the Combiner");
-        }
 
-        // No need to clone current and assigned.context
-        // combine() will not update them
-        ProtectionDomain[] combinedPds = assigned.combiner.combine(
-            current, assigned.context);
-
-        // return new AccessControlContext(combinedPds, assigned.combiner);
+    /*
+     * Calculate the additional domains that could potentially be reached via
+     * limited privilege scope. Mark the context as being subject to limited
+     * privilege scope unless the reachable domains (if any) are already
+     * contained in this domain context (in which case any limited
+     * privilege scope checking would be redundant).
+     */
+    private void calculateFields(AccessControlContext assigned,
+        AccessControlContext parent, Permission[] permissions)
+    {
+        ProtectionDomain[] parentLimit = null;
+        ProtectionDomain[] assignedLimit = null;
+        ProtectionDomain[] newLimit;
 
-        // Reuse existing ACC
-        this.context = combinedPds;
-        this.combiner = assigned.combiner;
-        this.isPrivileged = false;
+        parentLimit = (parent != null)? parent.limitedContext: null;
+        assignedLimit = (assigned != null)? assigned.limitedContext: null;
+        newLimit = combine(parentLimit, assignedLimit);
+        if (newLimit != null) {
+            if (context == null || !containsAllPDs(newLimit, context)) {
+                this.limitedContext = newLimit;
+                this.permissions = permissions;
+                this.parent = parent;
+                this.isLimited = true;
+            }
+        }
+    }
 
-        return this;
-    }
 
     /**
      * Checks two AccessControlContext objects for equality.
@@ -520,31 +730,131 @@
 
         AccessControlContext that = (AccessControlContext) obj;
 
+        if (!equalContext(that))
+            return false;
 
-        if (context == null) {
-            return (that.context == null);
-        }
+        if (!equalLimitedContext(that))
+            return false;
+
+        return true;
+    }
 
-        if (that.context == null)
+    /*
+     * Compare for equality based on state that is free of limited
+     * privilege complications.
+     */
+    private boolean equalContext(AccessControlContext that) {
+        if (!equalPDs(this.context, that.context))
             return false;
 
-        if (!(this.containsAllPDs(that) && that.containsAllPDs(this)))
+        if (this.combiner == null && that.combiner != null)
+            return false;
+
+        if (this.combiner != null && !this.combiner.equals(that.combiner))
             return false;
 
-        if (this.combiner == null)
-            return (that.combiner == null);
+        return true;
+    }
 
-        if (that.combiner == null)
+    private boolean equalPDs(ProtectionDomain[] a, ProtectionDomain[] b) {
+        if (a == null) {
+            return (b == null);
+        }
+
+        if (b == null)
             return false;
 
-        if (!this.combiner.equals(that.combiner))
+        if (!(containsAllPDs(a, b) && containsAllPDs(b, a)))
             return false;
 
         return true;
     }
 
-    private boolean containsAllPDs(AccessControlContext that) {
+    /*
+     * Compare for equality based on state that is captured during a
+     * call to AccessController.getContext() when a limited privilege
+     * scope is in effect.
+     */
+    private boolean equalLimitedContext(AccessControlContext that) {
+        if (that == null)
+            return false;
+
+        /*
+         * If neither instance has limited privilege scope then we're done.
+         */
+        if (!this.isLimited && !that.isLimited)
+            return true;
+
+        /*
+         * If only one instance has limited privilege scope then we're done.
+         */
+         if (!(this.isLimited && that.isLimited))
+             return false;
+
+        /*
+         * Wrapped instances should never escape outside the implementation
+         * this class and AccessController so this will probably never happen
+         * but it only makes any sense to compare if they both have the same
+         * isWrapped state.
+         */
+        if ((this.isWrapped && !that.isWrapped) ||
+            (!this.isWrapped && that.isWrapped)) {
+            return false;
+        }
+
+        if (this.permissions == null && that.permissions != null)
+            return false;
+
+        if (this.permissions != null && that.permissions == null)
+            return false;
+
+        if (!(this.containsAllLimits(that) && that.containsAllLimits(this)))
+            return false;
+
+        /*
+         * Skip through any wrapped contexts.
+         */
+        AccessControlContext thisNextPC = getNextPC(this);
+        AccessControlContext thatNextPC = getNextPC(that);
+
+        /*
+         * The protection domains and combiner of a privilegedContext are
+         * not relevant because they have already been included in the context
+         * of this instance by optimize() so we only care about any limited
+         * privilege state they may have.
+         */
+        if (thisNextPC == null && thatNextPC != null && thatNextPC.isLimited)
+            return false;
+
+        if (thisNextPC != null && !thisNextPC.equalLimitedContext(thatNextPC))
+            return false;
+
+        if (this.parent == null && that.parent != null)
+            return false;
+
+        if (this.parent != null && !this.parent.equals(that.parent))
+            return false;
+
+        return true;
+    }
+
+    /*
+     * Follow the privilegedContext link making our best effort to skip
+     * through any wrapper contexts.
+     */
+    private static AccessControlContext getNextPC(AccessControlContext acc) {
+        while (acc != null && acc.privilegedContext != null) {
+            acc = acc.privilegedContext;
+            if (!acc.isWrapped)
+                return acc;
+        }
+        return null;
+    }
+
+    private static boolean containsAllPDs(ProtectionDomain[] thisContext,
+        ProtectionDomain[] thatContext) {
         boolean match = false;
+
         //
         // ProtectionDomains within an ACC currently cannot be null
         // and this is enforced by the constructor and the various
@@ -552,17 +862,17 @@
         // to support the notion of a null PD and therefore this logic continues
         // to support that notion.
         ProtectionDomain thisPd;
-        for (int i = 0; i < context.length; i++) {
+        for (int i = 0; i < thisContext.length; i++) {
             match = false;
-            if ((thisPd = context[i]) == null) {
-                for (int j = 0; (j < that.context.length) && !match; j++) {
-                    match = (that.context[j] == null);
+            if ((thisPd = thisContext[i]) == null) {
+                for (int j = 0; (j < thatContext.length) && !match; j++) {
+                    match = (thatContext[j] == null);
                 }
             } else {
                 Class<?> thisPdClass = thisPd.getClass();
                 ProtectionDomain thatPd;
-                for (int j = 0; (j < that.context.length) && !match; j++) {
-                    thatPd = that.context[j];
+                for (int j = 0; (j < thatContext.length) && !match; j++) {
+                    thatPd = thatContext[j];
 
                     // Class check required to avoid PD exposure (4285406)
                     match = (thatPd != null &&
@@ -573,6 +883,29 @@
         }
         return match;
     }
+
+    private boolean containsAllLimits(AccessControlContext that) {
+        boolean match = false;
+        Permission thisPerm;
+
+        if (this.permissions == null && that.permissions == null)
+            return true;
+
+        for (int i = 0; i < this.permissions.length; i++) {
+            Permission limit = this.permissions[i];
+            Class <?> limitClass = limit.getClass();
+            match = false;
+            for (int j = 0; (j < that.permissions.length) && !match; j++) {
+                Permission perm = that.permissions[j];
+                match = (limitClass.equals(perm.getClass()) &&
+                    limit.equals(perm));
+            }
+            if (!match) return false;
+        }
+        return match;
+    }
+
+
     /**
      * Returns the hash code value for this context. The hash code
      * is computed by exclusive or-ing the hash code of all the protection
@@ -591,6 +924,7 @@
             if (context[i] != null)
                 hashCode ^= context[i].hashCode();
         }
+
         return hashCode;
     }
 }
--- a/jdk/src/share/classes/java/security/AccessController.java	Tue Jun 04 21:59:23 2013 +0100
+++ b/jdk/src/share/classes/java/security/AccessController.java	Tue Jun 04 15:33:42 2013 -0700
@@ -82,9 +82,15 @@
  *     else if (caller i is marked as privileged) {
  *         if (a context was specified in the call to doPrivileged)
  *             context.checkPermission(permission)
- *         return;
+ *         if (limited permissions were specified in the call to doPrivileged) {
+ *             for (each limited permission) {
+ *                 if (the limited permission implies the requested permission)
+ *                     return;
+ *             }
+ *         } else
+ *             return;
  *     }
- * };
+ * }
  *
  * // Next, check the context inherited when the thread was created.
  * // Whenever a new thread is created, the AccessControlContext at
@@ -101,11 +107,16 @@
  * was marked as "privileged" via a <code>doPrivileged</code>
  * call without a context argument (see below for information about a
  * context argument). If that caller's domain has the
- * specified permission, no further checking is done and
+ * specified permission and at least one limiting permission argument (if any)
+ * implies the requested permission, no further checking is done and
  * <code>checkPermission</code>
  * returns quietly, indicating that the requested access is allowed.
  * If that domain does not have the specified permission, an exception
- * is thrown, as usual.
+ * is thrown, as usual. If the caller's domain had the specified permission
+ * but it was not implied by any limiting permission arguments given in the call
+ * to <code>doPrivileged</code> then the permission checking continues
+ * until there are no more callers or another <code>doPrivileged</code>
+ * call matches the requested permission and returns normally.
  *
  * <p> The normal use of the "privileged" feature is as follows. If you
  * don't need to return a value from within the "privileged" block, do
@@ -180,6 +191,9 @@
  *
  * <p> Be *very* careful in your use of the "privileged" construct, and
  * always remember to make the privileged code section as small as possible.
+ * You can pass <code>Permission</code> arguments to further limit the
+ * scope of the "privilege" (see below).
+ *
  *
  * <p> Note that <code>checkPermission</code> always performs security checks
  * within the context of the currently executing thread.
@@ -215,7 +229,9 @@
  *
  * <p> There are also times where you don't know a priori which permissions
  * to check the context against. In these cases you can use the
- * doPrivileged method that takes a context:
+ * doPrivileged method that takes a context. You can also limit the scope
+ * of the privileged code by passing additional <code>Permission</code>
+ * parameters.
  *
  *  <pre> {@code
  * somemethod() {
@@ -223,12 +239,21 @@
  *         public Object run() {
  *             // Code goes here. Any permission checks within this
  *             // run method will require that the intersection of the
- *             // callers protection domain and the snapshot's
- *             // context have the desired permission.
+ *             // caller's protection domain and the snapshot's
+ *             // context have the desired permission. If a requested
+ *             // permission is not implied by the limiting FilePermission
+ *             // argument then checking of the thread continues beyond the
+ *             // caller of doPrivileged.
  *         }
- *     }, acc);
+ *     }, acc, new FilePermission("/temp/*", read));
  *     ...normal code here...
  * }}</pre>
+ * <p> Passing a limiting <code>Permission</code> argument of an instance of
+ * <code>AllPermission</code> is equivalent to calling the equivalent
+ * <code>doPrivileged</code> method without limiting <code>Permission</code>
+ * arguments. Passing a zero length array of <code>Permission</code> disables
+ * the code privileges so that checking always continues beyond the caller of
+ * that <code>doPrivileged</code> method.
  *
  * @see AccessControlContext
  *
@@ -334,6 +359,112 @@
     public static native <T> T doPrivileged(PrivilegedAction<T> action,
                                             AccessControlContext context);
 
+
+    /**
+     * Performs the specified <code>PrivilegedAction</code> with privileges
+     * enabled and restricted by the specified
+     * <code>AccessControlContext</code> and with a privilege scope limited
+     * by specified <code>Permission</code> arguments.
+     *
+     * The action is performed with the intersection of the permissions
+     * possessed by the caller's protection domain, and those possessed
+     * by the domains represented by the specified
+     * <code>AccessControlContext</code>.
+     * <p>
+     * If the action's <code>run</code> method throws an (unchecked) exception,
+     * it will propagate through this method.
+     *
+     * @param action the action to be performed.
+     * @param context an <i>access control context</i>
+     *                representing the restriction to be applied to the
+     *                caller's domain's privileges before performing
+     *                the specified action.  If the context is
+     *                <code>null</code>,
+     *                then no additional restriction is applied.
+     * @param perms the <code>Permission</code> arguments which limit the
+     *              scope of the caller's privileges. The number of arguments
+     *              is variable.
+     *
+     * @return the value returned by the action's <code>run</code> method.
+     *
+     * @throws NullPointerException if action or perms or any element of
+     *         perms is <code>null</code>
+     *
+     * @see #doPrivileged(PrivilegedAction)
+     * @see #doPrivileged(PrivilegedExceptionAction,AccessControlContext)
+     *
+     * @since 1.8
+     */
+    @CallerSensitive
+    public static <T> T doPrivileged(PrivilegedAction<T> action,
+        AccessControlContext context, Permission... perms) {
+
+        AccessControlContext parent = getContext();
+        if (perms == null) {
+            throw new NullPointerException("null permissions parameter");
+        }
+        Class <?> caller = Reflection.getCallerClass();
+        return AccessController.doPrivileged(action, createWrapper(null,
+            caller, parent, context, perms));
+    }
+
+
+    /**
+     * Performs the specified <code>PrivilegedAction</code> with privileges
+     * enabled and restricted by the specified
+     * <code>AccessControlContext</code> and with a privilege scope limited
+     * by specified <code>Permission</code> arguments.
+     *
+     * The action is performed with the intersection of the permissions
+     * possessed by the caller's protection domain, and those possessed
+     * by the domains represented by the specified
+     * <code>AccessControlContext</code>.
+     * <p>
+     * If the action's <code>run</code> method throws an (unchecked) exception,
+     * it will propagate through this method.
+     *
+     * <p> This method preserves the current AccessControlContext's
+     * DomainCombiner (which may be null) while the action is performed.
+     *
+     * @param action the action to be performed.
+     * @param context an <i>access control context</i>
+     *                representing the restriction to be applied to the
+     *                caller's domain's privileges before performing
+     *                the specified action.  If the context is
+     *                <code>null</code>,
+     *                then no additional restriction is applied.
+     * @param perms the <code>Permission</code> arguments which limit the
+     *              scope of the caller's privileges. The number of arguments
+     *              is variable.
+     *
+     * @return the value returned by the action's <code>run</code> method.
+     *
+     * @throws NullPointerException if action or perms or any element of
+     *         perms is <code>null</code>
+     *
+     * @see #doPrivileged(PrivilegedAction)
+     * @see #doPrivileged(PrivilegedExceptionAction,AccessControlContext)
+     * @see java.security.DomainCombiner
+     *
+     * @since 1.8
+     */
+    @CallerSensitive
+    public static <T> T doPrivilegedWithCombiner(PrivilegedAction<T> action,
+        AccessControlContext context, Permission... perms) {
+
+        AccessControlContext parent = getContext();
+        DomainCombiner dc = parent.getCombiner();
+        if (dc == null && context != null) {
+            dc = context.getCombiner();
+        }
+        if (perms == null) {
+            throw new NullPointerException("null permissions parameter");
+        }
+        Class <?> caller = Reflection.getCallerClass();
+        return AccessController.doPrivileged(action, createWrapper(dc, caller,
+            parent, context, perms));
+    }
+
     /**
      * Performs the specified <code>PrivilegedExceptionAction</code> with
      * privileges enabled.  The action is performed with <i>all</i> of the
@@ -408,6 +539,22 @@
     private static AccessControlContext preserveCombiner(DomainCombiner combiner,
                                                          Class<?> caller)
     {
+        return createWrapper(combiner, caller, null, null, null);
+    }
+
+    /**
+     * Create a wrapper to contain the limited privilege scope data.
+     */
+    private static AccessControlContext
+        createWrapper(DomainCombiner combiner, Class<?> caller,
+                      AccessControlContext parent, AccessControlContext context,
+                      Permission[] perms)
+    {
+        return new AccessControlContext(getCallerPD(caller), combiner, parent,
+                                        context, perms);
+    }
+
+    private static ProtectionDomain getCallerPD(final Class <?> caller) {
         ProtectionDomain callerPd = doPrivileged
             (new PrivilegedAction<ProtectionDomain>() {
             public ProtectionDomain run() {
@@ -415,18 +562,9 @@
             }
         });
 
-        // perform 'combine' on the caller of doPrivileged,
-        // even if the caller is from the bootclasspath
-        ProtectionDomain[] pds = new ProtectionDomain[] {callerPd};
-        if (combiner == null) {
-            return new AccessControlContext(pds);
-        } else {
-            return new AccessControlContext(combiner.combine(pds, null),
-                                            combiner);
-        }
+        return callerPd;
     }
 
-
     /**
      * Performs the specified <code>PrivilegedExceptionAction</code> with
      * privileges enabled and restricted by the specified
@@ -454,7 +592,7 @@
      * @exception NullPointerException if the action is <code>null</code>
      *
      * @see #doPrivileged(PrivilegedAction)
-     * @see #doPrivileged(PrivilegedExceptionAction,AccessControlContext)
+     * @see #doPrivileged(PrivilegedAction,AccessControlContext)
      */
     @CallerSensitive
     public static native <T> T
@@ -462,6 +600,118 @@
                      AccessControlContext context)
         throws PrivilegedActionException;
 
+
+    /**
+     * Performs the specified <code>PrivilegedExceptionAction</code> with
+     * privileges enabled and restricted by the specified
+     * <code>AccessControlContext</code> and with a privilege scope limited by
+     * specified <code>Permission</code> arguments.
+     *
+     * The action is performed with the intersection of the permissions
+     * possessed by the caller's protection domain, and those possessed
+     * by the domains represented by the specified
+     * <code>AccessControlContext</code>.
+     * <p>
+     * If the action's <code>run</code> method throws an (unchecked) exception,
+     * it will propagate through this method.
+     *
+     * @param action the action to be performed.
+     * @param context an <i>access control context</i>
+     *                representing the restriction to be applied to the
+     *                caller's domain's privileges before performing
+     *                the specified action.  If the context is
+     *                <code>null</code>,
+     *                then no additional restriction is applied.
+     * @param perms the <code>Permission</code> arguments which limit the
+     *              scope of the caller's privileges. The number of arguments
+     *              is variable.
+     *
+     * @return the value returned by the action's <code>run</code> method.
+     *
+     * @throws PrivilegedActionException if the specified action's
+     *         <code>run</code> method threw a <i>checked</i> exception
+     * @throws NullPointerException if action or perms or any element of
+     *         perms is <code>null</code>
+     *
+     * @see #doPrivileged(PrivilegedAction)
+     * @see #doPrivileged(PrivilegedAction,AccessControlContext)
+     *
+     * @since 1.8
+     */
+    @CallerSensitive
+    public static <T> T doPrivileged(PrivilegedExceptionAction<T> action,
+                                     AccessControlContext context, Permission... perms)
+        throws PrivilegedActionException
+    {
+        AccessControlContext parent = getContext();
+        if (perms == null) {
+            throw new NullPointerException("null permissions parameter");
+        }
+        Class <?> caller = Reflection.getCallerClass();
+        return AccessController.doPrivileged(action, createWrapper(null, caller, parent, context, perms));
+    }
+
+
+    /**
+     * Performs the specified <code>PrivilegedExceptionAction</code> with
+     * privileges enabled and restricted by the specified
+     * <code>AccessControlContext</code> and with a privilege scope limited by
+     * specified <code>Permission</code> arguments.
+     *
+     * The action is performed with the intersection of the permissions
+     * possessed by the caller's protection domain, and those possessed
+     * by the domains represented by the specified
+     * <code>AccessControlContext</code>.
+     * <p>
+     * If the action's <code>run</code> method throws an (unchecked) exception,
+     * it will propagate through this method.
+     *
+     * <p> This method preserves the current AccessControlContext's
+     * DomainCombiner (which may be null) while the action is performed.
+     *
+     * @param action the action to be performed.
+     * @param context an <i>access control context</i>
+     *                representing the restriction to be applied to the
+     *                caller's domain's privileges before performing
+     *                the specified action.  If the context is
+     *                <code>null</code>,
+     *                then no additional restriction is applied.
+     * @param perms the <code>Permission</code> arguments which limit the
+     *              scope of the caller's privileges. The number of arguments
+     *              is variable.
+     *
+     * @return the value returned by the action's <code>run</code> method.
+     *
+     * @throws PrivilegedActionException if the specified action's
+     *         <code>run</code> method threw a <i>checked</i> exception
+     * @throws NullPointerException if action or perms or any element of
+     *         perms is <code>null</code>
+     *
+     * @see #doPrivileged(PrivilegedAction)
+     * @see #doPrivileged(PrivilegedAction,AccessControlContext)
+     * @see java.security.DomainCombiner
+     *
+     * @since 1.8
+     */
+    @CallerSensitive
+    public static <T> T doPrivilegedWithCombiner(PrivilegedExceptionAction<T> action,
+                                                 AccessControlContext context,
+                                                 Permission... perms)
+        throws PrivilegedActionException
+    {
+        AccessControlContext parent = getContext();
+        DomainCombiner dc = parent.getCombiner();
+        if (dc == null && context != null) {
+            dc = context.getCombiner();
+        }
+        if (perms == null) {
+            throw new NullPointerException("null permissions parameter");
+        }
+        Class <?> caller = Reflection.getCallerClass();
+        return AccessController.doPrivileged(action, createWrapper(dc, caller,
+            parent, context, perms));
+    }
+
     /**
      * Returns the AccessControl context. i.e., it gets
      * the protection domains of all the callers on the stack,
@@ -474,6 +724,7 @@
 
     private static native AccessControlContext getStackAccessControlContext();
 
+
     /**
      * Returns the "inherited" AccessControl context. This is the context
      * that existed when the thread was created. Package private so
@@ -484,9 +735,9 @@
 
     /**
      * This method takes a "snapshot" of the current calling context, which
-     * includes the current Thread's inherited AccessControlContext,
-     * and places it in an AccessControlContext object. This context may then
-     * be checked at a later point, possibly in another thread.
+     * includes the current Thread's inherited AccessControlContext and any
+     * limited privilege scope, and places it in an AccessControlContext object.
+     * This context may then be checked at a later point, possibly in another thread.
      *
      * @see AccessControlContext
      *
@@ -524,7 +775,7 @@
      */
 
     public static void checkPermission(Permission perm)
-                 throws AccessControlException
+        throws AccessControlException
     {
         //System.err.println("checkPermission "+perm);
         //Thread.currentThread().dumpStack();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/security/AccessController/LimitedDoPrivileged.java	Tue Jun 04 15:33:42 2013 -0700
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8014097
+ * @summary Test the limited privilege scope version of doPrivileged
+ */
+
+import java.security.*;
+import java.util.*;
+
+public class LimitedDoPrivileged {
+    /*
+     * Test variations of doPrivileged() and doPrivileged() with a limited privilege scope
+     * in a sandbox with the usual default permission to read the system properties for the
+     * file and path separators.
+     *
+     * By passing in an "assigned" AccessControlContext that has
+     * no default permissions we can test how code privileges are being scoped.
+     */
+
+    private static final ProtectionDomain domain =
+        new ProtectionDomain(null, null, null, null);
+    private static final AccessControlContext acc =
+        new AccessControlContext(new ProtectionDomain[] { domain });
+    private static final PropertyPermission pathPerm =
+        new PropertyPermission("path.separator", "read");
+    private static final PropertyPermission filePerm =
+        new PropertyPermission("file.separator", "read");
+
+    public static void main(String[] args) throws Exception {
+        /*
+         * Verify that we have the usual default property read permission.
+         */
+        AccessController.getContext().checkPermission(filePerm);
+        AccessController.getContext().checkPermission(pathPerm);
+        System.out.println("test 1 passed");
+
+        /*
+         * Inject the "no permission" AccessControlContext.
+         */
+        AccessController.doPrivileged(new PrivilegedAction() {
+            public Object run() {
+
+                /*
+                 * Verify that we no longer have the "file.separator" permission.
+                 */
+                try {
+                    AccessController.getContext().checkPermission(pathPerm);
+                } catch (AccessControlException ace) {
+                    System.out.println("test 2 passed");
+                }
+
+                /*
+                 * Verify that we can give ourselves limited privilege to read
+                 * any system property starting with "path.".
+                 */
+                AccessController.doPrivileged
+                    (new PrivilegedAction() {
+                        public Object run() {
+                            AccessController.getContext().checkPermission(pathPerm);
+                            return null;
+                        }
+                }, null, new PropertyPermission("path.*", "read"));
+                System.out.println("test 3 passed");
+
+                /*
+                 * Verify that if we give ourselves limited privilege to read
+                 * any system property starting with "path." it won't give us the
+                 * the ability to read "file.separator".
+                 */
+                try {
+                    AccessController.doPrivileged
+                        (new PrivilegedAction() {
+                            public Object run() {
+                                AccessController.getContext().checkPermission(filePerm);
+                                return null;
+                            }
+                    }, null, new PropertyPermission("path.*", "read"));
+                } catch (AccessControlException ace) {
+                    System.out.println("test 4 passed");
+                }
+
+                /*
+                 * Verify that capturing and passing in the context with no default
+                 * system property permission grants will prevent access that succeeded
+                 * earlier without the context assignment.
+                 */
+                final AccessControlContext context = AccessController.getContext();
+                try {
+                    AccessController.doPrivileged
+                        (new PrivilegedAction() {
+                            public Object run() {
+                                AccessController.getContext().checkPermission(pathPerm);
+                                return null;
+                            }
+                    }, context, new PropertyPermission("path.*", "read"));
+                } catch (AccessControlException ace) {
+                    System.out.println("test 5 passed");
+                }
+
+                /*
+                 * Verify that we can give ourselves full privilege to read
+                 * any system property starting with "path.".
+                 */
+                AccessController.doPrivileged
+                     (new PrivilegedAction() {
+                        public Object run() {
+                            AccessController.getContext().checkPermission(pathPerm);
+                            return null;
+                        }
+                });
+                System.out.println("test 6 passed");
+
+                /*
+                 * Verify that capturing and passing in the context with no default
+                 * system property permission grants will prevent access that succeeded
+                 * earlier without the context assignment.
+                 */
+                try {
+                    AccessController.doPrivileged
+                        (new PrivilegedAction() {
+                            public Object run() {
+                                AccessController.getContext().checkPermission(pathPerm);
+                                return null;
+                            }
+                    }, context);
+                } catch (AccessControlException ace) {
+                    System.out.println("test 7 passed");
+                }
+
+                /*
+                 * Verify that we can give ourselves limited privilege to read
+                 * any system property starting with "path." when a limited
+                 * privilege scope context is captured and passed to a regular
+                 * doPrivileged() as an assigned context.
+                 */
+                AccessController.doPrivileged
+                     (new PrivilegedAction() {
+                        public Object run() {
+
+                            /*
+                             * Capture the limited privilege scope and inject it into the
+                             * regular doPrivileged().
+                             */
+                            final AccessControlContext limitedContext = AccessController.getContext();
+                            AccessController.doPrivileged
+                                (new PrivilegedAction() {
+                                    public Object run() {
+                                        AccessController.getContext().checkPermission(pathPerm);
+                                        return null;
+                                }
+                            }, limitedContext);
+                            return null;
+                        }
+                }, null, new PropertyPermission("path.*", "read"));
+                System.out.println("test 8 passed");
+
+                /*
+                 * Verify that we can give ourselves limited privilege to read
+                 * any system property starting with "path." it won't give us the
+                 * the ability to read "file.separator" when a limited
+                 * privilege scope context is captured and passed to a regular
+                 * doPrivileged() as an assigned context.
+                 */
+                AccessController.doPrivileged
+                     (new PrivilegedAction() {
+                        public Object run() {
+
+                            /*
+                             * Capture the limited privilege scope and inject it into the
+                             * regular doPrivileged().
+                             */
+                            final AccessControlContext limitedContext = AccessController.getContext();
+                            try {
+                                AccessController.doPrivileged
+                                    (new PrivilegedAction() {
+                                        public Object run() {
+                                            AccessController.getContext().checkPermission(filePerm);
+                                            return null;
+                                    }
+                                }, limitedContext);
+                            } catch (AccessControlException ace) {
+                                System.out.println("test 9 passed");
+                            }
+                            return null;
+                        }
+                }, null, new PropertyPermission("path.*", "read"));
+
+                return null;
+            }
+        }, acc);
+    }
+}