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