      1 /*
      2  * Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
      3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      4  *
      5  * This code is free software; you can redistribute it and/or modify it
      6  * under the terms of the GNU General Public License version 2 only, as
      7  * published by the Free Software Foundation.  Oracle designates this
      8  * particular file as subject to the "Classpath" exception as provided
      9  * by Oracle in the LICENSE file that accompanied this code.
     10  *
     11  * This code is distributed in the hope that it will be useful, but WITHOUT
     12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     14  * version 2 for more details (a copy is included in the LICENSE file that
     15  * accompanied this code).
     16  *
     17  * You should have received a copy of the GNU General Public License version
     18  * 2 along with this work; if not, write to the Free Software Foundation,
     19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     20  *
     21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     22  * or visit www.oracle.com if you need additional information or have any
     23  * questions.
     24  */
     25
     26 package java.security;
     27
     28 import java.util.ArrayList;
     29 import java.util.List;
     30
     31 import sun.security.util.Debug;
     32 import sun.security.util.FilePermCompat;
     33 import sun.security.util.SecurityConstants;
     34
     35
     36 /**
     37  * An AccessControlContext is used to make system resource access decisions
     38  * based on the context it encapsulates.
     39  *
     40  * <p>More specifically, it encapsulates a context and
     41  * has a single method, {@code checkPermission},
     42  * that is equivalent to the {@code checkPermission} method
     43  * in the AccessController class, with one difference: The AccessControlContext
     44  * {@code checkPermission} method makes access decisions based on the
     45  * context it encapsulates,
     46  * rather than that of the current execution thread.
     47  *
     48  * <p>Thus, the purpose of AccessControlContext is for those situations where
     49  * a security check that should be made within a given context
     50  * actually needs to be done from within a
     51  * <i>different</i> context (for example, from within a worker thread).
     52  *
     53  * <p> An AccessControlContext is created by calling the
     54  * {@code AccessController.getContext} method.
     55  * The {@code getContext} method takes a "snapshot"
     56  * of the current calling context, and places
     57  * it in an AccessControlContext object, which it returns. A sample call is
     58  * the following:
     59  *
     60  * <pre>
     61  *   AccessControlContext acc = AccessController.getContext()
     62  * </pre>
     63  *
     64  * <p>
     65  * Code within a different context can subsequently call the
     66  * {@code checkPermission} method on the
     67  * previously-saved AccessControlContext object. A sample call is the
     68  * following:
     69  *
     70  * <pre>
     71  *   acc.checkPermission(permission)
     72  * </pre>
     73  *
     74  * @see AccessController
     75  *
     76  * @author Roland Schemers
     77  * @since 1.2
     78  * @deprecated This class is only useful in conjunction with
     79  *       {@linkplain SecurityManager the Security Manager}, which is deprecated
     80  *       and subject to removal in a future release. Consequently, this class
     81  *       is also deprecated and subject to removal. There is no replacement for
     82  *       the Security Manager or this class.
     83  */
     84
     85 @Deprecated(since="17", forRemoval=true)
     86 public final class AccessControlContext {
     87
     88     private ProtectionDomain[] context;
     89     // isPrivileged and isAuthorized are referenced by the VM - do not remove
     90     // or change their names
     91     private boolean isPrivileged;
     92     private boolean isAuthorized = false;
     93
     94     // Note: This field is directly used by the virtual machine
     95     // native codes. Don't touch it.
     96     private AccessControlContext privilegedContext;
     97
     98     private DomainCombiner combiner = null;
     99
    100     // limited privilege scope
    101     private Permission[] permissions;
    102     private AccessControlContext parent;
    103     private boolean isWrapped;
    104
    105     // is constrained by limited privilege scope?
    106     private boolean isLimited;
    107     private ProtectionDomain[] limitedContext;
    108
    109     private static boolean debugInit = false;
    110     private static Debug debug = null;
    111
    112     static Debug getDebug()
    113     {
    114         if (debugInit)
    115             return debug;
    116         else {
    117             if (Policy.isSet()) {
    118                 debug = Debug.getInstance("access");
    119                 debugInit = true;
    120             }
    121             return debug;
    122         }
    123     }
    124
    125     /**
    126      * Create an AccessControlContext with the given array of ProtectionDomains.
    127      * Context must not be null. Duplicate domains will be removed from the
    128      * context.
    129      *
    130      * @param context the ProtectionDomains associated with this context.
    131      * The non-duplicate domains are copied from the array. Subsequent
    132      * changes to the array will not affect this AccessControlContext.
    133      * @throws NullPointerException if {@code context} is {@code null}
    134      */
    135     public AccessControlContext(ProtectionDomain[] context)
    136     {
    137         if (context.length == 0) {
    138             this.context = null;
    139         } else if (context.length == 1) {
    140             if (context[0] != null) {
    141                 this.context = context.clone();
    142             } else {
    143                 this.context = null;
    144             }
    145         } else {
    146             List<ProtectionDomain> v = new ArrayList<>(context.length);
    147             for (int i =0; i< context.length; i++) {
    148                 if ((context[i] != null) &&  (!v.contains(context[i])))
    149                     v.add(context[i]);
    150             }
    151             if (!v.isEmpty()) {
    152                 this.context = new ProtectionDomain[v.size()];
    153                 this.context = v.toArray(this.context);
    154             }
    155         }
    156     }
    157
    158     /**
    159      * Create a new {@code AccessControlContext} with the given
    160      * {@code AccessControlContext} and {@code DomainCombiner}.
    161      * This constructor associates the provided
    162      * {@code DomainCombiner} with the provided
    163      * {@code AccessControlContext}.
    164      *
    165      * @param acc the {@code AccessControlContext} associated
    166      *          with the provided {@code DomainCombiner}.
    167      *
    168      * @param combiner the {@code DomainCombiner} to be associated
    169      *          with the provided {@code AccessControlContext}.
    170      *
    171      * @throws    NullPointerException if the provided
    172      *          {@code context} is {@code null}.
    173      *
    174      * @throws    SecurityException if a security manager is installed and the
    175      *          caller does not have the "createAccessControlContext"
    176      *          {@link SecurityPermission}
    177      * @since 1.3
    178      */
    179     public AccessControlContext(AccessControlContext acc,
    180                                 DomainCombiner combiner) {
    181
    182         this(acc, combiner, false);
    183     }
    184
    185     /**
    186      * package private to allow calls from ProtectionDomain without performing
    187      * the security check for {@linkplain SecurityConstants#CREATE_ACC_PERMISSION}
    188      * permission
    189      */
    190     AccessControlContext(AccessControlContext acc,
    191                         DomainCombiner combiner,
    192                         boolean preauthorized) {
    193         if (!preauthorized) {
    194             SecurityManager sm = System.getSecurityManager();
    195             if (sm != null) {
    196                 sm.checkPermission(SecurityConstants.CREATE_ACC_PERMISSION);
    197                 this.isAuthorized = true;
    198             }
    199         } else {
    200             this.isAuthorized = true;
    201         }
    202
    203         this.context = acc.context;
    204
    205         // we do not need to run the combine method on the
    206         // provided ACC.  it was already "combined" when the
    207         // context was originally retrieved.
    208         //
    209         // at this point in time, we simply throw away the old
    210         // combiner and use the newly provided one.
    211         this.combiner = combiner;
    212     }
    213
    214     /**
    215      * package private for AccessController
    216      *
    217      * This "argument wrapper" context will be passed as the actual context
    218      * parameter on an internal doPrivileged() call used in the implementation.
    219      */
    220     AccessControlContext(ProtectionDomain caller, DomainCombiner combiner,
    221         AccessControlContext parent, AccessControlContext context,
    222         Permission[] perms)
    223     {
    224         /*
    225          * Combine the domains from the doPrivileged() context into our
    226          * wrapper context, if necessary.
    227          */
    228         ProtectionDomain[] callerPDs = null;
    229         if (caller != null) {
    230              callerPDs = new ProtectionDomain[] { caller };
    231         }
    232         if (context != null) {
    233             if (combiner != null) {
    234                 this.context = combiner.combine(callerPDs, context.context);
    235             } else {
    236                 this.context = combine(callerPDs, context.context);
    237             }
    238         } else {
    239             /*
    240              * Call combiner even if there is seemingly nothing to combine.
    241              */
    242             if (combiner != null) {
    243                 this.context = combiner.combine(callerPDs, null);
    244             } else {
    245                 this.context = combine(callerPDs, null);
    246             }
    247         }
    248         this.combiner = combiner;
    249
    250         Permission[] tmp = null;
    251         if (perms != null) {
    252             tmp = new Permission[perms.length];
    253             for (int i=0; i < perms.length; i++) {
    254                 if (perms[i] == null) {
    255                     throw new NullPointerException("permission can't be null");
    256                 }
    257
    258                 /*
    259                  * An AllPermission argument is equivalent to calling
    260                  * doPrivileged() without any limit permissions.
    261                  */
    262                 if (perms[i].getClass() == AllPermission.class) {
    263                     parent = null;
    264                 }
    265                 // Add altPath into permission for compatibility.
    266                 tmp[i] = FilePermCompat.newPermPlusAltPath(perms[i]);
    267             }
    268         }
    269
    270         /*
    271          * For a doPrivileged() with limited privilege scope, initialize
    272          * the relevant fields.
    273          *
    274          * The limitedContext field contains the union of all domains which
    275          * are enclosed by this limited privilege scope. In other words,
    276          * it contains all of the domains which could potentially be checked
    277          * if none of the limiting permissions implied a requested permission.
    278          */
    279         if (parent != null) {
    280             this.limitedContext = combine(parent.context, parent.limitedContext);
    281             this.isLimited = true;
    282             this.isWrapped = true;
    283             this.permissions = tmp;
    284             this.parent = parent;
    285             this.privilegedContext = context; // used in checkPermission2()
    286         }
    287         this.isAuthorized = true;
    288     }
    289
    290
    291     /**
    292      * package private constructor for AccessController.getContext()
    293      */
    294
    295     AccessControlContext(ProtectionDomain[] context,
    296                          boolean isPrivileged)
    297     {
    298         this.context = context;
    299         this.isPrivileged = isPrivileged;
    300         this.isAuthorized = true;
    301     }
    302
    303     /**
    304      * Constructor for JavaSecurityAccess.doIntersectionPrivilege()
    305      */
    306     AccessControlContext(ProtectionDomain[] context,
    307                          AccessControlContext privilegedContext)
    308     {
    309         this.context = context;
    310         this.privilegedContext = privilegedContext;
    311         this.isPrivileged = true;
    312     }
    313
    314     /**
    315      * Returns this context's context.
    316      */
    317     ProtectionDomain[] getContext() {
    318         return context;
    319     }
    320
    321     /**
    322      * Returns true if this context is privileged.
    323      */
    324     boolean isPrivileged()
    325     {
    326         return isPrivileged;
    327     }
    328
    329     /**
    330      * get the assigned combiner from the privileged or inherited context
    331      */
    332     DomainCombiner getAssignedCombiner() {
    333         AccessControlContext acc;
    334         if (isPrivileged) {
    335             acc = privilegedContext;
    336         } else {
    337             acc = AccessController.getInheritedAccessControlContext();
    338         }
    339         if (acc != null) {
    340             return acc.combiner;
    341         }
    342         return null;
    343     }
    344
    345     /**
    346      * Get the {@code DomainCombiner} associated with this
    347      * {@code AccessControlContext}.
    348      *
    349      * @return the {@code DomainCombiner} associated with this
    350      *          {@code AccessControlContext}, or {@code null}
    351      *          if there is none.
    352      *
    353      * @throws    SecurityException if a security manager is installed and
    354      *          the caller does not have the "getDomainCombiner"
    355      *          {@link SecurityPermission}
    356      * @since 1.3
    357      */
    358     public DomainCombiner getDomainCombiner() {
    359
    360         SecurityManager sm = System.getSecurityManager();
    361         if (sm != null) {
    362             sm.checkPermission(SecurityConstants.GET_COMBINER_PERMISSION);
    363         }
    364         return getCombiner();
    365     }
    366
    367     /**
    368      * package private for AccessController
    369      */
    370     DomainCombiner getCombiner() {
    371         return combiner;
    372     }
    373
    374     boolean isAuthorized() {
    375         return isAuthorized;
    376     }
    377
    378     /**
    379      * Determines whether the access request indicated by the
    380      * specified permission should be allowed or denied, based on
    381      * the security policy currently in effect, and the context in
    382      * this object. The request is allowed only if every ProtectionDomain
    383      * in the context implies the permission. Otherwise the request is
    384      * denied.
    385      *
    386      * <p>
    387      * This method quietly returns if the access request
    388      * is permitted, or throws a suitable AccessControlException otherwise.
    389      *
    390      * @param perm the requested permission.
    391      *
    392      * @throws    AccessControlException if the specified permission
    393      * is not permitted, based on the current security policy and the
    394      * context encapsulated by this object.
    395      * @throws    NullPointerException if the permission to check for is null.
    396      */
    397     public void checkPermission(Permission perm)
    398         throws AccessControlException
    399     {
    400         boolean dumpDebug = false;
    401
    402         if (perm == null) {
    403             throw new NullPointerException("permission can't be null");
    404         }
    405         if (getDebug() != null) {
    406             // If "codebase" is not specified, we dump the info by default.
    407             dumpDebug = !Debug.isOn("codebase=");
    408             if (!dumpDebug) {
    409                 // If "codebase" is specified, only dump if the specified code
    410                 // value is in the stack.
    411                 for (int i = 0; context != null && i < context.length; i++) {
    412                     if (context[i].getCodeSource() != null &&
    413                         context[i].getCodeSource().getLocation() != null &&
    414                         Debug.isOn("codebase=" + context[i].getCodeSource().getLocation().toString())) {
    415                         dumpDebug = true;
    416                         break;
    417                     }
    418                 }
    419             }
    420
    421             dumpDebug &= !Debug.isOn("permission=") ||
    422                 Debug.isOn("permission=" + perm.getClass().getCanonicalName());
    423
    424             if (dumpDebug && Debug.isOn("stack")) {
    425                 Thread.dumpStack();
    426             }
    427
    428             if (dumpDebug && Debug.isOn("domain")) {
    429                 if (context == null) {
    430                     debug.println("domain (context is null)");
    431                 } else {
    432                     for (int i=0; i< context.length; i++) {
    433                         debug.println("domain "+i+" "+context[i]);
    434                     }
    435                 }
    436             }
    437         }
    438
    439         /*
    440          * iterate through the ProtectionDomains in the context.
    441          * Stop at the first one that doesn't allow the
    442          * requested permission (throwing an exception).
    443          *
    444          */
    445
    446         /* if ctxt is null, all we had on the stack were system domains,
    447            or the first domain was a Privileged system domain. This
    448            is to make the common case for system code very fast */
    449
    450         if (context == null) {
    451             checkPermission2(perm);
    452             return;
    453         }
    454
    455         for (int i=0; i< context.length; i++) {
    456             if (context[i] != null && !context[i].impliesWithAltFilePerm(perm)) {
    457                 if (dumpDebug) {
    458                     debug.println("access denied " + perm);
    459                 }
    460
    461                 if (Debug.isOn("failure") && debug != null) {
    462                     // Want to make sure this is always displayed for failure,
    463                     // but do not want to display again if already displayed
    464                     // above.
    465                     if (!dumpDebug) {
    466                         debug.println("access denied " + perm);
    467                     }
    468                     Thread.dumpStack();
    469                     final ProtectionDomain pd = context[i];
    470                     final Debug db = debug;
    471                     AccessController.doPrivileged (new PrivilegedAction<>() {
    472                         public Void run() {
    473                             db.println("domain that failed "+pd);
    474                             return null;
    475                         }
    476                     });
    477                 }
    478                 throw new AccessControlException("access denied "+perm, perm);
    479             }
    480         }
    481
    482         // allow if all of them allowed access
    483         if (dumpDebug) {
    484             debug.println("access allowed "+perm);
    485         }
    486
    487         checkPermission2(perm);
    488     }
    489
    490     /*
    491      * Check the domains associated with the limited privilege scope.
    492      */
    493     private void checkPermission2(Permission perm) {
    494         if (!isLimited) {
    495             return;
    496         }
    497
    498         /*
    499          * Check the doPrivileged() context parameter, if present.
    500          */
    501         if (privilegedContext != null) {
    502             privilegedContext.checkPermission2(perm);
    503         }
    504
    505         /*
    506          * Ignore the limited permissions and parent fields of a wrapper
    507          * context since they were already carried down into the unwrapped
    508          * context.
    509          */
    510         if (isWrapped) {
    511             return;
    512         }
    513
    514         /*
    515          * Try to match any limited privilege scope.
    516          */
    517         if (permissions != null) {
    518             Class<?> permClass = perm.getClass();
    519             for (int i=0; i < permissions.length; i++) {
    520                 Permission limit = permissions[i];
    521                 if (limit.getClass().equals(permClass) && limit.implies(perm)) {
    522                     return;
    523                 }
    524             }
    525         }
    526
    527         /*
    528          * Check the limited privilege scope up the call stack or the inherited
    529          * parent thread call stack of this ACC.
    530          */
    531         if (parent != null) {
    532             /*
    533              * As an optimization, if the parent context is the inherited call
    534              * stack context from a parent thread then checking the protection
    535              * domains of the parent context is redundant since they have
    536              * already been merged into the child thread's context by
    537              * optimize(). When parent is set to an inherited context this
    538              * context was not directly created by a limited scope
    539              * doPrivileged() and it does not have its own limited permissions.
    540              */
    541             if (permissions == null) {
    542                 parent.checkPermission2(perm);
    543             } else {
    544                 parent.checkPermission(perm);
    545             }
    546         }
    547     }
    548
    549     /**
    550      * Take the stack-based context (this) and combine it with the
    551      * privileged or inherited context, if need be. Any limited
    552      * privilege scope is flagged regardless of whether the assigned
    553      * context comes from an immediately enclosing limited doPrivileged().
    554      * The limited privilege scope can indirectly flow from the inherited
    555      * parent thread or an assigned context previously captured by getContext().
    556      */
    557     AccessControlContext optimize() {
    558         // the assigned (privileged or inherited) context
    559         AccessControlContext acc;
    560         DomainCombiner combiner = null;
    561         AccessControlContext parent = null;
    562         Permission[] permissions = null;
    563
    564         if (isPrivileged) {
    565             acc = privilegedContext;
    566             if (acc != null) {
    567                 /*
    568                  * If the context is from a limited scope doPrivileged() then
    569                  * copy the permissions and parent fields out of the wrapper
    570                  * context that was created to hold them.
    571                  */
    572                 if (acc.isWrapped) {
    573                     permissions = acc.permissions;
    574                     parent = acc.parent;
    575                 }
    576             }
    577         } else {
    578             acc = AccessController.getInheritedAccessControlContext();
    579             if (acc != null) {
    580                 /*
    581                  * If the inherited context is constrained by a limited scope
    582                  * doPrivileged() then set it as our parent so we will process
    583                  * the non-domain-related state.
    584                  */
    585                 if (acc.isLimited) {
    586                     parent = acc;
    587                 }
    588             }
    589         }
    590
    591         // this.context could be null if only system code is on the stack;
    592         // in that case, ignore the stack context
    593         boolean skipStack = (context == null);
    594
    595         // acc.context could be null if only system code was involved;
    596         // in that case, ignore the assigned context
    597         boolean skipAssigned = (acc == null || acc.context == null);
    598         ProtectionDomain[] assigned = (skipAssigned) ? null : acc.context;
    599         ProtectionDomain[] pd;
    600
    601         // if there is no enclosing limited privilege scope on the stack or
    602         // inherited from a parent thread
    603         boolean skipLimited = ((acc == null || !acc.isWrapped) && parent == null);
    604
    605         if (acc != null && acc.combiner != null) {
    606             // let the assigned acc's combiner do its thing
    607             if (getDebug() != null) {
    608                 debug.println("AccessControlContext invoking the Combiner");
    609             }
    610
    611             // No need to clone current and assigned.context
    612             // combine() will not update them
    613             combiner = acc.combiner;
    614             pd = combiner.combine(context, assigned);
    615         } else {
    616             if (skipStack) {
    617                 if (skipAssigned) {
    618                     calculateFields(acc, parent, permissions);
    619                     return this;
    620                 } else if (skipLimited) {
    621                     return acc;
    622                 }
    623             } else if (assigned != null) {
    624                 if (skipLimited) {
    625                     // optimization: if there is a single stack domain and
    626                     // that domain is already in the assigned context; no
    627                     // need to combine
    628                     if (context.length == 1 && context[0] == assigned[0]) {
    629                         return acc;
    630                     }
    631                 }
    632             }
    633
    634             pd = combine(context, assigned);
    635             if (skipLimited && !skipAssigned && pd == assigned) {
    636                 return acc;
    637             } else if (skipAssigned && pd == context) {
    638                 calculateFields(acc, parent, permissions);
    639                 return this;
    640             }
    641         }
    642
    643         // Reuse existing ACC
    644         this.context = pd;
    645         this.combiner = combiner;
    646         this.isPrivileged = false;
    647
    648         calculateFields(acc, parent, permissions);
    649         return this;
    650     }
    651
    652
    653     /*
    654      * Combine the current (stack) and assigned domains.
    655      */
    656     private static ProtectionDomain[] combine(ProtectionDomain[] current,
    657         ProtectionDomain[] assigned) {
    658
    659         // current could be null if only system code is on the stack;
    660         // in that case, ignore the stack context
    661         boolean skipStack = (current == null);
    662
    663         // assigned could be null if only system code was involved;
    664         // in that case, ignore the assigned context
    665         boolean skipAssigned = (assigned == null);
    666
    667         int slen = (skipStack) ? 0 : current.length;
    668
    669         // optimization: if there is no assigned context and the stack length
    670         // is less then or equal to two; there is no reason to compress the
    671         // stack context, it already is
    672         if (skipAssigned && slen <= 2) {
    673             return current;
    674         }
    675
    676         int n = (skipAssigned) ? 0 : assigned.length;
    677
    678         // now we combine both of them, and create a new context
    679         ProtectionDomain[] pd = new ProtectionDomain[slen + n];
    680
    681         // first copy in the assigned context domains, no need to compress
    682         if (!skipAssigned) {
    683             System.arraycopy(assigned, 0, pd, 0, n);
    684         }
    685
    686         // now add the stack context domains, discarding nulls and duplicates
    687     outer:
    688         for (int i = 0; i < slen; i++) {
    689             ProtectionDomain sd = current[i];
    690             if (sd != null) {
    691                 for (int j = 0; j < n; j++) {
    692                     if (sd == pd[j]) {
    693                         continue outer;
    694                     }
    695                 }
    696                 pd[n++] = sd;
    697             }
    698         }
    699
    700         // if length isn't equal, we need to shorten the array
    701         if (n != pd.length) {
    702             // optimization: if we didn't really combine anything
    703             if (!skipAssigned && n == assigned.length) {
    704                 return assigned;
    705             } else if (skipAssigned && n == slen) {
    706                 return current;
    707             }
    708             ProtectionDomain[] tmp = new ProtectionDomain[n];
    709             System.arraycopy(pd, 0, tmp, 0, n);
    710             pd = tmp;
    711         }
    712
    713         return pd;
    714     }
    715
    716
    717     /*
    718      * Calculate the additional domains that could potentially be reached via
    719      * limited privilege scope. Mark the context as being subject to limited
    720      * privilege scope unless the reachable domains (if any) are already
    721      * contained in this domain context (in which case any limited
    722      * privilege scope checking would be redundant).
    723      */
    724     private void calculateFields(AccessControlContext assigned,
    725         AccessControlContext parent, Permission[] permissions)
    726     {
    727         ProtectionDomain[] parentLimit = null;
    728         ProtectionDomain[] assignedLimit = null;
    729         ProtectionDomain[] newLimit;
    730
    731         parentLimit = (parent != null)? parent.limitedContext: null;
    732         assignedLimit = (assigned != null)? assigned.limitedContext: null;
    733         newLimit = combine(parentLimit, assignedLimit);
    734         if (newLimit != null) {
    735             if (context == null || !containsAllPDs(newLimit, context)) {
    736                 this.limitedContext = newLimit;
    737                 this.permissions = permissions;
    738                 this.parent = parent;
    739                 this.isLimited = true;
    740             }
    741         }
    742     }
    743
    744
    745     /**
    746      * Checks two AccessControlContext objects for equality.
    747      * Checks that {@code obj} is
    748      * an AccessControlContext and has the same set of ProtectionDomains
    749      * as this context.
    750      *
    751      * @param obj the object we are testing for equality with this object.
    752      * @return true if {@code obj} is an AccessControlContext, and has the
    753      * same set of ProtectionDomains as this context, false otherwise.
    754      */
    755     public boolean equals(Object obj) {
    756         if (obj == this)
    757             return true;
    758
    759         return obj instanceof AccessControlContext that
    760                 && equalContext(that)
    761                 && equalLimitedContext(that);
    762     }
    763
    764     /*
    765      * Compare for equality based on state that is free of limited
    766      * privilege complications.
    767      */
    768     private boolean equalContext(AccessControlContext that) {
    769         if (!equalPDs(this.context, that.context))
    770             return false;
    771
    772         if (this.combiner == null && that.combiner != null)
    773             return false;
    774
    775         if (this.combiner != null && !this.combiner.equals(that.combiner))
    776             return false;
    777
    778         return true;
    779     }
    780
    781     private boolean equalPDs(ProtectionDomain[] a, ProtectionDomain[] b) {
    782         if (a == null) {
    783             return (b == null);
    784         }
    785
    786         if (b == null)
    787             return false;
    788
    789         if (!(containsAllPDs(a, b) && containsAllPDs(b, a)))
    790             return false;
    791
    792         return true;
    793     }
    794
    795     /*
    796      * Compare for equality based on state that is captured during a
    797      * call to AccessController.getContext() when a limited privilege
    798      * scope is in effect.
    799      */
    800     private boolean equalLimitedContext(AccessControlContext that) {
    801         if (that == null)
    802             return false;
    803
    804         /*
    805          * If neither instance has limited privilege scope then we're done.
    806          */
    807         if (!this.isLimited && !that.isLimited)
    808             return true;
    809
    810         /*
    811          * If only one instance has limited privilege scope then we're done.
    812          */
    813          if (!(this.isLimited && that.isLimited))
    814              return false;
    815
    816         /*
    817          * Wrapped instances should never escape outside the implementation
    818          * this class and AccessController so this will probably never happen
    819          * but it only makes any sense to compare if they both have the same
    820          * isWrapped state.
    821          */
    822         if ((this.isWrapped && !that.isWrapped) ||
    823             (!this.isWrapped && that.isWrapped)) {
    824             return false;
    825         }
    826
    827         if (this.permissions == null && that.permissions != null)
    828             return false;
    829
    830         if (this.permissions != null && that.permissions == null)
    831             return false;
    832
    833         if (!(this.containsAllLimits(that) && that.containsAllLimits(this)))
    834             return false;
    835
    836         /*
    837          * Skip through any wrapped contexts.
    838          */
    839         AccessControlContext thisNextPC = getNextPC(this);
    840         AccessControlContext thatNextPC = getNextPC(that);
    841
    842         /*
    843          * The protection domains and combiner of a privilegedContext are
    844          * not relevant because they have already been included in the context
    845          * of this instance by optimize() so we only care about any limited
    846          * privilege state they may have.
    847          */
    848         if (thisNextPC == null && thatNextPC != null && thatNextPC.isLimited)
    849             return false;
    850
    851         if (thisNextPC != null && !thisNextPC.equalLimitedContext(thatNextPC))
    852             return false;
    853
    854         if (this.parent == null && that.parent != null)
    855             return false;
    856
    857         if (this.parent != null && !this.parent.equals(that.parent))
    858             return false;
    859
    860         return true;
    861     }
    862
    863     /*
    864      * Follow the privilegedContext link making our best effort to skip
    865      * through any wrapper contexts.
    866      */
    867     private static AccessControlContext getNextPC(AccessControlContext acc) {
    868         while (acc != null && acc.privilegedContext != null) {
    869             acc = acc.privilegedContext;
    870             if (!acc.isWrapped)
    871                 return acc;
    872         }
    873         return null;
    874     }
    875
    876     private static boolean containsAllPDs(ProtectionDomain[] thisContext,
    877         ProtectionDomain[] thatContext) {
    878         boolean match = false;
    879
    880         //
    881         // ProtectionDomains within an ACC currently cannot be null
    882         // and this is enforced by the constructor and the various
    883         // optimize methods. However, historically this logic made attempts
    884         // to support the notion of a null PD and therefore this logic continues
    885         // to support that notion.
    886         ProtectionDomain thisPd;
    887         for (int i = 0; i < thisContext.length; i++) {
    888             match = false;
    889             if ((thisPd = thisContext[i]) == null) {
    890                 for (int j = 0; (j < thatContext.length) && !match; j++) {
    891                     match = (thatContext[j] == null);
    892                 }
    893             } else {
    894                 Class<?> thisPdClass = thisPd.getClass();
    895                 ProtectionDomain thatPd;
    896                 for (int j = 0; (j < thatContext.length) && !match; j++) {
    897                     thatPd = thatContext[j];
    898
    899                     // Class check required to avoid PD exposure (4285406)
    900                     match = (thatPd != null &&
    901                         thisPdClass == thatPd.getClass() && thisPd.equals(thatPd));
    902                 }
    903             }
    904             if (!match) return false;
    905         }
    906         return match;
    907     }
    908
    909     private boolean containsAllLimits(AccessControlContext that) {
    910         boolean match = false;
    911         Permission thisPerm;
    912
    913         if (this.permissions == null && that.permissions == null)
    914             return true;
    915
    916         for (int i = 0; i < this.permissions.length; i++) {
    917             Permission limit = this.permissions[i];
    918             Class <?> limitClass = limit.getClass();
    919             match = false;
    920             for (int j = 0; (j < that.permissions.length) && !match; j++) {
    921                 Permission perm = that.permissions[j];
    922                 match = (limitClass.equals(perm.getClass()) &&
    923                     limit.equals(perm));
    924             }
    925             if (!match) return false;
    926         }
    927         return match;
    928     }
    929
    930
    931     /**
    932      * Returns the hash code value for this context. The hash code
    933      * is computed by exclusive or-ing the hash code of all the protection
    934      * domains in the context together.
    935      *
    936      * @return a hash code value for this context.
    937      */
    938
    939     public int hashCode() {
    940         int hashCode = 0;
    941
    942         if (context == null)
    943             return hashCode;
    944
    945         for (int i =0; i < context.length; i++) {
    946             if (context[i] != null)
    947                 hashCode ^= context[i].hashCode();
    948         }
    949
    950         return hashCode;
    951     }
    952 }
    --- EOF ---