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