Path: blob/master/src/java.base/share/classes/java/security/AccessControlContext.java
41152 views
/*1* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.security;2627import java.util.ArrayList;28import java.util.List;2930import sun.security.util.Debug;31import sun.security.util.FilePermCompat;32import sun.security.util.SecurityConstants;333435/**36* An AccessControlContext is used to make system resource access decisions37* based on the context it encapsulates.38*39* <p>More specifically, it encapsulates a context and40* has a single method, {@code checkPermission},41* that is equivalent to the {@code checkPermission} method42* in the AccessController class, with one difference: The AccessControlContext43* {@code checkPermission} method makes access decisions based on the44* context it encapsulates,45* rather than that of the current execution thread.46*47* <p>Thus, the purpose of AccessControlContext is for those situations where48* a security check that should be made within a given context49* actually needs to be done from within a50* <i>different</i> context (for example, from within a worker thread).51*52* <p> An AccessControlContext is created by calling the53* {@code AccessController.getContext} method.54* The {@code getContext} method takes a "snapshot"55* of the current calling context, and places56* it in an AccessControlContext object, which it returns. A sample call is57* the following:58*59* <pre>60* AccessControlContext acc = AccessController.getContext()61* </pre>62*63* <p>64* Code within a different context can subsequently call the65* {@code checkPermission} method on the66* previously-saved AccessControlContext object. A sample call is the67* following:68*69* <pre>70* acc.checkPermission(permission)71* </pre>72*73* @see AccessController74*75* @author Roland Schemers76* @since 1.277* @deprecated This class is only useful in conjunction with78* {@linkplain SecurityManager the Security Manager}, which is deprecated79* and subject to removal in a future release. Consequently, this class80* is also deprecated and subject to removal. There is no replacement for81* the Security Manager or this class.82*/8384@Deprecated(since="17", forRemoval=true)85public final class AccessControlContext {8687private ProtectionDomain[] context;88// isPrivileged and isAuthorized are referenced by the VM - do not remove89// or change their names90private boolean isPrivileged;91private boolean isAuthorized = false;9293// Note: This field is directly used by the virtual machine94// native codes. Don't touch it.95private AccessControlContext privilegedContext;9697@SuppressWarnings("removal")98private DomainCombiner combiner = null;99100// limited privilege scope101private Permission[] permissions;102private AccessControlContext parent;103private boolean isWrapped;104105// is constrained by limited privilege scope?106private boolean isLimited;107private ProtectionDomain[] limitedContext;108109private static boolean debugInit = false;110private static Debug debug = null;111112@SuppressWarnings("removal")113static Debug getDebug()114{115if (debugInit)116return debug;117else {118if (Policy.isSet()) {119debug = Debug.getInstance("access");120debugInit = true;121}122return debug;123}124}125126/**127* Create an AccessControlContext with the given array of ProtectionDomains.128* Context must not be null. Duplicate domains will be removed from the129* context.130*131* @param context the ProtectionDomains associated with this context.132* The non-duplicate domains are copied from the array. Subsequent133* changes to the array will not affect this AccessControlContext.134* @throws NullPointerException if {@code context} is {@code null}135*/136public AccessControlContext(ProtectionDomain[] context)137{138if (context.length == 0) {139this.context = null;140} else if (context.length == 1) {141if (context[0] != null) {142this.context = context.clone();143} else {144this.context = null;145}146} else {147List<ProtectionDomain> v = new ArrayList<>(context.length);148for (int i =0; i< context.length; i++) {149if ((context[i] != null) && (!v.contains(context[i])))150v.add(context[i]);151}152if (!v.isEmpty()) {153this.context = new ProtectionDomain[v.size()];154this.context = v.toArray(this.context);155}156}157}158159/**160* Create a new {@code AccessControlContext} with the given161* {@code AccessControlContext} and {@code DomainCombiner}.162* This constructor associates the provided163* {@code DomainCombiner} with the provided164* {@code AccessControlContext}.165*166* @param acc the {@code AccessControlContext} associated167* with the provided {@code DomainCombiner}.168*169* @param combiner the {@code DomainCombiner} to be associated170* with the provided {@code AccessControlContext}.171*172* @throws NullPointerException if the provided173* {@code context} is {@code null}.174*175* @throws SecurityException if a security manager is installed and the176* caller does not have the "createAccessControlContext"177* {@link SecurityPermission}178* @since 1.3179*/180public AccessControlContext(AccessControlContext acc,181@SuppressWarnings("removal") DomainCombiner combiner) {182183this(acc, combiner, false);184}185186/**187* package private to allow calls from ProtectionDomain without performing188* the security check for {@linkplain SecurityConstants#CREATE_ACC_PERMISSION}189* permission190*/191AccessControlContext(AccessControlContext acc,192@SuppressWarnings("removal") DomainCombiner combiner,193boolean preauthorized) {194if (!preauthorized) {195@SuppressWarnings("removal")196SecurityManager sm = System.getSecurityManager();197if (sm != null) {198sm.checkPermission(SecurityConstants.CREATE_ACC_PERMISSION);199this.isAuthorized = true;200}201} else {202this.isAuthorized = true;203}204205this.context = acc.context;206207// we do not need to run the combine method on the208// provided ACC. it was already "combined" when the209// context was originally retrieved.210//211// at this point in time, we simply throw away the old212// combiner and use the newly provided one.213this.combiner = combiner;214}215216/**217* package private for AccessController218*219* This "argument wrapper" context will be passed as the actual context220* parameter on an internal doPrivileged() call used in the implementation.221*/222AccessControlContext(ProtectionDomain caller, @SuppressWarnings("removal") DomainCombiner combiner,223AccessControlContext parent, AccessControlContext context,224Permission[] perms)225{226/*227* Combine the domains from the doPrivileged() context into our228* wrapper context, if necessary.229*/230ProtectionDomain[] callerPDs = null;231if (caller != null) {232callerPDs = new ProtectionDomain[] { caller };233}234if (context != null) {235if (combiner != null) {236this.context = combiner.combine(callerPDs, context.context);237} else {238this.context = combine(callerPDs, context.context);239}240} else {241/*242* Call combiner even if there is seemingly nothing to combine.243*/244if (combiner != null) {245this.context = combiner.combine(callerPDs, null);246} else {247this.context = combine(callerPDs, null);248}249}250this.combiner = combiner;251252Permission[] tmp = null;253if (perms != null) {254tmp = new Permission[perms.length];255for (int i=0; i < perms.length; i++) {256if (perms[i] == null) {257throw new NullPointerException("permission can't be null");258}259260/*261* An AllPermission argument is equivalent to calling262* doPrivileged() without any limit permissions.263*/264if (perms[i].getClass() == AllPermission.class) {265parent = null;266}267// Add altPath into permission for compatibility.268tmp[i] = FilePermCompat.newPermPlusAltPath(perms[i]);269}270}271272/*273* For a doPrivileged() with limited privilege scope, initialize274* the relevant fields.275*276* The limitedContext field contains the union of all domains which277* are enclosed by this limited privilege scope. In other words,278* it contains all of the domains which could potentially be checked279* if none of the limiting permissions implied a requested permission.280*/281if (parent != null) {282this.limitedContext = combine(parent.context, parent.limitedContext);283this.isLimited = true;284this.isWrapped = true;285this.permissions = tmp;286this.parent = parent;287this.privilegedContext = context; // used in checkPermission2()288}289this.isAuthorized = true;290}291292293/**294* package private constructor for AccessController.getContext()295*/296297AccessControlContext(ProtectionDomain[] context,298boolean isPrivileged)299{300this.context = context;301this.isPrivileged = isPrivileged;302this.isAuthorized = true;303}304305/**306* Constructor for JavaSecurityAccess.doIntersectionPrivilege()307*/308AccessControlContext(ProtectionDomain[] context,309AccessControlContext privilegedContext)310{311this.context = context;312this.privilegedContext = privilegedContext;313this.isPrivileged = true;314}315316/**317* Returns this context's context.318*/319ProtectionDomain[] getContext() {320return context;321}322323/**324* Returns true if this context is privileged.325*/326boolean isPrivileged()327{328return isPrivileged;329}330331/**332* get the assigned combiner from the privileged or inherited context333*/334@SuppressWarnings("removal")335DomainCombiner getAssignedCombiner() {336AccessControlContext acc;337if (isPrivileged) {338acc = privilegedContext;339} else {340acc = AccessController.getInheritedAccessControlContext();341}342if (acc != null) {343return acc.combiner;344}345return null;346}347348/**349* Get the {@code DomainCombiner} associated with this350* {@code AccessControlContext}.351*352* @return the {@code DomainCombiner} associated with this353* {@code AccessControlContext}, or {@code null}354* if there is none.355*356* @throws SecurityException if a security manager is installed and357* the caller does not have the "getDomainCombiner"358* {@link SecurityPermission}359* @since 1.3360*/361@SuppressWarnings("removal")362public DomainCombiner getDomainCombiner() {363364SecurityManager sm = System.getSecurityManager();365if (sm != null) {366sm.checkPermission(SecurityConstants.GET_COMBINER_PERMISSION);367}368return getCombiner();369}370371/**372* package private for AccessController373*/374@SuppressWarnings("removal")375DomainCombiner getCombiner() {376return combiner;377}378379boolean isAuthorized() {380return isAuthorized;381}382383/**384* Determines whether the access request indicated by the385* specified permission should be allowed or denied, based on386* the security policy currently in effect, and the context in387* this object. The request is allowed only if every ProtectionDomain388* in the context implies the permission. Otherwise the request is389* denied.390*391* <p>392* This method quietly returns if the access request393* is permitted, or throws a suitable AccessControlException otherwise.394*395* @param perm the requested permission.396*397* @throws AccessControlException if the specified permission398* is not permitted, based on the current security policy and the399* context encapsulated by this object.400* @throws NullPointerException if the permission to check for is null.401*/402@SuppressWarnings("removal")403public void checkPermission(Permission perm)404throws AccessControlException405{406boolean dumpDebug = false;407408if (perm == null) {409throw new NullPointerException("permission can't be null");410}411if (getDebug() != null) {412// If "codebase" is not specified, we dump the info by default.413dumpDebug = !Debug.isOn("codebase=");414if (!dumpDebug) {415// If "codebase" is specified, only dump if the specified code416// value is in the stack.417for (int i = 0; context != null && i < context.length; i++) {418if (context[i].getCodeSource() != null &&419context[i].getCodeSource().getLocation() != null &&420Debug.isOn("codebase=" + context[i].getCodeSource().getLocation().toString())) {421dumpDebug = true;422break;423}424}425}426427dumpDebug &= !Debug.isOn("permission=") ||428Debug.isOn("permission=" + perm.getClass().getCanonicalName());429430if (dumpDebug && Debug.isOn("stack")) {431Thread.dumpStack();432}433434if (dumpDebug && Debug.isOn("domain")) {435if (context == null) {436debug.println("domain (context is null)");437} else {438for (int i=0; i< context.length; i++) {439debug.println("domain "+i+" "+context[i]);440}441}442}443}444445/*446* iterate through the ProtectionDomains in the context.447* Stop at the first one that doesn't allow the448* requested permission (throwing an exception).449*450*/451452/* if ctxt is null, all we had on the stack were system domains,453or the first domain was a Privileged system domain. This454is to make the common case for system code very fast */455456if (context == null) {457checkPermission2(perm);458return;459}460461for (int i=0; i< context.length; i++) {462if (context[i] != null && !context[i].impliesWithAltFilePerm(perm)) {463if (dumpDebug) {464debug.println("access denied " + perm);465}466467if (Debug.isOn("failure") && debug != null) {468// Want to make sure this is always displayed for failure,469// but do not want to display again if already displayed470// above.471if (!dumpDebug) {472debug.println("access denied " + perm);473}474Thread.dumpStack();475final ProtectionDomain pd = context[i];476final Debug db = debug;477AccessController.doPrivileged (new PrivilegedAction<>() {478public Void run() {479db.println("domain that failed "+pd);480return null;481}482});483}484throw new AccessControlException("access denied "+perm, perm);485}486}487488// allow if all of them allowed access489if (dumpDebug) {490debug.println("access allowed "+perm);491}492493checkPermission2(perm);494}495496/*497* Check the domains associated with the limited privilege scope.498*/499private void checkPermission2(Permission perm) {500if (!isLimited) {501return;502}503504/*505* Check the doPrivileged() context parameter, if present.506*/507if (privilegedContext != null) {508privilegedContext.checkPermission2(perm);509}510511/*512* Ignore the limited permissions and parent fields of a wrapper513* context since they were already carried down into the unwrapped514* context.515*/516if (isWrapped) {517return;518}519520/*521* Try to match any limited privilege scope.522*/523if (permissions != null) {524Class<?> permClass = perm.getClass();525for (int i=0; i < permissions.length; i++) {526Permission limit = permissions[i];527if (limit.getClass().equals(permClass) && limit.implies(perm)) {528return;529}530}531}532533/*534* Check the limited privilege scope up the call stack or the inherited535* parent thread call stack of this ACC.536*/537if (parent != null) {538/*539* As an optimization, if the parent context is the inherited call540* stack context from a parent thread then checking the protection541* domains of the parent context is redundant since they have542* already been merged into the child thread's context by543* optimize(). When parent is set to an inherited context this544* context was not directly created by a limited scope545* doPrivileged() and it does not have its own limited permissions.546*/547if (permissions == null) {548parent.checkPermission2(perm);549} else {550parent.checkPermission(perm);551}552}553}554555/**556* Take the stack-based context (this) and combine it with the557* privileged or inherited context, if need be. Any limited558* privilege scope is flagged regardless of whether the assigned559* context comes from an immediately enclosing limited doPrivileged().560* The limited privilege scope can indirectly flow from the inherited561* parent thread or an assigned context previously captured by getContext().562*/563@SuppressWarnings("removal")564AccessControlContext optimize() {565// the assigned (privileged or inherited) context566AccessControlContext acc;567DomainCombiner combiner = null;568AccessControlContext parent = null;569Permission[] permissions = null;570571if (isPrivileged) {572acc = privilegedContext;573if (acc != null) {574/*575* If the context is from a limited scope doPrivileged() then576* copy the permissions and parent fields out of the wrapper577* context that was created to hold them.578*/579if (acc.isWrapped) {580permissions = acc.permissions;581parent = acc.parent;582}583}584} else {585acc = AccessController.getInheritedAccessControlContext();586if (acc != null) {587/*588* If the inherited context is constrained by a limited scope589* doPrivileged() then set it as our parent so we will process590* the non-domain-related state.591*/592if (acc.isLimited) {593parent = acc;594}595}596}597598// this.context could be null if only system code is on the stack;599// in that case, ignore the stack context600boolean skipStack = (context == null);601602// acc.context could be null if only system code was involved;603// in that case, ignore the assigned context604boolean skipAssigned = (acc == null || acc.context == null);605ProtectionDomain[] assigned = (skipAssigned) ? null : acc.context;606ProtectionDomain[] pd;607608// if there is no enclosing limited privilege scope on the stack or609// inherited from a parent thread610boolean skipLimited = ((acc == null || !acc.isWrapped) && parent == null);611612if (acc != null && acc.combiner != null) {613// let the assigned acc's combiner do its thing614if (getDebug() != null) {615debug.println("AccessControlContext invoking the Combiner");616}617618// No need to clone current and assigned.context619// combine() will not update them620combiner = acc.combiner;621pd = combiner.combine(context, assigned);622} else {623if (skipStack) {624if (skipAssigned) {625calculateFields(acc, parent, permissions);626return this;627} else if (skipLimited) {628return acc;629}630} else if (assigned != null) {631if (skipLimited) {632// optimization: if there is a single stack domain and633// that domain is already in the assigned context; no634// need to combine635if (context.length == 1 && context[0] == assigned[0]) {636return acc;637}638}639}640641pd = combine(context, assigned);642if (skipLimited && !skipAssigned && pd == assigned) {643return acc;644} else if (skipAssigned && pd == context) {645calculateFields(acc, parent, permissions);646return this;647}648}649650// Reuse existing ACC651this.context = pd;652this.combiner = combiner;653this.isPrivileged = false;654655calculateFields(acc, parent, permissions);656return this;657}658659660/*661* Combine the current (stack) and assigned domains.662*/663private static ProtectionDomain[] combine(ProtectionDomain[] current,664ProtectionDomain[] assigned) {665666// current could be null if only system code is on the stack;667// in that case, ignore the stack context668boolean skipStack = (current == null);669670// assigned could be null if only system code was involved;671// in that case, ignore the assigned context672boolean skipAssigned = (assigned == null);673674int slen = (skipStack) ? 0 : current.length;675676// optimization: if there is no assigned context and the stack length677// is less then or equal to two; there is no reason to compress the678// stack context, it already is679if (skipAssigned && slen <= 2) {680return current;681}682683int n = (skipAssigned) ? 0 : assigned.length;684685// now we combine both of them, and create a new context686ProtectionDomain[] pd = new ProtectionDomain[slen + n];687688// first copy in the assigned context domains, no need to compress689if (!skipAssigned) {690System.arraycopy(assigned, 0, pd, 0, n);691}692693// now add the stack context domains, discarding nulls and duplicates694outer:695for (int i = 0; i < slen; i++) {696ProtectionDomain sd = current[i];697if (sd != null) {698for (int j = 0; j < n; j++) {699if (sd == pd[j]) {700continue outer;701}702}703pd[n++] = sd;704}705}706707// if length isn't equal, we need to shorten the array708if (n != pd.length) {709// optimization: if we didn't really combine anything710if (!skipAssigned && n == assigned.length) {711return assigned;712} else if (skipAssigned && n == slen) {713return current;714}715ProtectionDomain[] tmp = new ProtectionDomain[n];716System.arraycopy(pd, 0, tmp, 0, n);717pd = tmp;718}719720return pd;721}722723724/*725* Calculate the additional domains that could potentially be reached via726* limited privilege scope. Mark the context as being subject to limited727* privilege scope unless the reachable domains (if any) are already728* contained in this domain context (in which case any limited729* privilege scope checking would be redundant).730*/731private void calculateFields(AccessControlContext assigned,732AccessControlContext parent, Permission[] permissions)733{734ProtectionDomain[] parentLimit = null;735ProtectionDomain[] assignedLimit = null;736ProtectionDomain[] newLimit;737738parentLimit = (parent != null)? parent.limitedContext: null;739assignedLimit = (assigned != null)? assigned.limitedContext: null;740newLimit = combine(parentLimit, assignedLimit);741if (newLimit != null) {742if (context == null || !containsAllPDs(newLimit, context)) {743this.limitedContext = newLimit;744this.permissions = permissions;745this.parent = parent;746this.isLimited = true;747}748}749}750751752/**753* Checks two AccessControlContext objects for equality.754* Checks that {@code obj} is755* an AccessControlContext and has the same set of ProtectionDomains756* as this context.757*758* @param obj the object we are testing for equality with this object.759* @return true if {@code obj} is an AccessControlContext, and has the760* same set of ProtectionDomains as this context, false otherwise.761*/762public boolean equals(Object obj) {763if (obj == this)764return true;765766return obj instanceof AccessControlContext that767&& equalContext(that)768&& equalLimitedContext(that);769}770771/*772* Compare for equality based on state that is free of limited773* privilege complications.774*/775private boolean equalContext(AccessControlContext that) {776if (!equalPDs(this.context, that.context))777return false;778779if (this.combiner == null && that.combiner != null)780return false;781782if (this.combiner != null && !this.combiner.equals(that.combiner))783return false;784785return true;786}787788private boolean equalPDs(ProtectionDomain[] a, ProtectionDomain[] b) {789if (a == null) {790return (b == null);791}792793if (b == null)794return false;795796if (!(containsAllPDs(a, b) && containsAllPDs(b, a)))797return false;798799return true;800}801802/*803* Compare for equality based on state that is captured during a804* call to AccessController.getContext() when a limited privilege805* scope is in effect.806*/807private boolean equalLimitedContext(AccessControlContext that) {808if (that == null)809return false;810811/*812* If neither instance has limited privilege scope then we're done.813*/814if (!this.isLimited && !that.isLimited)815return true;816817/*818* If only one instance has limited privilege scope then we're done.819*/820if (!(this.isLimited && that.isLimited))821return false;822823/*824* Wrapped instances should never escape outside the implementation825* this class and AccessController so this will probably never happen826* but it only makes any sense to compare if they both have the same827* isWrapped state.828*/829if ((this.isWrapped && !that.isWrapped) ||830(!this.isWrapped && that.isWrapped)) {831return false;832}833834if (this.permissions == null && that.permissions != null)835return false;836837if (this.permissions != null && that.permissions == null)838return false;839840if (!(this.containsAllLimits(that) && that.containsAllLimits(this)))841return false;842843/*844* Skip through any wrapped contexts.845*/846AccessControlContext thisNextPC = getNextPC(this);847AccessControlContext thatNextPC = getNextPC(that);848849/*850* The protection domains and combiner of a privilegedContext are851* not relevant because they have already been included in the context852* of this instance by optimize() so we only care about any limited853* privilege state they may have.854*/855if (thisNextPC == null && thatNextPC != null && thatNextPC.isLimited)856return false;857858if (thisNextPC != null && !thisNextPC.equalLimitedContext(thatNextPC))859return false;860861if (this.parent == null && that.parent != null)862return false;863864if (this.parent != null && !this.parent.equals(that.parent))865return false;866867return true;868}869870/*871* Follow the privilegedContext link making our best effort to skip872* through any wrapper contexts.873*/874private static AccessControlContext getNextPC(AccessControlContext acc) {875while (acc != null && acc.privilegedContext != null) {876acc = acc.privilegedContext;877if (!acc.isWrapped)878return acc;879}880return null;881}882883private static boolean containsAllPDs(ProtectionDomain[] thisContext,884ProtectionDomain[] thatContext) {885boolean match = false;886887//888// ProtectionDomains within an ACC currently cannot be null889// and this is enforced by the constructor and the various890// optimize methods. However, historically this logic made attempts891// to support the notion of a null PD and therefore this logic continues892// to support that notion.893ProtectionDomain thisPd;894for (int i = 0; i < thisContext.length; i++) {895match = false;896if ((thisPd = thisContext[i]) == null) {897for (int j = 0; (j < thatContext.length) && !match; j++) {898match = (thatContext[j] == null);899}900} else {901Class<?> thisPdClass = thisPd.getClass();902ProtectionDomain thatPd;903for (int j = 0; (j < thatContext.length) && !match; j++) {904thatPd = thatContext[j];905906// Class check required to avoid PD exposure (4285406)907match = (thatPd != null &&908thisPdClass == thatPd.getClass() && thisPd.equals(thatPd));909}910}911if (!match) return false;912}913return match;914}915916private boolean containsAllLimits(AccessControlContext that) {917boolean match = false;918Permission thisPerm;919920if (this.permissions == null && that.permissions == null)921return true;922923for (int i = 0; i < this.permissions.length; i++) {924Permission limit = this.permissions[i];925Class <?> limitClass = limit.getClass();926match = false;927for (int j = 0; (j < that.permissions.length) && !match; j++) {928Permission perm = that.permissions[j];929match = (limitClass.equals(perm.getClass()) &&930limit.equals(perm));931}932if (!match) return false;933}934return match;935}936937938/**939* Returns the hash code value for this context. The hash code940* is computed by exclusive or-ing the hash code of all the protection941* domains in the context together.942*943* @return a hash code value for this context.944*/945946public int hashCode() {947int hashCode = 0;948949if (context == null)950return hashCode;951952for (int i =0; i < context.length; i++) {953if (context[i] != null)954hashCode ^= context[i].hashCode();955}956957return hashCode;958}959}960961962