* No longer support a package name stanza outside of
a signature tag. Package names, by themselves, have
no security associated with them in Android and thus we
should not be allowing or encouraging this
type of policy.
* Allow for nested package name stanzas inside
signature stanzas. There are cases where a finer
distinction needs to be made among apps signed with
the same cert. New code allows a different seinfo
tag to be assigned to the listed package names
signed by the parent cert. When a determination needs
to be made concerning seinfo assignments, the inner
seinfo tag takes precedence over the outer seinfo
labels which are assigned to just the signature.
* Temp structures are now used to parse new policy files
until the entire xml file is parsed and deemed correct,
at which time the temp structures are copied over to the
permanent class structures. This ensures that any structural
errors with the policy will not result in partial loads.
* Valid stanzas look like the following with the inner
package piece being optional.
<signer signature="">
<seinfo value=""/>
<package name="">
<seinfo value=""/>
</package>
<signer>
<default>
<seinfo value=""/>
</default>
Change-Id: Ia204d71211776dcf9b2dcc86ad6d77c4ad39dc25
395 lines
13 KiB
Java
395 lines
13 KiB
Java
/*
|
|
* 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<Signature, Policy> sSigSeinfo =
|
|
new HashMap<Signature, Policy>();
|
|
|
|
// 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<String, String> pkgMap;
|
|
|
|
Policy() {
|
|
seinfo = null;
|
|
pkgMap = new HashMap<String, String>();
|
|
}
|
|
|
|
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<Signature, Policy> sigSeinfo = new HashMap<Signature, Policy>();
|
|
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, "<signer> without signature at "
|
|
+ parser.getPositionDescription());
|
|
XmlUtils.skipCurrentTag(parser);
|
|
continue;
|
|
}
|
|
Signature signature;
|
|
try {
|
|
signature = new Signature(cert);
|
|
} catch (IllegalArgumentException e) {
|
|
Slog.w(TAG, "<signer> 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, "<default> 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, "<package> 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, "<seinfo> 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<N; i++) {
|
|
final char c = name.charAt(i);
|
|
if ((c >= '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);
|
|
}
|
|
}
|