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