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.Signature;
     21 import android.os.Environment;
     22 import android.util.Slog;
     23 import android.util.Xml;
     24 
     25 import libcore.io.IoUtils;
     26 
     27 import java.io.File;
     28 import java.io.FileNotFoundException;
     29 import java.io.FileOutputStream;
     30 import java.io.FileReader;
     31 import java.io.IOException;
     32 import java.security.MessageDigest;
     33 import java.security.NoSuchAlgorithmException;
     34 
     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 import org.xmlpull.v1.XmlPullParser;
     45 import org.xmlpull.v1.XmlPullParserException;
     46 
     47 /**
     48  * Centralized access to SELinux MMAC (middleware MAC) implementation. This
     49  * class is responsible for loading the appropriate mac_permissions.xml file
     50  * as well as providing an interface for assigning seinfo values to apks.
     51  *
     52  * {@hide}
     53  */
     54 public final class SELinuxMMAC {
     55 
     56     static final String TAG = "SELinuxMMAC";
     57 
     58     private static final boolean DEBUG_POLICY = false;
     59     private static final boolean DEBUG_POLICY_INSTALL = DEBUG_POLICY || false;
     60     private static final boolean DEBUG_POLICY_ORDER = DEBUG_POLICY || false;
     61 
     62     // All policy stanzas read from mac_permissions.xml. This is also the lock
     63     // to synchronize access during policy load and access attempts.
     64     private static List<Policy> sPolicies = new ArrayList<>();
     65 
     66     // Data policy override version file.
     67     private static final String DATA_VERSION_FILE =
     68             Environment.getDataDirectory() + "/security/current/selinux_version";
     69 
     70     // Base policy version file.
     71     private static final String BASE_VERSION_FILE = "/selinux_version";
     72 
     73     // Whether override security policies should be loaded.
     74     private static final boolean USE_OVERRIDE_POLICY = useOverridePolicy();
     75 
     76     // Data override mac_permissions.xml policy file.
     77     private static final String DATA_MAC_PERMISSIONS =
     78             Environment.getDataDirectory() + "/security/current/mac_permissions.xml";
     79 
     80     // Base mac_permissions.xml policy file.
     81     private static final String BASE_MAC_PERMISSIONS =
     82             Environment.getRootDirectory() + "/etc/security/mac_permissions.xml";
     83 
     84     // Determine which mac_permissions.xml file to use.
     85     private static final String MAC_PERMISSIONS = USE_OVERRIDE_POLICY ?
     86             DATA_MAC_PERMISSIONS : BASE_MAC_PERMISSIONS;
     87 
     88     // Data override seapp_contexts policy file.
     89     private static final String DATA_SEAPP_CONTEXTS =
     90             Environment.getDataDirectory() + "/security/current/seapp_contexts";
     91 
     92     // Base seapp_contexts policy file.
     93     private static final String BASE_SEAPP_CONTEXTS = "/seapp_contexts";
     94 
     95     // Determine which seapp_contexts file to use.
     96     private static final String SEAPP_CONTEXTS = USE_OVERRIDE_POLICY ?
     97             DATA_SEAPP_CONTEXTS : BASE_SEAPP_CONTEXTS;
     98 
     99     // Stores the hash of the last used seapp_contexts file.
    100     private static final String SEAPP_HASH_FILE =
    101             Environment.getDataDirectory().toString() + "/system/seapp_hash";
    102 
    103     /**
    104      * Load the mac_permissions.xml file containing all seinfo assignments used to
    105      * label apps. The loaded mac_permissions.xml file is determined by the
    106      * MAC_PERMISSIONS class variable which is set at class load time which itself
    107      * is based on the USE_OVERRIDE_POLICY class variable. For further guidance on
    108      * the proper structure of a mac_permissions.xml file consult the source code
    109      * located at external/sepolicy/mac_permissions.xml.
    110      *
    111      * @return boolean indicating if policy was correctly loaded. A value of false
    112      *         typically indicates a structural problem with the xml or incorrectly
    113      *         constructed policy stanzas. A value of true means that all stanzas
    114      *         were loaded successfully; no partial loading is possible.
    115      */
    116     public static boolean readInstallPolicy() {
    117         // Temp structure to hold the rules while we parse the xml file
    118         List<Policy> policies = new ArrayList<>();
    119 
    120         FileReader policyFile = null;
    121         XmlPullParser parser = Xml.newPullParser();
    122         try {
    123             policyFile = new FileReader(MAC_PERMISSIONS);
    124             Slog.d(TAG, "Using policy file " + MAC_PERMISSIONS);
    125 
    126             parser.setInput(policyFile);
    127             parser.nextTag();
    128             parser.require(XmlPullParser.START_TAG, null, "policy");
    129 
    130             while (parser.next() != XmlPullParser.END_TAG) {
    131                 if (parser.getEventType() != XmlPullParser.START_TAG) {
    132                     continue;
    133                 }
    134 
    135                 switch (parser.getName()) {
    136                     case "signer":
    137                         policies.add(readSignerOrThrow(parser));
    138                         break;
    139                     case "default":
    140                         policies.add(readDefaultOrThrow(parser));
    141                         break;
    142                     default:
    143                         skip(parser);
    144                 }
    145             }
    146         } catch (IllegalStateException | IllegalArgumentException |
    147                 XmlPullParserException ex) {
    148             StringBuilder sb = new StringBuilder("Exception @");
    149             sb.append(parser.getPositionDescription());
    150             sb.append(" while parsing ");
    151             sb.append(MAC_PERMISSIONS);
    152             sb.append(":");
    153             sb.append(ex);
    154             Slog.w(TAG, sb.toString());
    155             return false;
    156         } catch (IOException ioe) {
    157             Slog.w(TAG, "Exception parsing " + MAC_PERMISSIONS, ioe);
    158             return false;
    159         } finally {
    160             IoUtils.closeQuietly(policyFile);
    161         }
    162 
    163         // Now sort the policy stanzas
    164         PolicyComparator policySort = new PolicyComparator();
    165         Collections.sort(policies, policySort);
    166         if (policySort.foundDuplicate()) {
    167             Slog.w(TAG, "ERROR! Duplicate entries found parsing " + MAC_PERMISSIONS);
    168             return false;
    169         }
    170 
    171         synchronized (sPolicies) {
    172             sPolicies = policies;
    173 
    174             if (DEBUG_POLICY_ORDER) {
    175                 for (Policy policy : sPolicies) {
    176                     Slog.d(TAG, "Policy: " + policy.toString());
    177                 }
    178             }
    179         }
    180 
    181         return true;
    182     }
    183 
    184     /**
    185      * Loop over a signer tag looking for seinfo, package and cert tags. A {@link Policy}
    186      * instance will be created and returned in the process. During the pass all other
    187      * tag elements will be skipped.
    188      *
    189      * @param parser an XmlPullParser object representing a signer element.
    190      * @return the constructed {@link Policy} instance
    191      * @throws IOException
    192      * @throws XmlPullParserException
    193      * @throws IllegalArgumentException if any of the validation checks fail while
    194      *         parsing tag values.
    195      * @throws IllegalStateException if any of the invariants fail when constructing
    196      *         the {@link Policy} instance.
    197      */
    198     private static Policy readSignerOrThrow(XmlPullParser parser) throws IOException,
    199             XmlPullParserException {
    200 
    201         parser.require(XmlPullParser.START_TAG, null, "signer");
    202         Policy.PolicyBuilder pb = new Policy.PolicyBuilder();
    203 
    204         // Check for a cert attached to the signer tag. We allow a signature
    205         // to appear as an attribute as well as those attached to cert tags.
    206         String cert = parser.getAttributeValue(null, "signature");
    207         if (cert != null) {
    208             pb.addSignature(cert);
    209         }
    210 
    211         while (parser.next() != XmlPullParser.END_TAG) {
    212             if (parser.getEventType() != XmlPullParser.START_TAG) {
    213                 continue;
    214             }
    215 
    216             String tagName = parser.getName();
    217             if ("seinfo".equals(tagName)) {
    218                 String seinfo = parser.getAttributeValue(null, "value");
    219                 pb.setGlobalSeinfoOrThrow(seinfo);
    220                 readSeinfo(parser);
    221             } else if ("package".equals(tagName)) {
    222                 readPackageOrThrow(parser, pb);
    223             } else if ("cert".equals(tagName)) {
    224                 String sig = parser.getAttributeValue(null, "signature");
    225                 pb.addSignature(sig);
    226                 readCert(parser);
    227             } else {
    228                 skip(parser);
    229             }
    230         }
    231 
    232         return pb.build();
    233     }
    234 
    235     /**
    236      * Loop over a default element looking for seinfo child tags. A {@link Policy}
    237      * instance will be created and returned in the process. All other tags encountered
    238      * will be skipped.
    239      *
    240      * @param parser an XmlPullParser object representing a default element.
    241      * @return the constructed {@link Policy} instance
    242      * @throws IOException
    243      * @throws XmlPullParserException
    244      * @throws IllegalArgumentException if any of the validation checks fail while
    245      *         parsing tag values.
    246      * @throws IllegalStateException if any of the invariants fail when constructing
    247      *         the {@link Policy} instance.
    248      */
    249     private static Policy readDefaultOrThrow(XmlPullParser parser) throws IOException,
    250             XmlPullParserException {
    251 
    252         parser.require(XmlPullParser.START_TAG, null, "default");
    253         Policy.PolicyBuilder pb = new Policy.PolicyBuilder();
    254         pb.setAsDefaultPolicy();
    255 
    256         while (parser.next() != XmlPullParser.END_TAG) {
    257             if (parser.getEventType() != XmlPullParser.START_TAG) {
    258                 continue;
    259             }
    260 
    261             String tagName = parser.getName();
    262             if ("seinfo".equals(tagName)) {
    263                 String seinfo = parser.getAttributeValue(null, "value");
    264                 pb.setGlobalSeinfoOrThrow(seinfo);
    265                 readSeinfo(parser);
    266             } else {
    267                 skip(parser);
    268             }
    269         }
    270 
    271         return pb.build();
    272     }
    273 
    274     /**
    275      * Loop over a package element looking for seinfo child tags. If found return the
    276      * value attribute of the seinfo tag, otherwise return null. All other tags encountered
    277      * will be skipped.
    278      *
    279      * @param parser an XmlPullParser object representing a package element.
    280      * @param pb a Policy.PolicyBuilder instance to build
    281      * @throws IOException
    282      * @throws XmlPullParserException
    283      * @throws IllegalArgumentException if any of the validation checks fail while
    284      *         parsing tag values.
    285      * @throws IllegalStateException if there is a duplicate seinfo tag for the current
    286      *         package tag.
    287      */
    288     private static void readPackageOrThrow(XmlPullParser parser, Policy.PolicyBuilder pb) throws
    289             IOException, XmlPullParserException {
    290         parser.require(XmlPullParser.START_TAG, null, "package");
    291         String pkgName = parser.getAttributeValue(null, "name");
    292 
    293         while (parser.next() != XmlPullParser.END_TAG) {
    294             if (parser.getEventType() != XmlPullParser.START_TAG) {
    295                 continue;
    296             }
    297 
    298             String tagName = parser.getName();
    299             if ("seinfo".equals(tagName)) {
    300                 String seinfo = parser.getAttributeValue(null, "value");
    301                 pb.addInnerPackageMapOrThrow(pkgName, seinfo);
    302                 readSeinfo(parser);
    303             } else {
    304                 skip(parser);
    305             }
    306         }
    307     }
    308 
    309     private static void readCert(XmlPullParser parser) throws IOException,
    310             XmlPullParserException {
    311         parser.require(XmlPullParser.START_TAG, null, "cert");
    312         parser.nextTag();
    313     }
    314 
    315     private static void readSeinfo(XmlPullParser parser) throws IOException,
    316             XmlPullParserException {
    317         parser.require(XmlPullParser.START_TAG, null, "seinfo");
    318         parser.nextTag();
    319     }
    320 
    321     private static void skip(XmlPullParser p) throws IOException, XmlPullParserException {
    322         if (p.getEventType() != XmlPullParser.START_TAG) {
    323             throw new IllegalStateException();
    324         }
    325         int depth = 1;
    326         while (depth != 0) {
    327             switch (p.next()) {
    328             case XmlPullParser.END_TAG:
    329                 depth--;
    330                 break;
    331             case XmlPullParser.START_TAG:
    332                 depth++;
    333                 break;
    334             }
    335         }
    336     }
    337 
    338     /**
    339      * Applies a security label to a package based on an seinfo tag taken from a matched
    340      * policy. All signature based policy stanzas are consulted first and, if no match
    341      * is found, the default policy stanza is then consulted. The security label is
    342      * attached to the ApplicationInfo instance of the package in the event that a matching
    343      * policy was found.
    344      *
    345      * @param pkg object representing the package to be labeled.
    346      * @return boolean which determines whether a non null seinfo label was assigned
    347      *         to the package. A null value simply represents that no policy matched.
    348      */
    349     public static boolean assignSeinfoValue(PackageParser.Package pkg) {
    350         synchronized (sPolicies) {
    351             for (Policy policy : sPolicies) {
    352                 String seinfo = policy.getMatchedSeinfo(pkg);
    353                 if (seinfo != null) {
    354                     pkg.applicationInfo.seinfo = seinfo;
    355                     if (DEBUG_POLICY_INSTALL) {
    356                         Slog.i(TAG, "package (" + pkg.packageName + ") labeled with " +
    357                                "seinfo=" + seinfo);
    358                     }
    359                     return true;
    360                 }
    361             }
    362         }
    363 
    364         if (DEBUG_POLICY_INSTALL) {
    365             Slog.i(TAG, "package (" + pkg.packageName + ") doesn't match any policy; " +
    366                    "seinfo will remain null");
    367         }
    368         return false;
    369     }
    370 
    371     /**
    372      * Determines if a recursive restorecon on /data/data and /data/user is needed.
    373      * It does this by comparing the SHA-1 of the seapp_contexts file against the
    374      * stored hash at /data/system/seapp_hash.
    375      *
    376      * @return Returns true if the restorecon should occur or false otherwise.
    377      */
    378     public static boolean shouldRestorecon() {
    379         // Any error with the seapp_contexts file should be fatal
    380         byte[] currentHash = null;
    381         try {
    382             currentHash = returnHash(SEAPP_CONTEXTS);
    383         } catch (IOException ioe) {
    384             Slog.e(TAG, "Error with hashing seapp_contexts.", ioe);
    385             return false;
    386         }
    387 
    388         // Push past any error with the stored hash file
    389         byte[] storedHash = null;
    390         try {
    391             storedHash = IoUtils.readFileAsByteArray(SEAPP_HASH_FILE);
    392         } catch (IOException ioe) {
    393             Slog.w(TAG, "Error opening " + SEAPP_HASH_FILE + ". Assuming first boot.");
    394         }
    395 
    396         return (storedHash == null || !MessageDigest.isEqual(storedHash, currentHash));
    397     }
    398 
    399     /**
    400      * Stores the SHA-1 of the seapp_contexts to /data/system/seapp_hash.
    401      */
    402     public static void setRestoreconDone() {
    403         try {
    404             final byte[] currentHash = returnHash(SEAPP_CONTEXTS);
    405             dumpHash(new File(SEAPP_HASH_FILE), currentHash);
    406         } catch (IOException ioe) {
    407             Slog.e(TAG, "Error with saving hash to " + SEAPP_HASH_FILE, ioe);
    408         }
    409     }
    410 
    411     /**
    412      * Dump the contents of a byte array to a specified file.
    413      *
    414      * @param file The file that receives the byte array content.
    415      * @param content A byte array that will be written to the specified file.
    416      * @throws IOException if any failed I/O operation occured.
    417      *         Included is the failure to atomically rename the tmp
    418      *         file used in the process.
    419      */
    420     private static void dumpHash(File file, byte[] content) throws IOException {
    421         FileOutputStream fos = null;
    422         File tmp = null;
    423         try {
    424             tmp = File.createTempFile("seapp_hash", ".journal", file.getParentFile());
    425             tmp.setReadable(true);
    426             fos = new FileOutputStream(tmp);
    427             fos.write(content);
    428             fos.getFD().sync();
    429             if (!tmp.renameTo(file)) {
    430                 throw new IOException("Failure renaming " + file.getCanonicalPath());
    431             }
    432         } finally {
    433             if (tmp != null) {
    434                 tmp.delete();
    435             }
    436             IoUtils.closeQuietly(fos);
    437         }
    438     }
    439 
    440     /**
    441      * Return the SHA-1 of a file.
    442      *
    443      * @param file The path to the file given as a string.
    444      * @return Returns the SHA-1 of the file as a byte array.
    445      * @throws IOException if any failed I/O operations occured.
    446      */
    447     private static byte[] returnHash(String file) throws IOException {
    448         try {
    449             final byte[] contents = IoUtils.readFileAsByteArray(file);
    450             return MessageDigest.getInstance("SHA-1").digest(contents);
    451         } catch (NoSuchAlgorithmException nsae) {
    452             throw new RuntimeException(nsae);  // impossible
    453         }
    454     }
    455 
    456     private static boolean useOverridePolicy() {
    457         try {
    458             final String overrideVersion = IoUtils.readFileAsString(DATA_VERSION_FILE);
    459             final String baseVersion = IoUtils.readFileAsString(BASE_VERSION_FILE);
    460             if (overrideVersion.equals(baseVersion)) {
    461                 return true;
    462             }
    463             Slog.e(TAG, "Override policy version '" + overrideVersion + "' doesn't match " +
    464                    "base version '" + baseVersion + "'. Skipping override policy files.");
    465         } catch (FileNotFoundException fnfe) {
    466             // Override version file doesn't have to exist so silently ignore.
    467         } catch (IOException ioe) {
    468             Slog.w(TAG, "Skipping override policy files.", ioe);
    469         }
    470         return false;
    471     }
    472 }
    473 
    474 /**
    475  * Holds valid policy representations of individual stanzas from a mac_permissions.xml
    476  * file. Each instance can further be used to assign seinfo values to apks using the
    477  * {@link Policy#getMatchedSeinfo} method. To create an instance of this use the
    478  * {@link PolicyBuilder} pattern class, where each instance is validated against a set
    479  * of invariants before being built and returned. Each instance can be guaranteed to
    480  * hold one valid policy stanza as outlined in the external/sepolicy/mac_permissions.xml
    481  * file.
    482  * <p>
    483  * The following is an example of how to use {@link Policy.PolicyBuilder} to create a
    484  * signer based Policy instance with only inner package name refinements.
    485  * </p>
    486  * <pre>
    487  * {@code
    488  * Policy policy = new Policy.PolicyBuilder()
    489  *         .addSignature("308204a8...")
    490  *         .addSignature("483538c8...")
    491  *         .addInnerPackageMapOrThrow("com.foo.", "bar")
    492  *         .addInnerPackageMapOrThrow("com.foo.other", "bar")
    493  *         .build();
    494  * }
    495  * </pre>
    496  * <p>
    497  * The following is an example of how to use {@link Policy.PolicyBuilder} to create a
    498  * signer based Policy instance with only a global seinfo tag.
    499  * </p>
    500  * <pre>
    501  * {@code
    502  * Policy policy = new Policy.PolicyBuilder()
    503  *         .addSignature("308204a8...")
    504  *         .addSignature("483538c8...")
    505  *         .setGlobalSeinfoOrThrow("paltform")
    506  *         .build();
    507  * }
    508  * </pre>
    509  * <p>
    510  * The following is an example of how to use {@link Policy.PolicyBuilder} to create a
    511  * default based Policy instance.
    512  * </p>
    513  * <pre>
    514  * {@code
    515  * Policy policy = new Policy.PolicyBuilder()
    516  *         .setAsDefaultPolicy()
    517  *         .setGlobalSeinfoOrThrow("default")
    518  *         .build();
    519  * }
    520  * </pre>
    521  */
    522 final class Policy {
    523 
    524     private final String mSeinfo;
    525     private final boolean mDefaultStanza;
    526     private final Set<Signature> mCerts;
    527     private final Map<String, String> mPkgMap;
    528 
    529     // Use the PolicyBuilder pattern to instantiate
    530     private Policy(PolicyBuilder builder) {
    531         mSeinfo = builder.mSeinfo;
    532         mDefaultStanza = builder.mDefaultStanza;
    533         mCerts = Collections.unmodifiableSet(builder.mCerts);
    534         mPkgMap = Collections.unmodifiableMap(builder.mPkgMap);
    535     }
    536 
    537     /**
    538      * Return all the certs stored with this policy stanza.
    539      *
    540      * @return A set of Signature objects representing all the certs stored
    541      *         with the policy.
    542      */
    543     public Set<Signature> getSignatures() {
    544         return mCerts;
    545     }
    546 
    547     /**
    548      * Return whether this policy object represents a default stanza.
    549      *
    550      * @return A boolean indicating if this object represents a default policy stanza.
    551      */
    552     public boolean isDefaultStanza() {
    553         return mDefaultStanza;
    554     }
    555 
    556     /**
    557      * Return whether this policy object contains package name mapping refinements.
    558      *
    559      * @return A boolean indicating if this object has inner package name mappings.
    560      */
    561     public boolean hasInnerPackages() {
    562         return !mPkgMap.isEmpty();
    563     }
    564 
    565     /**
    566      * Return the mapping of all package name refinements.
    567      *
    568      * @return A Map object whose keys are the package names and whose values are
    569      *         the seinfo assignments.
    570      */
    571     public Map<String, String> getInnerPackages() {
    572         return mPkgMap;
    573     }
    574 
    575     /**
    576      * Return whether the policy object has a global seinfo tag attached.
    577      *
    578      * @return A boolean indicating if this stanza has a global seinfo tag.
    579      */
    580     public boolean hasGlobalSeinfo() {
    581         return mSeinfo != null;
    582     }
    583 
    584     @Override
    585     public String toString() {
    586         StringBuilder sb = new StringBuilder();
    587         if (mDefaultStanza) {
    588             sb.append("defaultStanza=true ");
    589         }
    590 
    591         for (Signature cert : mCerts) {
    592             sb.append("cert=" + cert.toCharsString().substring(0, 11) + "... ");
    593         }
    594 
    595         if (mSeinfo != null) {
    596             sb.append("seinfo=" + mSeinfo);
    597         }
    598 
    599         for (String name : mPkgMap.keySet()) {
    600             sb.append(" " + name + "=" + mPkgMap.get(name));
    601         }
    602 
    603         return sb.toString();
    604     }
    605 
    606     /**
    607      * <p>
    608      * Determine the seinfo value to assign to an apk. The appropriate seinfo value
    609      * is determined using the following steps:
    610      * </p>
    611      * <ul>
    612      *   <li> If this Policy instance is defined as a default stanza:
    613      *       <ul><li>Return the global seinfo value</li></ul>
    614      *   </li>
    615      *   <li> If this Policy instance is defined as a signer stanza:
    616      *     <ul>
    617      *       <li> All certs used to sign the apk and all certs stored with this policy
    618      *         instance are tested for set equality. If this fails then null is returned.
    619      *       </li>
    620      *       <li> If all certs match then an appropriate inner package stanza is
    621      *         searched based on package name alone. If matched, the stored seinfo
    622      *         value for that mapping is returned.
    623      *       </li>
    624      *       <li> If all certs matched and no inner package stanza matches then return
    625      *         the global seinfo value. The returned value can be null in this case.
    626      *       </li>
    627      *     </ul>
    628      *   </li>
    629      * </ul>
    630      * <p>
    631      * In all cases, a return value of null should be interpreted as the apk failing
    632      * to match this Policy instance; i.e. failing this policy stanza.
    633      * </p>
    634      * @param pkg the apk to check given as a PackageParser.Package object
    635      * @return A string representing the seinfo matched during policy lookup.
    636      *         A value of null can also be returned if no match occured.
    637      */
    638     public String getMatchedSeinfo(PackageParser.Package pkg) {
    639         if (!mDefaultStanza) {
    640             // Check for exact signature matches across all certs.
    641             Signature[] certs = mCerts.toArray(new Signature[0]);
    642             if (!Signature.areExactMatch(certs, pkg.mSignatures)) {
    643                 return null;
    644             }
    645 
    646             // Check for inner package name matches given that the
    647             // signature checks already passed.
    648             String seinfoValue = mPkgMap.get(pkg.packageName);
    649             if (seinfoValue != null) {
    650                 return seinfoValue;
    651             }
    652         }
    653 
    654         // Return the global seinfo value (even if it's null).
    655         return mSeinfo;
    656     }
    657 
    658     /**
    659      * A nested builder class to create {@link Policy} instances. A {@link Policy}
    660      * class instance represents one valid policy stanza found in a mac_permissions.xml
    661      * file. A valid policy stanza is defined to be either a signer or default stanza
    662      * which obeys the rules outlined in external/sepolicy/mac_permissions.xml. The
    663      * {@link #build} method ensures a set of invariants are upheld enforcing the correct
    664      * stanza structure before returning a valid Policy object.
    665      */
    666     public static final class PolicyBuilder {
    667 
    668         private String mSeinfo;
    669         private boolean mDefaultStanza;
    670         private final Set<Signature> mCerts;
    671         private final Map<String, String> mPkgMap;
    672 
    673         public PolicyBuilder() {
    674             mCerts = new HashSet<Signature>(2);
    675             mPkgMap = new HashMap<String, String>(2);
    676         }
    677 
    678         /**
    679          * Sets this stanza as a default stanza. All policy stanzas are assumed to
    680          * be signer stanzas unless this method is explicitly called. Default stanzas
    681          * are treated differently with respect to allowable child tags, ordering and
    682          * when and how policy decisions are enforced.
    683          *
    684          * @return The reference to this PolicyBuilder.
    685          */
    686         public PolicyBuilder setAsDefaultPolicy() {
    687             mDefaultStanza = true;
    688             return this;
    689         }
    690 
    691         /**
    692          * Adds a signature to the set of certs used for validation checks. The purpose
    693          * being that all contained certs will need to be matched against all certs
    694          * contained with an apk.
    695          *
    696          * @param cert the signature to add given as a String.
    697          * @return The reference to this PolicyBuilder.
    698          * @throws IllegalArgumentException if the cert value fails validation;
    699          *         null or is an invalid hex-encoded ASCII string.
    700          */
    701         public PolicyBuilder addSignature(String cert) {
    702             if (cert == null) {
    703                 String err = "Invalid signature value " + cert;
    704                 throw new IllegalArgumentException(err);
    705             }
    706 
    707             mCerts.add(new Signature(cert));
    708             return this;
    709         }
    710 
    711         /**
    712          * Set the global seinfo tag for this policy stanza. The global seinfo tag
    713          * represents the seinfo element that is used in one of two ways depending on
    714          * its context. When attached to a signer tag the global seinfo represents an
    715          * assignment when there isn't a further inner package refinement in policy.
    716          * When used with a default tag, it represents the only allowable assignment
    717          * value.
    718          *
    719          * @param seinfo the seinfo value given as a String.
    720          * @return The reference to this PolicyBuilder.
    721          * @throws IllegalArgumentException if the seinfo value fails validation;
    722          *         null, zero length or contains non-valid characters [^a-zA-Z_\._0-9].
    723          * @throws IllegalStateException if an seinfo value has already been found
    724          */
    725         public PolicyBuilder setGlobalSeinfoOrThrow(String seinfo) {
    726             if (!validateValue(seinfo)) {
    727                 String err = "Invalid seinfo value " + seinfo;
    728                 throw new IllegalArgumentException(err);
    729             }
    730 
    731             if (mSeinfo != null && !mSeinfo.equals(seinfo)) {
    732                 String err = "Duplicate seinfo tag found";
    733                 throw new IllegalStateException(err);
    734             }
    735 
    736             mSeinfo = seinfo;
    737             return this;
    738         }
    739 
    740         /**
    741          * Create a package name to seinfo value mapping. Each mapping represents
    742          * the seinfo value that will be assigned to the described package name.
    743          * These localized mappings allow the global seinfo to be overriden. This
    744          * mapping provides no value when used in conjunction with a default stanza;
    745          * enforced through the {@link #build} method.
    746          *
    747          * @param pkgName the android package name given to the app
    748          * @param seinfo the seinfo value that will be assigned to the passed pkgName
    749          * @return The reference to this PolicyBuilder.
    750          * @throws IllegalArgumentException if the seinfo value fails validation;
    751          *         null, zero length or contains non-valid characters [^a-zA-Z_\.0-9].
    752          *         Or, if the package name isn't a valid android package name.
    753          * @throws IllegalStateException if trying to reset a package mapping with a
    754          *         different seinfo value.
    755          */
    756         public PolicyBuilder addInnerPackageMapOrThrow(String pkgName, String seinfo) {
    757             if (!validateValue(pkgName)) {
    758                 String err = "Invalid package name " + pkgName;
    759                 throw new IllegalArgumentException(err);
    760             }
    761             if (!validateValue(seinfo)) {
    762                 String err = "Invalid seinfo value " + seinfo;
    763                 throw new IllegalArgumentException(err);
    764             }
    765 
    766             String pkgValue = mPkgMap.get(pkgName);
    767             if (pkgValue != null && !pkgValue.equals(seinfo)) {
    768                 String err = "Conflicting seinfo value found";
    769                 throw new IllegalStateException(err);
    770             }
    771 
    772             mPkgMap.put(pkgName, seinfo);
    773             return this;
    774         }
    775 
    776         /**
    777          * General validation routine for the attribute strings of an element. Checks
    778          * if the string is non-null, positive length and only contains [a-zA-Z_\.0-9].
    779          *
    780          * @param name the string to validate.
    781          * @return boolean indicating if the string was valid.
    782          */
    783         private boolean validateValue(String name) {
    784             if (name == null)
    785                 return false;
    786 
    787             // Want to match on [0-9a-zA-Z_.]
    788             if (!name.matches("\\A[\\.\\w]+\\z")) {
    789                 return false;
    790             }
    791 
    792             return true;
    793         }
    794 
    795         /**
    796          * <p>
    797          * Create a {@link Policy} instance based on the current configuration. This
    798          * method checks for certain policy invariants used to enforce certain guarantees
    799          * about the expected structure of a policy stanza.
    800          * Those invariants are:
    801          * </p>
    802          *    <ul>
    803          *      <li> If a default stanza
    804          *        <ul>
    805          *          <li> an attached global seinfo tag must be present </li>
    806          *          <li> no signatures and no package names can be present </li>
    807          *        </ul>
    808          *      </li>
    809          *      <li> If a signer stanza
    810          *        <ul>
    811          *           <li> at least one cert must be found </li>
    812          *           <li> either a global seinfo value is present OR at least one
    813          *           inner package mapping must be present BUT not both. </li>
    814          *        </ul>
    815          *      </li>
    816          *    </ul>
    817          *
    818          * @return an instance of {@link Policy} with the options set from this builder
    819          * @throws IllegalStateException if an invariant is violated.
    820          */
    821         public Policy build() {
    822             Policy p = new Policy(this);
    823 
    824             if (p.mDefaultStanza) {
    825                 if (p.mSeinfo == null) {
    826                     String err = "Missing global seinfo tag with default stanza.";
    827                     throw new IllegalStateException(err);
    828                 }
    829                 if (p.mCerts.size() != 0) {
    830                     String err = "Certs not allowed with default stanza.";
    831                     throw new IllegalStateException(err);
    832                 }
    833                 if (!p.mPkgMap.isEmpty()) {
    834                     String err = "Inner package mappings not allowed with default stanza.";
    835                     throw new IllegalStateException(err);
    836                 }
    837             } else {
    838                 if (p.mCerts.size() == 0) {
    839                     String err = "Missing certs with signer tag. Expecting at least one.";
    840                     throw new IllegalStateException(err);
    841                 }
    842                 if (!(p.mSeinfo == null ^ p.mPkgMap.isEmpty())) {
    843                     String err = "Only seinfo tag XOR package tags are allowed within " +
    844                             "a signer stanza.";
    845                     throw new IllegalStateException(err);
    846                 }
    847             }
    848 
    849             return p;
    850         }
    851     }
    852 }
    853 
    854 /**
    855  * Comparision imposing an ordering on Policy objects. It is understood that Policy
    856  * objects can only take one of three forms and ordered according to the following
    857  * set of rules most specific to least.
    858  * <ul>
    859  *   <li> signer stanzas with inner package mappings </li>
    860  *   <li> signer stanzas with global seinfo tags </li>
    861  *   <li> default stanza </li>
    862  * </ul>
    863  * This comparison also checks for duplicate entries on the input selectors. Any
    864  * found duplicates will be flagged and can be checked with {@link #foundDuplicate}.
    865  */
    866 
    867 final class PolicyComparator implements Comparator<Policy> {
    868 
    869     private boolean duplicateFound = false;
    870 
    871     public boolean foundDuplicate() {
    872         return duplicateFound;
    873     }
    874 
    875     @Override
    876     public int compare(Policy p1, Policy p2) {
    877 
    878         // Give precedence to signature stanzas over default stanzas
    879         if (p1.isDefaultStanza() != p2.isDefaultStanza()) {
    880             return p1.isDefaultStanza() ? 1 : -1;
    881         }
    882 
    883         // Give precedence to stanzas with inner package mappings
    884         if (p1.hasInnerPackages() != p2.hasInnerPackages()) {
    885             return p1.hasInnerPackages() ? -1 : 1;
    886         }
    887 
    888         // Check for duplicate entries
    889         if (p1.getSignatures().equals(p2.getSignatures())) {
    890             // Checks if default stanza or a signer w/o inner package names
    891             if (p1.hasGlobalSeinfo()) {
    892                 duplicateFound = true;
    893                 Slog.e(SELinuxMMAC.TAG, "Duplicate policy entry: " + p1.toString());
    894             }
    895 
    896             // Look for common inner package name mappings
    897             final Map<String, String> p1Packages = p1.getInnerPackages();
    898             final Map<String, String> p2Packages = p2.getInnerPackages();
    899             if (!Collections.disjoint(p1Packages.keySet(), p2Packages.keySet())) {
    900                 duplicateFound = true;
    901                 Slog.e(SELinuxMMAC.TAG, "Duplicate policy entry: " + p1.toString());
    902             }
    903         }
    904 
    905         return 0;
    906     }
    907 }
    908