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