8014097: add doPrivileged methods with limited privilege scope
Reviewed-by: mchung
--- 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);
+ }
+}