Home | History | Annotate | Download | only in pm
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.pm;
     18 
     19 import android.content.pm.PackageParser;
     20 import android.content.pm.PackageUserState;
     21 import android.content.pm.SELinuxUtil;
     22 import android.content.pm.Signature;
     23 import android.os.Environment;
     24 import android.util.Slog;
     25 import android.util.Xml;
     26 
     27 import libcore.io.IoUtils;
     28 
     29 import org.xmlpull.v1.XmlPullParser;
     30 import org.xmlpull.v1.XmlPullParserException;
     31 
     32 import java.io.File;
     33 import java.io.FileReader;
     34 import java.io.IOException;
     35 import java.util.ArrayList;
     36 import java.util.Collections;
     37 import java.util.Comparator;
     38 import java.util.HashMap;
     39 import java.util.HashSet;
     40 import java.util.List;
     41 import java.util.Map;
     42 import java.util.Set;
     43 
     44 /**
     45  * Centralized access to SELinux MMAC (middleware MAC) implementation. This
     46  * class is responsible for loading the appropriate mac_permissions.xml file
     47  * as well as providing an interface for assigning seinfo values to apks.
     48  *
     49  * {@hide}
     50  */
     51 public final class SELinuxMMAC {
     52 
     53     static final String TAG = "SELinuxMMAC";
     54 
     55     private static final boolean DEBUG_POLICY = false;
     56     private static final boolean DEBUG_POLICY_INSTALL = DEBUG_POLICY || false;
     57     private static final boolean DEBUG_POLICY_ORDER = DEBUG_POLICY || false;
     58 
     59     // All policy stanzas read from mac_permissions.xml. This is also the lock
     60     // to synchronize access during policy load and access attempts.
     61     private static List<Policy> sPolicies = new ArrayList<>();
     62 
     63     /** Path to MAC permissions on system image */
     64     private static final File[] MAC_PERMISSIONS =
     65     { new File(Environment.getRootDirectory(), "/etc/selinux/plat_mac_permissions.xml"),
     66       new File(Environment.getVendorDirectory(), "/etc/selinux/nonplat_mac_permissions.xml") };
     67 
     68     // Append privapp to existing seinfo label
     69     private static final String PRIVILEGED_APP_STR = ":privapp";
     70 
     71     // Append v2 to existing seinfo label
     72     private static final String SANDBOX_V2_STR = ":v2";
     73 
     74     // Append targetSdkVersion=n to existing seinfo label where n is the app's targetSdkVersion
     75     private static final String TARGETSDKVERSION_STR = ":targetSdkVersion=";
     76 
     77     /**
     78      * Load the mac_permissions.xml file containing all seinfo assignments used to
     79      * label apps. The loaded mac_permissions.xml file is determined by the
     80      * MAC_PERMISSIONS class variable which is set at class load time which itself
     81      * is based on the USE_OVERRIDE_POLICY class variable. For further guidance on
     82      * the proper structure of a mac_permissions.xml file consult the source code
     83      * located at system/sepolicy/mac_permissions.xml.
     84      *
     85      * @return boolean indicating if policy was correctly loaded. A value of false
     86      *         typically indicates a structural problem with the xml or incorrectly
     87      *         constructed policy stanzas. A value of true means that all stanzas
     88      *         were loaded successfully; no partial loading is possible.
     89      */
     90     public static boolean readInstallPolicy() {
     91         // Temp structure to hold the rules while we parse the xml file
     92         List<Policy> policies = new ArrayList<>();
     93 
     94         FileReader policyFile = null;
     95         XmlPullParser parser = Xml.newPullParser();
     96         for (int i = 0; i < MAC_PERMISSIONS.length; i++) {
     97             try {
     98                 policyFile = new FileReader(MAC_PERMISSIONS[i]);
     99                 Slog.d(TAG, "Using policy file " + MAC_PERMISSIONS[i]);
    100 
    101                 parser.setInput(policyFile);
    102                 parser.nextTag();
    103                 parser.require(XmlPullParser.START_TAG, null, "policy");
    104 
    105                 while (parser.next() != XmlPullParser.END_TAG) {
    106                     if (parser.getEventType() != XmlPullParser.START_TAG) {
    107                         continue;
    108                     }
    109 
    110                     switch (parser.getName()) {
    111                         case "signer":
    112                             policies.add(readSignerOrThrow(parser));
    113                             break;
    114                         default:
    115                             skip(parser);
    116                     }
    117                 }
    118             } catch (IllegalStateException | IllegalArgumentException |
    119                      XmlPullParserException ex) {
    120                 StringBuilder sb = new StringBuilder("Exception @");
    121                 sb.append(parser.getPositionDescription());
    122                 sb.append(" while parsing ");
    123                 sb.append(MAC_PERMISSIONS[i]);
    124                 sb.append(":");
    125                 sb.append(ex);
    126                 Slog.w(TAG, sb.toString());
    127                 return false;
    128             } catch (IOException ioe) {
    129                 Slog.w(TAG, "Exception parsing " + MAC_PERMISSIONS[i], ioe);
    130                 return false;
    131             } finally {
    132                 IoUtils.closeQuietly(policyFile);
    133             }
    134         }
    135 
    136         // Now sort the policy stanzas
    137         PolicyComparator policySort = new PolicyComparator();
    138         Collections.sort(policies, policySort);
    139         if (policySort.foundDuplicate()) {
    140             Slog.w(TAG, "ERROR! Duplicate entries found parsing mac_permissions.xml files");
    141             return false;
    142         }
    143 
    144         synchronized (sPolicies) {
    145             sPolicies = policies;
    146 
    147             if (DEBUG_POLICY_ORDER) {
    148                 for (Policy policy : sPolicies) {
    149                     Slog.d(TAG, "Policy: " + policy.toString());
    150                 }
    151             }
    152         }
    153 
    154         return true;
    155     }
    156 
    157     /**
    158      * Loop over a signer tag looking for seinfo, package and cert tags. A {@link Policy}
    159      * instance will be created and returned in the process. During the pass all other
    160      * tag elements will be skipped.
    161      *
    162      * @param parser an XmlPullParser object representing a signer element.
    163      * @return the constructed {@link Policy} instance
    164      * @throws IOException
    165      * @throws XmlPullParserException
    166      * @throws IllegalArgumentException if any of the validation checks fail while
    167      *         parsing tag values.
    168      * @throws IllegalStateException if any of the invariants fail when constructing
    169      *         the {@link Policy} instance.
    170      */
    171     private static Policy readSignerOrThrow(XmlPullParser parser) throws IOException,
    172             XmlPullParserException {
    173 
    174         parser.require(XmlPullParser.START_TAG, null, "signer");
    175         Policy.PolicyBuilder pb = new Policy.PolicyBuilder();
    176 
    177         // Check for a cert attached to the signer tag. We allow a signature
    178         // to appear as an attribute as well as those attached to cert tags.
    179         String cert = parser.getAttributeValue(null, "signature");
    180         if (cert != null) {
    181             pb.addSignature(cert);
    182         }
    183 
    184         while (parser.next() != XmlPullParser.END_TAG) {
    185             if (parser.getEventType() != XmlPullParser.START_TAG) {
    186                 continue;
    187             }
    188 
    189             String tagName = parser.getName();
    190             if ("seinfo".equals(tagName)) {
    191                 String seinfo = parser.getAttributeValue(null, "value");
    192                 pb.setGlobalSeinfoOrThrow(seinfo);
    193                 readSeinfo(parser);
    194             } else if ("package".equals(tagName)) {
    195                 readPackageOrThrow(parser, pb);
    196             } else if ("cert".equals(tagName)) {
    197                 String sig = parser.getAttributeValue(null, "signature");
    198                 pb.addSignature(sig);
    199                 readCert(parser);
    200             } else {
    201                 skip(parser);
    202             }
    203         }
    204 
    205         return pb.build();
    206     }
    207 
    208     /**
    209      * Loop over a package element looking for seinfo child tags. If found return the
    210      * value attribute of the seinfo tag, otherwise return null. All other tags encountered
    211      * will be skipped.
    212      *
    213      * @param parser an XmlPullParser object representing a package element.
    214      * @param pb a Policy.PolicyBuilder instance to build
    215      * @throws IOException
    216      * @throws XmlPullParserException
    217      * @throws IllegalArgumentException if any of the validation checks fail while
    218      *         parsing tag values.
    219      * @throws IllegalStateException if there is a duplicate seinfo tag for the current
    220      *         package tag.
    221      */
    222     private static void readPackageOrThrow(XmlPullParser parser, Policy.PolicyBuilder pb) throws
    223             IOException, XmlPullParserException {
    224         parser.require(XmlPullParser.START_TAG, null, "package");
    225         String pkgName = parser.getAttributeValue(null, "name");
    226 
    227         while (parser.next() != XmlPullParser.END_TAG) {
    228             if (parser.getEventType() != XmlPullParser.START_TAG) {
    229                 continue;
    230             }
    231 
    232             String tagName = parser.getName();
    233             if ("seinfo".equals(tagName)) {
    234                 String seinfo = parser.getAttributeValue(null, "value");
    235                 pb.addInnerPackageMapOrThrow(pkgName, seinfo);
    236                 readSeinfo(parser);
    237             } else {
    238                 skip(parser);
    239             }
    240         }
    241     }
    242 
    243     private static void readCert(XmlPullParser parser) throws IOException,
    244             XmlPullParserException {
    245         parser.require(XmlPullParser.START_TAG, null, "cert");
    246         parser.nextTag();
    247     }
    248 
    249     private static void readSeinfo(XmlPullParser parser) throws IOException,
    250             XmlPullParserException {
    251         parser.require(XmlPullParser.START_TAG, null, "seinfo");
    252         parser.nextTag();
    253     }
    254 
    255     private static void skip(XmlPullParser p) throws IOException, XmlPullParserException {
    256         if (p.getEventType() != XmlPullParser.START_TAG) {
    257             throw new IllegalStateException();
    258         }
    259         int depth = 1;
    260         while (depth != 0) {
    261             switch (p.next()) {
    262             case XmlPullParser.END_TAG:
    263                 depth--;
    264                 break;
    265             case XmlPullParser.START_TAG:
    266                 depth++;
    267                 break;
    268             }
    269         }
    270     }
    271 
    272     /**
    273      * Applies a security label to a package based on an seinfo tag taken from a matched
    274      * policy. All signature based policy stanzas are consulted and, if no match is
    275      * found, the default seinfo label of 'default' (set in ApplicationInfo object) is
    276      * used. The security label is attached to the ApplicationInfo instance of the package
    277      * in the event that a matching policy was found.
    278      *
    279      * @param pkg object representing the package to be labeled.
    280      */
    281     public static void assignSeInfoValue(PackageParser.Package pkg) {
    282         synchronized (sPolicies) {
    283             for (Policy policy : sPolicies) {
    284                 String seInfo = policy.getMatchedSeInfo(pkg);
    285                 if (seInfo != null) {
    286                     pkg.applicationInfo.seInfo = seInfo;
    287                     break;
    288                 }
    289             }
    290         }
    291 
    292         if (pkg.applicationInfo.targetSandboxVersion == 2)
    293             pkg.applicationInfo.seInfo += SANDBOX_V2_STR;
    294 
    295         if (pkg.applicationInfo.isPrivilegedApp())
    296             pkg.applicationInfo.seInfo += PRIVILEGED_APP_STR;
    297 
    298         pkg.applicationInfo.seInfo += TARGETSDKVERSION_STR + pkg.applicationInfo.targetSdkVersion;
    299 
    300         if (DEBUG_POLICY_INSTALL) {
    301             Slog.i(TAG, "package (" + pkg.packageName + ") labeled with " +
    302                     "seinfo=" + pkg.applicationInfo.seInfo);
    303         }
    304     }
    305 }
    306 
    307 /**
    308  * Holds valid policy representations of individual stanzas from a mac_permissions.xml
    309  * file. Each instance can further be used to assign seinfo values to apks using the
    310  * {@link Policy#getMatchedSeinfo} method. To create an instance of this use the
    311  * {@link PolicyBuilder} pattern class, where each instance is validated against a set
    312  * of invariants before being built and returned. Each instance can be guaranteed to
    313  * hold one valid policy stanza as outlined in the system/sepolicy/mac_permissions.xml
    314  * file.
    315  * <p>
    316  * The following is an example of how to use {@link Policy.PolicyBuilder} to create a
    317  * signer based Policy instance with only inner package name refinements.
    318  * </p>
    319  * <pre>
    320  * {@code
    321  * Policy policy = new Policy.PolicyBuilder()
    322  *         .addSignature("308204a8...")
    323  *         .addSignature("483538c8...")
    324  *         .addInnerPackageMapOrThrow("com.foo.", "bar")
    325  *         .addInnerPackageMapOrThrow("com.foo.other", "bar")
    326  *         .build();
    327  * }
    328  * </pre>
    329  * <p>
    330  * The following is an example of how to use {@link Policy.PolicyBuilder} to create a
    331  * signer based Policy instance with only a global seinfo tag.
    332  * </p>
    333  * <pre>
    334  * {@code
    335  * Policy policy = new Policy.PolicyBuilder()
    336  *         .addSignature("308204a8...")
    337  *         .addSignature("483538c8...")
    338  *         .setGlobalSeinfoOrThrow("paltform")
    339  *         .build();
    340  * }
    341  * </pre>
    342  */
    343 final class Policy {
    344 
    345     private final String mSeinfo;
    346     private final Set<Signature> mCerts;
    347     private final Map<String, String> mPkgMap;
    348 
    349     // Use the PolicyBuilder pattern to instantiate
    350     private Policy(PolicyBuilder builder) {
    351         mSeinfo = builder.mSeinfo;
    352         mCerts = Collections.unmodifiableSet(builder.mCerts);
    353         mPkgMap = Collections.unmodifiableMap(builder.mPkgMap);
    354     }
    355 
    356     /**
    357      * Return all the certs stored with this policy stanza.
    358      *
    359      * @return A set of Signature objects representing all the certs stored
    360      *         with the policy.
    361      */
    362     public Set<Signature> getSignatures() {
    363         return mCerts;
    364     }
    365 
    366     /**
    367      * Return whether this policy object contains package name mapping refinements.
    368      *
    369      * @return A boolean indicating if this object has inner package name mappings.
    370      */
    371     public boolean hasInnerPackages() {
    372         return !mPkgMap.isEmpty();
    373     }
    374 
    375     /**
    376      * Return the mapping of all package name refinements.
    377      *
    378      * @return A Map object whose keys are the package names and whose values are
    379      *         the seinfo assignments.
    380      */
    381     public Map<String, String> getInnerPackages() {
    382         return mPkgMap;
    383     }
    384 
    385     /**
    386      * Return whether the policy object has a global seinfo tag attached.
    387      *
    388      * @return A boolean indicating if this stanza has a global seinfo tag.
    389      */
    390     public boolean hasGlobalSeinfo() {
    391         return mSeinfo != null;
    392     }
    393 
    394     @Override
    395     public String toString() {
    396         StringBuilder sb = new StringBuilder();
    397         for (Signature cert : mCerts) {
    398             sb.append("cert=" + cert.toCharsString().substring(0, 11) + "... ");
    399         }
    400 
    401         if (mSeinfo != null) {
    402             sb.append("seinfo=" + mSeinfo);
    403         }
    404 
    405         for (String name : mPkgMap.keySet()) {
    406             sb.append(" " + name + "=" + mPkgMap.get(name));
    407         }
    408 
    409         return sb.toString();
    410     }
    411 
    412     /**
    413      * <p>
    414      * Determine the seinfo value to assign to an apk. The appropriate seinfo value
    415      * is determined using the following steps:
    416      * </p>
    417      * <ul>
    418      *   <li> All certs used to sign the apk and all certs stored with this policy
    419      *     instance are tested for set equality. If this fails then null is returned.
    420      *   </li>
    421      *   <li> If all certs match then an appropriate inner package stanza is
    422      *     searched based on package name alone. If matched, the stored seinfo
    423      *     value for that mapping is returned.
    424      *   </li>
    425      *   <li> If all certs matched and no inner package stanza matches then return
    426      *     the global seinfo value. The returned value can be null in this case.
    427      *   </li>
    428      * </ul>
    429      * <p>
    430      * In all cases, a return value of null should be interpreted as the apk failing
    431      * to match this Policy instance; i.e. failing this policy stanza.
    432      * </p>
    433      * @param pkg the apk to check given as a PackageParser.Package object
    434      * @return A string representing the seinfo matched during policy lookup.
    435      *         A value of null can also be returned if no match occured.
    436      */
    437     public String getMatchedSeInfo(PackageParser.Package pkg) {
    438         // Check for exact signature matches across all certs.
    439         Signature[] certs = mCerts.toArray(new Signature[0]);
    440         if (!Signature.areExactMatch(certs, pkg.mSignatures)) {
    441             return null;
    442         }
    443 
    444         // Check for inner package name matches given that the
    445         // signature checks already passed.
    446         String seinfoValue = mPkgMap.get(pkg.packageName);
    447         if (seinfoValue != null) {
    448             return seinfoValue;
    449         }
    450 
    451         // Return the global seinfo value.
    452         return mSeinfo;
    453     }
    454 
    455     /**
    456      * A nested builder class to create {@link Policy} instances. A {@link Policy}
    457      * class instance represents one valid policy stanza found in a mac_permissions.xml
    458      * file. A valid policy stanza is defined to be a signer stanza which obeys the rules
    459      * outlined in system/sepolicy/mac_permissions.xml. The {@link #build} method
    460      * ensures a set of invariants are upheld enforcing the correct stanza structure
    461      * before returning a valid Policy object.
    462      */
    463     public static final class PolicyBuilder {
    464 
    465         private String mSeinfo;
    466         private final Set<Signature> mCerts;
    467         private final Map<String, String> mPkgMap;
    468 
    469         public PolicyBuilder() {
    470             mCerts = new HashSet<Signature>(2);
    471             mPkgMap = new HashMap<String, String>(2);
    472         }
    473 
    474         /**
    475          * Adds a signature to the set of certs used for validation checks. The purpose
    476          * being that all contained certs will need to be matched against all certs
    477          * contained with an apk.
    478          *
    479          * @param cert the signature to add given as a String.
    480          * @return The reference to this PolicyBuilder.
    481          * @throws IllegalArgumentException if the cert value fails validation;
    482          *         null or is an invalid hex-encoded ASCII string.
    483          */
    484         public PolicyBuilder addSignature(String cert) {
    485             if (cert == null) {
    486                 String err = "Invalid signature value " + cert;
    487                 throw new IllegalArgumentException(err);
    488             }
    489 
    490             mCerts.add(new Signature(cert));
    491             return this;
    492         }
    493 
    494         /**
    495          * Set the global seinfo tag for this policy stanza. The global seinfo tag
    496          * when attached to a signer tag represents the assignment when there isn't a
    497          * further inner package refinement in policy.
    498          *
    499          * @param seinfo the seinfo value given as a String.
    500          * @return The reference to this PolicyBuilder.
    501          * @throws IllegalArgumentException if the seinfo value fails validation;
    502          *         null, zero length or contains non-valid characters [^a-zA-Z_\._0-9].
    503          * @throws IllegalStateException if an seinfo value has already been found
    504          */
    505         public PolicyBuilder setGlobalSeinfoOrThrow(String seinfo) {
    506             if (!validateValue(seinfo)) {
    507                 String err = "Invalid seinfo value " + seinfo;
    508                 throw new IllegalArgumentException(err);
    509             }
    510 
    511             if (mSeinfo != null && !mSeinfo.equals(seinfo)) {
    512                 String err = "Duplicate seinfo tag found";
    513                 throw new IllegalStateException(err);
    514             }
    515 
    516             mSeinfo = seinfo;
    517             return this;
    518         }
    519 
    520         /**
    521          * Create a package name to seinfo value mapping. Each mapping represents
    522          * the seinfo value that will be assigned to the described package name.
    523          * These localized mappings allow the global seinfo to be overriden.
    524          *
    525          * @param pkgName the android package name given to the app
    526          * @param seinfo the seinfo value that will be assigned to the passed pkgName
    527          * @return The reference to this PolicyBuilder.
    528          * @throws IllegalArgumentException if the seinfo value fails validation;
    529          *         null, zero length or contains non-valid characters [^a-zA-Z_\.0-9].
    530          *         Or, if the package name isn't a valid android package name.
    531          * @throws IllegalStateException if trying to reset a package mapping with a
    532          *         different seinfo value.
    533          */
    534         public PolicyBuilder addInnerPackageMapOrThrow(String pkgName, String seinfo) {
    535             if (!validateValue(pkgName)) {
    536                 String err = "Invalid package name " + pkgName;
    537                 throw new IllegalArgumentException(err);
    538             }
    539             if (!validateValue(seinfo)) {
    540                 String err = "Invalid seinfo value " + seinfo;
    541                 throw new IllegalArgumentException(err);
    542             }
    543 
    544             String pkgValue = mPkgMap.get(pkgName);
    545             if (pkgValue != null && !pkgValue.equals(seinfo)) {
    546                 String err = "Conflicting seinfo value found";
    547                 throw new IllegalStateException(err);
    548             }
    549 
    550             mPkgMap.put(pkgName, seinfo);
    551             return this;
    552         }
    553 
    554         /**
    555          * General validation routine for the attribute strings of an element. Checks
    556          * if the string is non-null, positive length and only contains [a-zA-Z_\.0-9].
    557          *
    558          * @param name the string to validate.
    559          * @return boolean indicating if the string was valid.
    560          */
    561         private boolean validateValue(String name) {
    562             if (name == null)
    563                 return false;
    564 
    565             // Want to match on [0-9a-zA-Z_.]
    566             if (!name.matches("\\A[\\.\\w]+\\z")) {
    567                 return false;
    568             }
    569 
    570             return true;
    571         }
    572 
    573         /**
    574          * <p>
    575          * Create a {@link Policy} instance based on the current configuration. This
    576          * method checks for certain policy invariants used to enforce certain guarantees
    577          * about the expected structure of a policy stanza.
    578          * Those invariants are:
    579          * </p>
    580          * <ul>
    581          *   <li> at least one cert must be found </li>
    582          *   <li> either a global seinfo value is present OR at least one
    583          *     inner package mapping must be present BUT not both. </li>
    584          * </ul>
    585          * @return an instance of {@link Policy} with the options set from this builder
    586          * @throws IllegalStateException if an invariant is violated.
    587          */
    588         public Policy build() {
    589             Policy p = new Policy(this);
    590 
    591             if (p.mCerts.isEmpty()) {
    592                 String err = "Missing certs with signer tag. Expecting at least one.";
    593                 throw new IllegalStateException(err);
    594             }
    595             if (!(p.mSeinfo == null ^ p.mPkgMap.isEmpty())) {
    596                 String err = "Only seinfo tag XOR package tags are allowed within " +
    597                         "a signer stanza.";
    598                 throw new IllegalStateException(err);
    599             }
    600 
    601             return p;
    602         }
    603     }
    604 }
    605 
    606 /**
    607  * Comparision imposing an ordering on Policy objects. It is understood that Policy
    608  * objects can only take one of three forms and ordered according to the following
    609  * set of rules most specific to least.
    610  * <ul>
    611  *   <li> signer stanzas with inner package mappings </li>
    612  *   <li> signer stanzas with global seinfo tags </li>
    613  * </ul>
    614  * This comparison also checks for duplicate entries on the input selectors. Any
    615  * found duplicates will be flagged and can be checked with {@link #foundDuplicate}.
    616  */
    617 
    618 final class PolicyComparator implements Comparator<Policy> {
    619 
    620     private boolean duplicateFound = false;
    621 
    622     public boolean foundDuplicate() {
    623         return duplicateFound;
    624     }
    625 
    626     @Override
    627     public int compare(Policy p1, Policy p2) {
    628 
    629         // Give precedence to stanzas with inner package mappings
    630         if (p1.hasInnerPackages() != p2.hasInnerPackages()) {
    631             return p1.hasInnerPackages() ? -1 : 1;
    632         }
    633 
    634         // Check for duplicate entries
    635         if (p1.getSignatures().equals(p2.getSignatures())) {
    636             // Checks if signer w/o inner package names
    637             if (p1.hasGlobalSeinfo()) {
    638                 duplicateFound = true;
    639                 Slog.e(SELinuxMMAC.TAG, "Duplicate policy entry: " + p1.toString());
    640             }
    641 
    642             // Look for common inner package name mappings
    643             final Map<String, String> p1Packages = p1.getInnerPackages();
    644             final Map<String, String> p2Packages = p2.getInnerPackages();
    645             if (!Collections.disjoint(p1Packages.keySet(), p2Packages.keySet())) {
    646                 duplicateFound = true;
    647                 Slog.e(SELinuxMMAC.TAG, "Duplicate policy entry: " + p1.toString());
    648             }
    649         }
    650 
    651         return 0;
    652     }
    653 }
    654