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