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