SELinuxMMAC.java revision 6da39a4406a5768cefb99e5a5426fb22248523bc
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
17package com.android.server.pm;
18
19import android.content.pm.PackageParser;
20import android.content.pm.Signature;
21import android.os.Environment;
22import android.os.SystemProperties;
23import android.system.ErrnoException;
24import android.system.Os;
25import android.system.OsConstants;
26import android.util.Slog;
27import android.util.Xml;
28
29import libcore.io.IoUtils;
30
31import org.xmlpull.v1.XmlPullParser;
32import org.xmlpull.v1.XmlPullParserException;
33
34import java.io.File;
35import java.io.FileReader;
36import java.io.IOException;
37import java.security.MessageDigest;
38import java.security.NoSuchAlgorithmException;
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.Collections;
42import java.util.Comparator;
43import java.util.HashMap;
44import java.util.HashSet;
45import java.util.List;
46import java.util.Map;
47import 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 */
56public 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     */
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     */
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     */
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
256    private static void readCert(XmlPullParser parser) throws IOException,
257            XmlPullParserException {
258        parser.require(XmlPullParser.START_TAG, null, "cert");
259        parser.nextTag();
260    }
261
262    private static void readSeinfo(XmlPullParser parser) throws IOException,
263            XmlPullParserException {
264        parser.require(XmlPullParser.START_TAG, null, "seinfo");
265        parser.nextTag();
266    }
267
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     */
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     */
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     */
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     */
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 */
414final 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
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     */
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     */
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     */
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     */
461    public boolean hasGlobalSeinfo() {
462        return mSeinfo != null;
463    }
464
465    @Override
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     */
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
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         */
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         */
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         */
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         */
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         */
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
689final class PolicyComparator implements Comparator<Policy> {
690
691    private boolean duplicateFound = false;
692
693    public boolean foundDuplicate() {
694        return duplicateFound;
695    }
696
697    @Override
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