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.content.pm.PackageParser.SigningDetails; 22 import android.os.Environment; 23 import android.util.Slog; 24 import android.util.Xml; 25 26 import libcore.io.IoUtils; 27 28 import org.xmlpull.v1.XmlPullParser; 29 import org.xmlpull.v1.XmlPullParserException; 30 31 import java.io.File; 32 import java.io.FileReader; 33 import java.io.IOException; 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.Comparator; 37 import java.util.HashMap; 38 import java.util.HashSet; 39 import java.util.List; 40 import java.util.Map; 41 import 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 */ 50 public 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 private static final String DEFAULT_SEINFO = "default"; 68 69 // Append privapp to existing seinfo label 70 private static final String PRIVILEGED_APP_STR = ":privapp"; 71 72 // Append v2 to existing seinfo label 73 private static final String SANDBOX_V2_STR = ":v2"; 74 75 // Append targetSdkVersion=n to existing seinfo label where n is the app's targetSdkVersion 76 private static final String TARGETSDKVERSION_STR = ":targetSdkVersion="; 77 78 // Only initialize sMacPermissions once. 79 static { 80 // Platform mac permissions. 81 sMacPermissions.add(new File( 82 Environment.getRootDirectory(), "/etc/selinux/plat_mac_permissions.xml")); 83 84 // Vendor mac permissions. 85 // The filename has been renamed from nonplat_mac_permissions to 86 // vendor_mac_permissions. Either of them should exist. 87 final File vendorMacPermission = new File( 88 Environment.getVendorDirectory(), "/etc/selinux/vendor_mac_permissions.xml"); 89 if (vendorMacPermission.exists()) { 90 sMacPermissions.add(vendorMacPermission); 91 } else { 92 // For backward compatibility. 93 sMacPermissions.add(new File(Environment.getVendorDirectory(), 94 "/etc/selinux/nonplat_mac_permissions.xml")); 95 } 96 97 // ODM mac permissions (optional). 98 final File odmMacPermission = new File( 99 Environment.getOdmDirectory(), "/etc/selinux/odm_mac_permissions.xml"); 100 if (odmMacPermission.exists()) { 101 sMacPermissions.add(odmMacPermission); 102 } 103 } 104 105 /** 106 * Load the mac_permissions.xml file containing all seinfo assignments used to 107 * label apps. The loaded mac_permissions.xml files are plat_mac_permissions.xml and 108 * vendor_mac_permissions.xml, on /system and /vendor partitions, respectively. 109 * odm_mac_permissions.xml on /odm partition is optional. For further guidance on 110 * the proper structure of a mac_permissions.xml file consult the source code 111 * located at system/sepolicy/private/mac_permissions.xml. 112 * 113 * @return boolean indicating if policy was correctly loaded. A value of false 114 * typically indicates a structural problem with the xml or incorrectly 115 * constructed policy stanzas. A value of true means that all stanzas 116 * were loaded successfully; no partial loading is possible. 117 */ 118 public static boolean readInstallPolicy() { 119 synchronized (sPolicies) { 120 if (sPolicyRead) { 121 return true; 122 } 123 } 124 125 // Temp structure to hold the rules while we parse the xml file 126 List<Policy> policies = new ArrayList<>(); 127 128 FileReader policyFile = null; 129 XmlPullParser parser = Xml.newPullParser(); 130 131 final int count = sMacPermissions.size(); 132 for (int i = 0; i < count; ++i) { 133 final File macPermission = sMacPermissions.get(i); 134 try { 135 policyFile = new FileReader(macPermission); 136 Slog.d(TAG, "Using policy file " + macPermission); 137 138 parser.setInput(policyFile); 139 parser.nextTag(); 140 parser.require(XmlPullParser.START_TAG, null, "policy"); 141 142 while (parser.next() != XmlPullParser.END_TAG) { 143 if (parser.getEventType() != XmlPullParser.START_TAG) { 144 continue; 145 } 146 147 switch (parser.getName()) { 148 case "signer": 149 policies.add(readSignerOrThrow(parser)); 150 break; 151 default: 152 skip(parser); 153 } 154 } 155 } catch (IllegalStateException | IllegalArgumentException | 156 XmlPullParserException ex) { 157 StringBuilder sb = new StringBuilder("Exception @"); 158 sb.append(parser.getPositionDescription()); 159 sb.append(" while parsing "); 160 sb.append(macPermission); 161 sb.append(":"); 162 sb.append(ex); 163 Slog.w(TAG, sb.toString()); 164 return false; 165 } catch (IOException ioe) { 166 Slog.w(TAG, "Exception parsing " + macPermission, ioe); 167 return false; 168 } finally { 169 IoUtils.closeQuietly(policyFile); 170 } 171 } 172 173 // Now sort the policy stanzas 174 PolicyComparator policySort = new PolicyComparator(); 175 Collections.sort(policies, policySort); 176 if (policySort.foundDuplicate()) { 177 Slog.w(TAG, "ERROR! Duplicate entries found parsing mac_permissions.xml files"); 178 return false; 179 } 180 181 synchronized (sPolicies) { 182 sPolicies.clear(); 183 sPolicies.addAll(policies); 184 sPolicyRead = true; 185 186 if (DEBUG_POLICY_ORDER) { 187 for (Policy policy : sPolicies) { 188 Slog.d(TAG, "Policy: " + policy.toString()); 189 } 190 } 191 } 192 193 return true; 194 } 195 196 /** 197 * Loop over a signer tag looking for seinfo, package and cert tags. A {@link Policy} 198 * instance will be created and returned in the process. During the pass all other 199 * tag elements will be skipped. 200 * 201 * @param parser an XmlPullParser object representing a signer element. 202 * @return the constructed {@link Policy} instance 203 * @throws IOException 204 * @throws XmlPullParserException 205 * @throws IllegalArgumentException if any of the validation checks fail while 206 * parsing tag values. 207 * @throws IllegalStateException if any of the invariants fail when constructing 208 * the {@link Policy} instance. 209 */ 210 private static Policy readSignerOrThrow(XmlPullParser parser) throws IOException, 211 XmlPullParserException { 212 213 parser.require(XmlPullParser.START_TAG, null, "signer"); 214 Policy.PolicyBuilder pb = new Policy.PolicyBuilder(); 215 216 // Check for a cert attached to the signer tag. We allow a signature 217 // to appear as an attribute as well as those attached to cert tags. 218 String cert = parser.getAttributeValue(null, "signature"); 219 if (cert != null) { 220 pb.addSignature(cert); 221 } 222 223 while (parser.next() != XmlPullParser.END_TAG) { 224 if (parser.getEventType() != XmlPullParser.START_TAG) { 225 continue; 226 } 227 228 String tagName = parser.getName(); 229 if ("seinfo".equals(tagName)) { 230 String seinfo = parser.getAttributeValue(null, "value"); 231 pb.setGlobalSeinfoOrThrow(seinfo); 232 readSeinfo(parser); 233 } else if ("package".equals(tagName)) { 234 readPackageOrThrow(parser, pb); 235 } else if ("cert".equals(tagName)) { 236 String sig = parser.getAttributeValue(null, "signature"); 237 pb.addSignature(sig); 238 readCert(parser); 239 } else { 240 skip(parser); 241 } 242 } 243 244 return pb.build(); 245 } 246 247 /** 248 * Loop over a package element looking for seinfo child tags. If found return the 249 * value attribute of the seinfo tag, otherwise return null. All other tags encountered 250 * will be skipped. 251 * 252 * @param parser an XmlPullParser object representing a package element. 253 * @param pb a Policy.PolicyBuilder instance to build 254 * @throws IOException 255 * @throws XmlPullParserException 256 * @throws IllegalArgumentException if any of the validation checks fail while 257 * parsing tag values. 258 * @throws IllegalStateException if there is a duplicate seinfo tag for the current 259 * package tag. 260 */ 261 private static void readPackageOrThrow(XmlPullParser parser, Policy.PolicyBuilder pb) throws 262 IOException, XmlPullParserException { 263 parser.require(XmlPullParser.START_TAG, null, "package"); 264 String pkgName = parser.getAttributeValue(null, "name"); 265 266 while (parser.next() != XmlPullParser.END_TAG) { 267 if (parser.getEventType() != XmlPullParser.START_TAG) { 268 continue; 269 } 270 271 String tagName = parser.getName(); 272 if ("seinfo".equals(tagName)) { 273 String seinfo = parser.getAttributeValue(null, "value"); 274 pb.addInnerPackageMapOrThrow(pkgName, seinfo); 275 readSeinfo(parser); 276 } else { 277 skip(parser); 278 } 279 } 280 } 281 282 private static void readCert(XmlPullParser parser) throws IOException, 283 XmlPullParserException { 284 parser.require(XmlPullParser.START_TAG, null, "cert"); 285 parser.nextTag(); 286 } 287 288 private static void readSeinfo(XmlPullParser parser) throws IOException, 289 XmlPullParserException { 290 parser.require(XmlPullParser.START_TAG, null, "seinfo"); 291 parser.nextTag(); 292 } 293 294 private static void skip(XmlPullParser p) throws IOException, XmlPullParserException { 295 if (p.getEventType() != XmlPullParser.START_TAG) { 296 throw new IllegalStateException(); 297 } 298 int depth = 1; 299 while (depth != 0) { 300 switch (p.next()) { 301 case XmlPullParser.END_TAG: 302 depth--; 303 break; 304 case XmlPullParser.START_TAG: 305 depth++; 306 break; 307 } 308 } 309 } 310 311 /** 312 * Selects a security label to a package based on input parameters and the seinfo tag taken 313 * from a matched policy. All signature based policy stanzas are consulted and, if no match 314 * is found, the default seinfo label of 'default' is used. The security label is attached to 315 * the ApplicationInfo instance of the package. 316 * 317 * @param pkg object representing the package to be labeled. 318 * @param isPrivileged boolean. 319 * @param targetSandboxVersion int. 320 * @param targetSdkVersion int. If this pkg runs as a sharedUser, targetSdkVersion is the 321 * greater of: lowest targetSdk for all pkgs in the sharedUser, or 322 * MINIMUM_TARGETSDKVERSION. 323 * @return String representing the resulting seinfo. 324 */ 325 public static String getSeInfo(PackageParser.Package pkg, boolean isPrivileged, 326 int targetSandboxVersion, int targetSdkVersion) { 327 String seInfo = null; 328 synchronized (sPolicies) { 329 if (!sPolicyRead) { 330 if (DEBUG_POLICY) { 331 Slog.d(TAG, "Policy not read"); 332 } 333 } else { 334 for (Policy policy : sPolicies) { 335 seInfo = policy.getMatchedSeInfo(pkg); 336 if (seInfo != null) { 337 break; 338 } 339 } 340 } 341 } 342 343 if (seInfo == null) { 344 seInfo = DEFAULT_SEINFO; 345 } 346 347 if (targetSandboxVersion == 2) { 348 seInfo += SANDBOX_V2_STR; 349 } 350 351 if (isPrivileged) { 352 seInfo += PRIVILEGED_APP_STR; 353 } 354 355 seInfo += TARGETSDKVERSION_STR + targetSdkVersion; 356 357 if (DEBUG_POLICY_INSTALL) { 358 Slog.i(TAG, "package (" + pkg.packageName + ") labeled with " + 359 "seinfo=" + seInfo); 360 } 361 return seInfo; 362 } 363 } 364 365 /** 366 * Holds valid policy representations of individual stanzas from a mac_permissions.xml 367 * file. Each instance can further be used to assign seinfo values to apks using the 368 * {@link Policy#getMatchedSeinfo} method. To create an instance of this use the 369 * {@link PolicyBuilder} pattern class, where each instance is validated against a set 370 * of invariants before being built and returned. Each instance can be guaranteed to 371 * hold one valid policy stanza as outlined in the system/sepolicy/mac_permissions.xml 372 * file. 373 * <p> 374 * The following is an example of how to use {@link Policy.PolicyBuilder} to create a 375 * signer based Policy instance with only inner package name refinements. 376 * </p> 377 * <pre> 378 * {@code 379 * Policy policy = new Policy.PolicyBuilder() 380 * .addSignature("308204a8...") 381 * .addSignature("483538c8...") 382 * .addInnerPackageMapOrThrow("com.foo.", "bar") 383 * .addInnerPackageMapOrThrow("com.foo.other", "bar") 384 * .build(); 385 * } 386 * </pre> 387 * <p> 388 * The following is an example of how to use {@link Policy.PolicyBuilder} to create a 389 * signer based Policy instance with only a global seinfo tag. 390 * </p> 391 * <pre> 392 * {@code 393 * Policy policy = new Policy.PolicyBuilder() 394 * .addSignature("308204a8...") 395 * .addSignature("483538c8...") 396 * .setGlobalSeinfoOrThrow("paltform") 397 * .build(); 398 * } 399 * </pre> 400 */ 401 final class Policy { 402 403 private final String mSeinfo; 404 private final Set<Signature> mCerts; 405 private final Map<String, String> mPkgMap; 406 407 // Use the PolicyBuilder pattern to instantiate 408 private Policy(PolicyBuilder builder) { 409 mSeinfo = builder.mSeinfo; 410 mCerts = Collections.unmodifiableSet(builder.mCerts); 411 mPkgMap = Collections.unmodifiableMap(builder.mPkgMap); 412 } 413 414 /** 415 * Return all the certs stored with this policy stanza. 416 * 417 * @return A set of Signature objects representing all the certs stored 418 * with the policy. 419 */ 420 public Set<Signature> getSignatures() { 421 return mCerts; 422 } 423 424 /** 425 * Return whether this policy object contains package name mapping refinements. 426 * 427 * @return A boolean indicating if this object has inner package name mappings. 428 */ 429 public boolean hasInnerPackages() { 430 return !mPkgMap.isEmpty(); 431 } 432 433 /** 434 * Return the mapping of all package name refinements. 435 * 436 * @return A Map object whose keys are the package names and whose values are 437 * the seinfo assignments. 438 */ 439 public Map<String, String> getInnerPackages() { 440 return mPkgMap; 441 } 442 443 /** 444 * Return whether the policy object has a global seinfo tag attached. 445 * 446 * @return A boolean indicating if this stanza has a global seinfo tag. 447 */ 448 public boolean hasGlobalSeinfo() { 449 return mSeinfo != null; 450 } 451 452 @Override 453 public String toString() { 454 StringBuilder sb = new StringBuilder(); 455 for (Signature cert : mCerts) { 456 sb.append("cert=" + cert.toCharsString().substring(0, 11) + "... "); 457 } 458 459 if (mSeinfo != null) { 460 sb.append("seinfo=" + mSeinfo); 461 } 462 463 for (String name : mPkgMap.keySet()) { 464 sb.append(" " + name + "=" + mPkgMap.get(name)); 465 } 466 467 return sb.toString(); 468 } 469 470 /** 471 * <p> 472 * Determine the seinfo value to assign to an apk. The appropriate seinfo value 473 * is determined using the following steps: 474 * </p> 475 * <ul> 476 * <li> All certs used to sign the apk and all certs stored with this policy 477 * instance are tested for set equality. If this fails then null is returned. 478 * </li> 479 * <li> If all certs match then an appropriate inner package stanza is 480 * searched based on package name alone. If matched, the stored seinfo 481 * value for that mapping is returned. 482 * </li> 483 * <li> If all certs matched and no inner package stanza matches then return 484 * the global seinfo value. The returned value can be null in this case. 485 * </li> 486 * </ul> 487 * <p> 488 * In all cases, a return value of null should be interpreted as the apk failing 489 * to match this Policy instance; i.e. failing this policy stanza. 490 * </p> 491 * @param pkg the apk to check given as a PackageParser.Package object 492 * @return A string representing the seinfo matched during policy lookup. 493 * A value of null can also be returned if no match occured. 494 */ 495 public String getMatchedSeInfo(PackageParser.Package pkg) { 496 // Check for exact signature matches across all certs. 497 Signature[] certs = mCerts.toArray(new Signature[0]); 498 if (pkg.mSigningDetails != SigningDetails.UNKNOWN 499 && !Signature.areExactMatch(certs, pkg.mSigningDetails.signatures)) { 500 501 // certs aren't exact match, but the package may have rotated from the known system cert 502 if (certs.length > 1 || !pkg.mSigningDetails.hasCertificate(certs[0])) { 503 return null; 504 } 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 685 public boolean foundDuplicate() { 686 return duplicateFound; 687 } 688 689 @Override 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