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