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