/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.pm; import android.content.pm.ApplicationInfo; import android.content.pm.PackageParser; import android.content.pm.Signature; import android.os.Environment; import android.util.Slog; import android.util.Xml; import com.android.internal.util.XmlUtils; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.HashMap; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; /** * Centralized access to SELinux MMAC (middleware MAC) implementation. * {@hide} */ public final class SELinuxMMAC { private static final String TAG = "SELinuxMMAC"; private static final boolean DEBUG_POLICY = false; private static final boolean DEBUG_POLICY_INSTALL = DEBUG_POLICY || false; // Signature seinfo values read from policy. private static HashMap sSigSeinfo = new HashMap(); // Default seinfo read from policy. private static String sDefaultSeinfo = null; // Locations of potential install policy files. private static final File[] INSTALL_POLICY_FILE = { new File(Environment.getDataDirectory(), "security/mac_permissions.xml"), new File(Environment.getRootDirectory(), "etc/security/mac_permissions.xml"), null}; // Signature policy stanzas static class Policy { private String seinfo; private final HashMap pkgMap; Policy() { seinfo = null; pkgMap = new HashMap(); } void putSeinfo(String seinfoValue) { seinfo = seinfoValue; } void putPkg(String pkg, String seinfoValue) { pkgMap.put(pkg, seinfoValue); } // Valid policy stanza means there exists a global // seinfo value or at least one package policy. boolean isValid() { return (seinfo != null) || (!pkgMap.isEmpty()); } String checkPolicy(String pkgName) { // Check for package name seinfo value first. String seinfoValue = pkgMap.get(pkgName); if (seinfoValue != null) { return seinfoValue; } // Return the global seinfo value. return seinfo; } } private static void flushInstallPolicy() { sSigSeinfo.clear(); sDefaultSeinfo = null; } /** * Parses an MMAC install policy from a predefined list of locations. * @param none * @return boolean indicating whether an install policy was correctly parsed. */ public static boolean readInstallPolicy() { return readInstallPolicy(INSTALL_POLICY_FILE); } /** * Parses an MMAC install policy given as an argument. * @param File object representing the path of the policy. * @return boolean indicating whether the install policy was correctly parsed. */ public static boolean readInstallPolicy(File policyFile) { return readInstallPolicy(new File[]{policyFile,null}); } private static boolean readInstallPolicy(File[] policyFiles) { // Temp structures to hold the rules while we parse the xml file. // We add all the rules together once we know there's no structural problems. HashMap sigSeinfo = new HashMap(); String defaultSeinfo = null; FileReader policyFile = null; int i = 0; while (policyFile == null && policyFiles != null && policyFiles[i] != null) { try { policyFile = new FileReader(policyFiles[i]); break; } catch (FileNotFoundException e) { Slog.d(TAG,"Couldn't find install policy " + policyFiles[i].getPath()); } i++; } if (policyFile == null) { Slog.d(TAG, "No policy file found. All seinfo values will be null."); return false; } Slog.d(TAG, "Using install policy file " + policyFiles[i].getPath()); try { XmlPullParser parser = Xml.newPullParser(); parser.setInput(policyFile); XmlUtils.beginDocument(parser, "policy"); while (true) { XmlUtils.nextElement(parser); if (parser.getEventType() == XmlPullParser.END_DOCUMENT) { break; } String tagName = parser.getName(); if ("signer".equals(tagName)) { String cert = parser.getAttributeValue(null, "signature"); if (cert == null) { Slog.w(TAG, " without signature at " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } Signature signature; try { signature = new Signature(cert); } catch (IllegalArgumentException e) { Slog.w(TAG, " with bad signature at " + parser.getPositionDescription(), e); XmlUtils.skipCurrentTag(parser); continue; } Policy policy = readPolicyTags(parser); if (policy.isValid()) { sigSeinfo.put(signature, policy); } } else if ("default".equals(tagName)) { // Value is null if default tag is absent or seinfo tag is malformed. defaultSeinfo = readSeinfoTag(parser); if (DEBUG_POLICY_INSTALL) Slog.i(TAG, " tag assigned seinfo=" + defaultSeinfo); } else { XmlUtils.skipCurrentTag(parser); } } } catch (XmlPullParserException e) { // An error outside of a stanza means a structural problem // with the xml file. So ignore it. Slog.w(TAG, "Got exception parsing ", e); return false; } catch (IOException e) { Slog.w(TAG, "Got exception parsing ", e); return false; } finally { try { policyFile.close(); } catch (IOException e) { //omit } } flushInstallPolicy(); sSigSeinfo = sigSeinfo; sDefaultSeinfo = defaultSeinfo; return true; } private static Policy readPolicyTags(XmlPullParser parser) throws IOException, XmlPullParserException { int type; int outerDepth = parser.getDepth(); Policy policy = new Policy(); while ((type=parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { continue; } String tagName = parser.getName(); if ("seinfo".equals(tagName)) { String seinfo = parseSeinfo(parser); if (seinfo != null) { policy.putSeinfo(seinfo); } XmlUtils.skipCurrentTag(parser); } else if ("package".equals(tagName)) { String pkg = parser.getAttributeValue(null, "name"); if (!validatePackageName(pkg)) { Slog.w(TAG, " without valid name at " + parser.getPositionDescription()); XmlUtils.skipCurrentTag(parser); continue; } String seinfo = readSeinfoTag(parser); if (seinfo != null) { policy.putPkg(pkg, seinfo); } } else { XmlUtils.skipCurrentTag(parser); } } return policy; } private static String readSeinfoTag(XmlPullParser parser) throws IOException, XmlPullParserException { int type; int outerDepth = parser.getDepth(); String seinfo = null; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { continue; } String tagName = parser.getName(); if ("seinfo".equals(tagName)) { seinfo = parseSeinfo(parser); } XmlUtils.skipCurrentTag(parser); } return seinfo; } private static String parseSeinfo(XmlPullParser parser) { String seinfoValue = parser.getAttributeValue(null, "value"); if (!validateValue(seinfoValue)) { Slog.w(TAG, " without valid value at " + parser.getPositionDescription()); seinfoValue = null; } return seinfoValue; } /** * General validation routine for package names. * Returns a boolean indicating if the passed string * is a valid android package name. */ private static boolean validatePackageName(String name) { if (name == null) return false; final int N = name.length(); boolean hasSep = false; boolean front = true; for (int i=0; i= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { front = false; continue; } if (!front) { if ((c >= '0' && c <= '9') || c == '_') { continue; } } if (c == '.') { hasSep = true; front = true; continue; } return false; } return hasSep; } /** * General validation routine for tag values. * Returns a boolean indicating if the passed string * contains only letters or underscores. */ private static boolean validateValue(String name) { if (name == null) return false; final int N = name.length(); if (N == 0) return false; for (int i = 0; i < N; i++) { final char c = name.charAt(i); if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c != '_')) { return false; } } return true; } /** * Labels a package based on an seinfo tag from install policy. * The label is attached to the ApplicationInfo instance of the package. * @param PackageParser.Package object representing the package * to labeled. * @return boolean which determines whether a non null seinfo label * was assigned to the package. A null value simply meaning that * no policy matched. */ public static boolean assignSeinfoValue(PackageParser.Package pkg) { /* * Non system installed apps should be treated the same. This * means that any post-loaded apk will be assigned the default * tag, if one exists in the policy, else null, without respect * to the signing key. */ if (((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) || ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) { // We just want one of the signatures to match. for (Signature s : pkg.mSignatures) { if (s == null) continue; Policy policy = sSigSeinfo.get(s); if (policy != null) { String seinfo = policy.checkPolicy(pkg.packageName); if (seinfo != null) { pkg.applicationInfo.seinfo = seinfo; if (DEBUG_POLICY_INSTALL) Slog.i(TAG, "package (" + pkg.packageName + ") labeled with seinfo=" + seinfo); return true; } } } } // If we have a default seinfo value then great, otherwise // we set a null object and that is what we started with. pkg.applicationInfo.seinfo = sDefaultSeinfo; if (DEBUG_POLICY_INSTALL) Slog.i(TAG, "package (" + pkg.packageName + ") labeled with seinfo=" + (sDefaultSeinfo == null ? "null" : sDefaultSeinfo)); return (sDefaultSeinfo != null); } }