diff --git a/jdk/src/share/classes/java/security/AccessControlContext.java b/jdk/src/share/classes/java/security/AccessControlContext.java index ba088af8716..9ca78fe9495 100644 --- a/jdk/src/share/classes/java/security/AccessControlContext.java +++ b/jdk/src/share/classes/java/security/AccessControlContext.java @@ -85,6 +85,15 @@ public final class AccessControlContext { 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 @@ public final class AccessControlContext { /** * 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 @@ public final class AccessControlContext { if (sm != null) { sm.checkPermission(SecurityConstants.GET_COMBINER_PERMISSION); } + return getCombiner(); + } + + /** + * package private for AccessController + */ + DomainCombiner getCombiner() { return combiner; } @@ -335,8 +416,10 @@ public final class AccessControlContext { 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 @@ public final class AccessControlContext { 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 @@ public final class AccessControlContext { // 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; - // optimization: if there is no stack context; there is no reason - // to compress the assigned context, it already is compressed - if (skipStack) { - return acc; - } + calculateFields(acc, parent, permissions); + return this; + } - int slen = context.length; + + /* + * Combine the current (stack) and assigned domains. + */ + private static ProtectionDomain[] combine(ProtectionDomain[]current, + ProtectionDomain[] assigned) { + + // 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,54 +669,48 @@ public final class AccessControlContext { // 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 + /* + * 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; - // XXX we could add optimizations to 'current' here ... - - if (getDebug() != null) { - debug.println("AccessControlContext invoking the Combiner"); + 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; + } } - - // 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); - - // Reuse existing ACC - this.context = combinedPds; - this.combiner = assigned.combiner; - this.isPrivileged = false; - - return this; } + /** * Checks two AccessControlContext objects for equality. * Checks that obj is @@ -520,31 +730,131 @@ public final class AccessControlContext { AccessControlContext that = (AccessControlContext) obj; - - if (context == null) { - return (that.context == null); - } - - if (that.context == null) + if (!equalContext(that)) return false; - if (!(this.containsAllPDs(that) && that.containsAllPDs(this))) - return false; - - if (this.combiner == null) - return (that.combiner == null); - - if (that.combiner == null) - return false; - - if (!this.combiner.equals(that.combiner)) + if (!equalLimitedContext(that)) return false; return true; } - private boolean containsAllPDs(AccessControlContext that) { + /* + * 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.combiner == null && that.combiner != null) + return false; + + if (this.combiner != null && !this.combiner.equals(that.combiner)) + return false; + + return true; + } + + private boolean equalPDs(ProtectionDomain[] a, ProtectionDomain[] b) { + if (a == null) { + return (b == null); + } + + if (b == null) + return false; + + if (!(containsAllPDs(a, b) && containsAllPDs(b, a))) + return false; + + return true; + } + + /* + * 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 @@ public final class AccessControlContext { // 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 @@ public final class AccessControlContext { } 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 @@ public final class AccessControlContext { if (context[i] != null) hashCode ^= context[i].hashCode(); } + return hashCode; } } diff --git a/jdk/src/share/classes/java/security/AccessController.java b/jdk/src/share/classes/java/security/AccessController.java index e7fbe737a8e..6eac9a619b2 100644 --- a/jdk/src/share/classes/java/security/AccessController.java +++ b/jdk/src/share/classes/java/security/AccessController.java @@ -82,9 +82,15 @@ import sun.reflect.Reflection; * 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 @@ import sun.reflect.Reflection; * was marked as "privileged" via a doPrivileged * 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 * checkPermission * 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 doPrivileged then the permission checking continues + * until there are no more callers or another doPrivileged + * call matches the requested permission and returns normally. * *

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 @@ import sun.reflect.Reflection; * *

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 Permission arguments to further limit the + * scope of the "privilege" (see below). + * * *

Note that checkPermission always performs security checks * within the context of the currently executing thread. @@ -215,7 +229,9 @@ import sun.reflect.Reflection; * *

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 Permission + * parameters. * *

 {@code
  * somemethod() {
@@ -223,12 +239,21 @@ import sun.reflect.Reflection;
  *         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...
  * }}
+ *

Passing a limiting Permission argument of an instance of + * AllPermission is equivalent to calling the equivalent + * doPrivileged method without limiting Permission + * arguments. Passing a zero length array of Permission disables + * the code privileges so that checking always continues beyond the caller of + * that doPrivileged method. * * @see AccessControlContext * @@ -334,6 +359,112 @@ public final class AccessController { public static native T doPrivileged(PrivilegedAction action, AccessControlContext context); + + /** + * Performs the specified PrivilegedAction with privileges + * enabled and restricted by the specified + * AccessControlContext and with a privilege scope limited + * by specified Permission 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 + * AccessControlContext. + *

+ * If the action's run method throws an (unchecked) exception, + * it will propagate through this method. + * + * @param action the action to be performed. + * @param context an access control context + * representing the restriction to be applied to the + * caller's domain's privileges before performing + * the specified action. If the context is + * null, + * then no additional restriction is applied. + * @param perms the Permission arguments which limit the + * scope of the caller's privileges. The number of arguments + * is variable. + * + * @return the value returned by the action's run method. + * + * @throws NullPointerException if action or perms or any element of + * perms is null + * + * @see #doPrivileged(PrivilegedAction) + * @see #doPrivileged(PrivilegedExceptionAction,AccessControlContext) + * + * @since 1.8 + */ + @CallerSensitive + public static T doPrivileged(PrivilegedAction 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 PrivilegedAction with privileges + * enabled and restricted by the specified + * AccessControlContext and with a privilege scope limited + * by specified Permission 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 + * AccessControlContext. + *

+ * If the action's run method throws an (unchecked) exception, + * it will propagate through this method. + * + *

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 access control context + * representing the restriction to be applied to the + * caller's domain's privileges before performing + * the specified action. If the context is + * null, + * then no additional restriction is applied. + * @param perms the Permission arguments which limit the + * scope of the caller's privileges. The number of arguments + * is variable. + * + * @return the value returned by the action's run method. + * + * @throws NullPointerException if action or perms or any element of + * perms is null + * + * @see #doPrivileged(PrivilegedAction) + * @see #doPrivileged(PrivilegedExceptionAction,AccessControlContext) + * @see java.security.DomainCombiner + * + * @since 1.8 + */ + @CallerSensitive + public static T doPrivilegedWithCombiner(PrivilegedAction 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 PrivilegedExceptionAction with * privileges enabled. The action is performed with all of the @@ -408,6 +539,22 @@ public final class AccessController { 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() { public ProtectionDomain run() { @@ -415,18 +562,9 @@ public final class AccessController { } }); - // 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 PrivilegedExceptionAction with * privileges enabled and restricted by the specified @@ -454,7 +592,7 @@ public final class AccessController { * @exception NullPointerException if the action is null * * @see #doPrivileged(PrivilegedAction) - * @see #doPrivileged(PrivilegedExceptionAction,AccessControlContext) + * @see #doPrivileged(PrivilegedAction,AccessControlContext) */ @CallerSensitive public static native T @@ -462,6 +600,118 @@ public final class AccessController { AccessControlContext context) throws PrivilegedActionException; + + /** + * Performs the specified PrivilegedExceptionAction with + * privileges enabled and restricted by the specified + * AccessControlContext and with a privilege scope limited by + * specified Permission 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 + * AccessControlContext. + *

+ * If the action's run method throws an (unchecked) exception, + * it will propagate through this method. + * + * @param action the action to be performed. + * @param context an access control context + * representing the restriction to be applied to the + * caller's domain's privileges before performing + * the specified action. If the context is + * null, + * then no additional restriction is applied. + * @param perms the Permission arguments which limit the + * scope of the caller's privileges. The number of arguments + * is variable. + * + * @return the value returned by the action's run method. + * + * @throws PrivilegedActionException if the specified action's + * run method threw a checked exception + * @throws NullPointerException if action or perms or any element of + * perms is null + * + * @see #doPrivileged(PrivilegedAction) + * @see #doPrivileged(PrivilegedAction,AccessControlContext) + * + * @since 1.8 + */ + @CallerSensitive + public static T doPrivileged(PrivilegedExceptionAction 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 PrivilegedExceptionAction with + * privileges enabled and restricted by the specified + * AccessControlContext and with a privilege scope limited by + * specified Permission 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 + * AccessControlContext. + *

+ * If the action's run method throws an (unchecked) exception, + * it will propagate through this method. + * + *

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 access control context + * representing the restriction to be applied to the + * caller's domain's privileges before performing + * the specified action. If the context is + * null, + * then no additional restriction is applied. + * @param perms the Permission arguments which limit the + * scope of the caller's privileges. The number of arguments + * is variable. + * + * @return the value returned by the action's run method. + * + * @throws PrivilegedActionException if the specified action's + * run method threw a checked exception + * @throws NullPointerException if action or perms or any element of + * perms is null + * + * @see #doPrivileged(PrivilegedAction) + * @see #doPrivileged(PrivilegedAction,AccessControlContext) + * @see java.security.DomainCombiner + * + * @since 1.8 + */ + @CallerSensitive + public static T doPrivilegedWithCombiner(PrivilegedExceptionAction 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 @@ public final class AccessController { 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 @@ public final class AccessController { /** * 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 final class AccessController { */ public static void checkPermission(Permission perm) - throws AccessControlException + throws AccessControlException { //System.err.println("checkPermission "+perm); //Thread.currentThread().dumpStack(); diff --git a/jdk/test/java/security/AccessController/LimitedDoPrivileged.java b/jdk/test/java/security/AccessController/LimitedDoPrivileged.java new file mode 100644 index 00000000000..bbdf677fbea --- /dev/null +++ b/jdk/test/java/security/AccessController/LimitedDoPrivileged.java @@ -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); + } +}