2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.server.pm;
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;
28 import libcore.io.IoUtils;
30 import org.xmlpull.v1.XmlPullParser;
31 import org.xmlpull.v1.XmlPullParserException;
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;
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.
55 public final class SELinuxMMAC {
57 static final String TAG = "SELinuxMMAC";
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;
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<>();
67 /** Path to version on rootfs */
68 private static final File VERSION_FILE = new File("/selinux_version");
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");
74 /** Path to app contexts on rootfs */
75 private static final File SEAPP_CONTEXTS = new File("/seapp_contexts");
77 /** Calculated hash of {@link #SEAPP_CONTEXTS} */
78 private static final byte[] SEAPP_CONTEXTS_HASH = returnHash(SEAPP_CONTEXTS);
80 /** Attribute where {@link #SEAPP_CONTEXTS_HASH} is stored */
81 private static final String XATTR_SEAPP_HASH = "user.seapp_hash";
83 // Append privapp to existing seinfo label
84 private static final String PRIVILEGED_APP_STR = ":privapp";
86 // Append autoplay to existing seinfo label
87 private static final String AUTOPLAY_APP_STR = ":autoplayapp";
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.
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.
102 public static boolean readInstallPolicy() {
103 // Temp structure to hold the rules while we parse the xml file
104 List<Policy> policies = new ArrayList<>();
106 FileReader policyFile = null;
107 XmlPullParser parser = Xml.newPullParser();
109 policyFile = new FileReader(MAC_PERMISSIONS);
110 Slog.d(TAG, "Using policy file " + MAC_PERMISSIONS);
112 parser.setInput(policyFile);
114 parser.require(XmlPullParser.START_TAG, null, "policy");
116 while (parser.next() != XmlPullParser.END_TAG) {
117 if (parser.getEventType() != XmlPullParser.START_TAG) {
121 switch (parser.getName()) {
123 policies.add(readSignerOrThrow(parser));
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);
137 Slog.w(TAG, sb.toString());
139 } catch (IOException ioe) {
140 Slog.w(TAG, "Exception parsing " + MAC_PERMISSIONS, ioe);
143 IoUtils.closeQuietly(policyFile);
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);
154 synchronized (sPolicies) {
155 sPolicies = policies;
157 if (DEBUG_POLICY_ORDER) {
158 for (Policy policy : sPolicies) {
159 Slog.d(TAG, "Policy: " + policy.toString());
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.
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.
181 private static Policy readSignerOrThrow(XmlPullParser parser) throws IOException,
182 XmlPullParserException {
184 parser.require(XmlPullParser.START_TAG, null, "signer");
185 Policy.PolicyBuilder pb = new Policy.PolicyBuilder();
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");
191 pb.addSignature(cert);
194 while (parser.next() != XmlPullParser.END_TAG) {
195 if (parser.getEventType() != XmlPullParser.START_TAG) {
199 String tagName = parser.getName();
200 if ("seinfo".equals(tagName)) {
201 String seinfo = parser.getAttributeValue(null, "value");
202 pb.setGlobalSeinfoOrThrow(seinfo);
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);
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
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
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");
237 while (parser.next() != XmlPullParser.END_TAG) {
238 if (parser.getEventType() != XmlPullParser.START_TAG) {
242 String tagName = parser.getName();
243 if ("seinfo".equals(tagName)) {
244 String seinfo = parser.getAttributeValue(null, "value");
245 pb.addInnerPackageMapOrThrow(pkgName, seinfo);
253 private static void readCert(XmlPullParser parser) throws IOException,
254 XmlPullParserException {
255 parser.require(XmlPullParser.START_TAG, null, "cert");
259 private static void readSeinfo(XmlPullParser parser) throws IOException,
260 XmlPullParserException {
261 parser.require(XmlPullParser.START_TAG, null, "seinfo");
265 private static void skip(XmlPullParser p) throws IOException, XmlPullParserException {
266 if (p.getEventType() != XmlPullParser.START_TAG) {
267 throw new IllegalStateException();
272 case XmlPullParser.END_TAG:
275 case XmlPullParser.START_TAG:
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.
289 * @param pkg object representing the package to be labeled.
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;
302 if (pkg.applicationInfo.isAutoPlayApp())
303 pkg.applicationInfo.seinfo += AUTOPLAY_APP_STR;
305 if (pkg.applicationInfo.isPrivilegedApp())
306 pkg.applicationInfo.seinfo += PRIVILEGED_APP_STR;
308 if (DEBUG_POLICY_INSTALL) {
309 Slog.i(TAG, "package (" + pkg.packageName + ") labeled with " +
310 "seinfo=" + pkg.applicationInfo.seinfo);
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.
319 * Note that the xattr isn't in the 'security' namespace, so this should
320 * only be run on directories owned by the system.
322 * @return Returns true if the restorecon should occur or false otherwise.
324 public static boolean isRestoreconNeeded(File file) {
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)) {
331 } catch (ErrnoException e) {
332 if (e.errno != OsConstants.ENODATA) {
333 Slog.e(TAG, "Failed to read seapp hash for " + file, e);
341 * Stores the SHA-1 of the seapp_contexts into an xattr.
343 * Note that the xattr isn't in the 'security' namespace, so this should
344 * only be run on directories owned by the system.
346 public static void setRestoreconDone(File file) {
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);
355 * Return the SHA-1 of a file.
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.
360 private static byte[] returnHash(File file) {
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);
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
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.
384 * Policy policy = new Policy.PolicyBuilder()
385 * .addSignature("308204a8...")
386 * .addSignature("483538c8...")
387 * .addInnerPackageMapOrThrow("com.foo.", "bar")
388 * .addInnerPackageMapOrThrow("com.foo.other", "bar")
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.
398 * Policy policy = new Policy.PolicyBuilder()
399 * .addSignature("308204a8...")
400 * .addSignature("483538c8...")
401 * .setGlobalSeinfoOrThrow("paltform")
408 private final String mSeinfo;
409 private final Set<Signature> mCerts;
410 private final Map<String, String> mPkgMap;
412 // Use the PolicyBuilder pattern to instantiate
413 private Policy(PolicyBuilder builder) {
414 mSeinfo = builder.mSeinfo;
415 mCerts = Collections.unmodifiableSet(builder.mCerts);
416 mPkgMap = Collections.unmodifiableMap(builder.mPkgMap);
420 * Return all the certs stored with this policy stanza.
422 * @return A set of Signature objects representing all the certs stored
425 public Set<Signature> getSignatures() {
430 * Return whether this policy object contains package name mapping refinements.
432 * @return A boolean indicating if this object has inner package name mappings.
434 public boolean hasInnerPackages() {
435 return !mPkgMap.isEmpty();
439 * Return the mapping of all package name refinements.
441 * @return A Map object whose keys are the package names and whose values are
442 * the seinfo assignments.
444 public Map<String, String> getInnerPackages() {
449 * Return whether the policy object has a global seinfo tag attached.
451 * @return A boolean indicating if this stanza has a global seinfo tag.
453 public boolean hasGlobalSeinfo() {
454 return mSeinfo != null;
458 public String toString() {
459 StringBuilder sb = new StringBuilder();
460 for (Signature cert : mCerts) {
461 sb.append("cert=" + cert.toCharsString().substring(0, 11) + "... ");
464 if (mSeinfo != null) {
465 sb.append("seinfo=" + mSeinfo);
468 for (String name : mPkgMap.keySet()) {
469 sb.append(" " + name + "=" + mPkgMap.get(name));
472 return sb.toString();
477 * Determine the seinfo value to assign to an apk. The appropriate seinfo value
478 * is determined using the following steps:
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.
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.
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.
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.
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.
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)) {
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) {
514 // Return the global seinfo value.
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.
526 public static final class PolicyBuilder {
528 private String mSeinfo;
529 private final Set<Signature> mCerts;
530 private final Map<String, String> mPkgMap;
532 public PolicyBuilder() {
533 mCerts = new HashSet<Signature>(2);
534 mPkgMap = new HashMap<String, String>(2);
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.
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.
547 public PolicyBuilder addSignature(String cert) {
549 String err = "Invalid signature value " + cert;
550 throw new IllegalArgumentException(err);
553 mCerts.add(new Signature(cert));
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.
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
568 public PolicyBuilder setGlobalSeinfoOrThrow(String seinfo) {
569 if (!validateValue(seinfo)) {
570 String err = "Invalid seinfo value " + seinfo;
571 throw new IllegalArgumentException(err);
574 if (mSeinfo != null && !mSeinfo.equals(seinfo)) {
575 String err = "Duplicate seinfo tag found";
576 throw new IllegalStateException(err);
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.
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.
597 public PolicyBuilder addInnerPackageMapOrThrow(String pkgName, String seinfo) {
598 if (!validateValue(pkgName)) {
599 String err = "Invalid package name " + pkgName;
600 throw new IllegalArgumentException(err);
602 if (!validateValue(seinfo)) {
603 String err = "Invalid seinfo value " + seinfo;
604 throw new IllegalArgumentException(err);
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);
613 mPkgMap.put(pkgName, seinfo);
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].
621 * @param name the string to validate.
622 * @return boolean indicating if the string was valid.
624 private boolean validateValue(String name) {
628 // Want to match on [0-9a-zA-Z_.]
629 if (!name.matches("\\A[\\.\\w]+\\z")) {
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:
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>
648 * @return an instance of {@link Policy} with the options set from this builder
649 * @throws IllegalStateException if an invariant is violated.
651 public Policy build() {
652 Policy p = new Policy(this);
654 if (p.mCerts.isEmpty()) {
655 String err = "Missing certs with signer tag. Expecting at least one.";
656 throw new IllegalStateException(err);
658 if (!(p.mSeinfo == null ^ p.mPkgMap.isEmpty())) {
659 String err = "Only seinfo tag XOR package tags are allowed within " +
661 throw new IllegalStateException(err);
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.
674 * <li> signer stanzas with inner package mappings </li>
675 * <li> signer stanzas with global seinfo tags </li>
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}.
681 final class PolicyComparator implements Comparator<Policy> {
683 private boolean duplicateFound = false;
685 public boolean foundDuplicate() {
686 return duplicateFound;
690 public int compare(Policy p1, Policy p2) {
692 // Give precedence to stanzas with inner package mappings
693 if (p1.hasInnerPackages() != p2.hasInnerPackages()) {
694 return p1.hasInnerPackages() ? -1 : 1;
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());
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());