blob: 1d68afa546e8118f25afba3d6a2da13209af0070 [file] [log] [blame]
Robert Craigd3f8d032013-03-25 06:33:03 -04001/*
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
17package com.android.server.pm;
18
19import android.content.pm.ApplicationInfo;
20import android.content.pm.PackageParser;
21import android.content.pm.Signature;
22import android.os.Environment;
23import android.util.Slog;
24import android.util.Xml;
25
26import com.android.internal.util.XmlUtils;
27
28import java.io.File;
29import java.io.FileInputStream;
30import java.io.FileNotFoundException;
31import java.io.FileReader;
32import java.io.IOException;
33
34import java.util.HashMap;
35
36import org.xmlpull.v1.XmlPullParser;
37import org.xmlpull.v1.XmlPullParserException;
38
39/**
40 * Centralized access to SELinux MMAC (middleware MAC) implementation.
41 * {@hide}
42 */
43public final class SELinuxMMAC {
44
45 private static final String TAG = "SELinuxMMAC";
46
47 private static final boolean DEBUG_POLICY = false;
48 private static final boolean DEBUG_POLICY_INSTALL = DEBUG_POLICY || false;
49
50 // Signature seinfo values read from policy.
Robert Craig99a626c2013-12-02 10:24:23 -050051 private static HashMap<Signature, Policy> sSigSeinfo =
52 new HashMap<Signature, Policy>();
Robert Craigd3f8d032013-03-25 06:33:03 -040053
Robert Craig99a626c2013-12-02 10:24:23 -050054 // Default seinfo read from policy.
55 private static String sDefaultSeinfo = null;
Robert Craigd3f8d032013-03-25 06:33:03 -040056
57 // Locations of potential install policy files.
58 private static final File[] INSTALL_POLICY_FILE = {
Stephen Smalley2c90ac62013-04-05 11:27:19 -040059 new File(Environment.getDataDirectory(), "security/mac_permissions.xml"),
Robert Craigd3f8d032013-03-25 06:33:03 -040060 new File(Environment.getRootDirectory(), "etc/security/mac_permissions.xml"),
61 null};
62
Robert Craig99a626c2013-12-02 10:24:23 -050063 // Signature policy stanzas
64 static class Policy {
65 private String seinfo;
66 private final HashMap<String, String> pkgMap;
67
68 Policy() {
69 seinfo = null;
70 pkgMap = new HashMap<String, String>();
71 }
72
73 void putSeinfo(String seinfoValue) {
74 seinfo = seinfoValue;
75 }
76
77 void putPkg(String pkg, String seinfoValue) {
78 pkgMap.put(pkg, seinfoValue);
79 }
80
81 // Valid policy stanza means there exists a global
82 // seinfo value or at least one package policy.
83 boolean isValid() {
84 return (seinfo != null) || (!pkgMap.isEmpty());
85 }
86
87 String checkPolicy(String pkgName) {
88 // Check for package name seinfo value first.
89 String seinfoValue = pkgMap.get(pkgName);
90 if (seinfoValue != null) {
91 return seinfoValue;
92 }
93
94 // Return the global seinfo value.
95 return seinfo;
96 }
97 }
98
Robert Craigd3f8d032013-03-25 06:33:03 -040099 private static void flushInstallPolicy() {
100 sSigSeinfo.clear();
Robert Craig99a626c2013-12-02 10:24:23 -0500101 sDefaultSeinfo = null;
Robert Craigd3f8d032013-03-25 06:33:03 -0400102 }
103
104 /**
105 * Parses an MMAC install policy from a predefined list of locations.
106 * @param none
107 * @return boolean indicating whether an install policy was correctly parsed.
108 */
109 public static boolean readInstallPolicy() {
110
111 return readInstallPolicy(INSTALL_POLICY_FILE);
112 }
113
114 /**
115 * Parses an MMAC install policy given as an argument.
116 * @param File object representing the path of the policy.
117 * @return boolean indicating whether the install policy was correctly parsed.
118 */
119 public static boolean readInstallPolicy(File policyFile) {
120
121 return readInstallPolicy(new File[]{policyFile,null});
122 }
123
124 private static boolean readInstallPolicy(File[] policyFiles) {
Robert Craig99a626c2013-12-02 10:24:23 -0500125 // Temp structures to hold the rules while we parse the xml file.
126 // We add all the rules together once we know there's no structural problems.
127 HashMap<Signature, Policy> sigSeinfo = new HashMap<Signature, Policy>();
128 String defaultSeinfo = null;
Robert Craigd3f8d032013-03-25 06:33:03 -0400129
130 FileReader policyFile = null;
131 int i = 0;
132 while (policyFile == null && policyFiles != null && policyFiles[i] != null) {
133 try {
134 policyFile = new FileReader(policyFiles[i]);
135 break;
136 } catch (FileNotFoundException e) {
137 Slog.d(TAG,"Couldn't find install policy " + policyFiles[i].getPath());
138 }
139 i++;
140 }
141
142 if (policyFile == null) {
143 Slog.d(TAG, "No policy file found. All seinfo values will be null.");
144 return false;
145 }
146
147 Slog.d(TAG, "Using install policy file " + policyFiles[i].getPath());
148
Robert Craigd3f8d032013-03-25 06:33:03 -0400149 try {
150 XmlPullParser parser = Xml.newPullParser();
151 parser.setInput(policyFile);
152
153 XmlUtils.beginDocument(parser, "policy");
154 while (true) {
155 XmlUtils.nextElement(parser);
156 if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
157 break;
158 }
159
160 String tagName = parser.getName();
161 if ("signer".equals(tagName)) {
162 String cert = parser.getAttributeValue(null, "signature");
163 if (cert == null) {
164 Slog.w(TAG, "<signer> without signature at "
165 + parser.getPositionDescription());
166 XmlUtils.skipCurrentTag(parser);
167 continue;
168 }
169 Signature signature;
170 try {
171 signature = new Signature(cert);
172 } catch (IllegalArgumentException e) {
173 Slog.w(TAG, "<signer> with bad signature at "
174 + parser.getPositionDescription(), e);
175 XmlUtils.skipCurrentTag(parser);
176 continue;
177 }
Robert Craig99a626c2013-12-02 10:24:23 -0500178 Policy policy = readPolicyTags(parser);
179 if (policy.isValid()) {
180 sigSeinfo.put(signature, policy);
Robert Craigd3f8d032013-03-25 06:33:03 -0400181 }
182 } else if ("default".equals(tagName)) {
Robert Craig99a626c2013-12-02 10:24:23 -0500183 // Value is null if default tag is absent or seinfo tag is malformed.
184 defaultSeinfo = readSeinfoTag(parser);
185 if (DEBUG_POLICY_INSTALL)
186 Slog.i(TAG, "<default> tag assigned seinfo=" + defaultSeinfo);
Robert Craigd3f8d032013-03-25 06:33:03 -0400187
Robert Craigd3f8d032013-03-25 06:33:03 -0400188 } else {
189 XmlUtils.skipCurrentTag(parser);
Robert Craigd3f8d032013-03-25 06:33:03 -0400190 }
191 }
192 } catch (XmlPullParserException e) {
Robert Craig99a626c2013-12-02 10:24:23 -0500193 // An error outside of a stanza means a structural problem
194 // with the xml file. So ignore it.
195 Slog.w(TAG, "Got exception parsing ", e);
196 return false;
Robert Craigd3f8d032013-03-25 06:33:03 -0400197 } catch (IOException e) {
Robert Craig99a626c2013-12-02 10:24:23 -0500198 Slog.w(TAG, "Got exception parsing ", e);
199 return false;
200 } finally {
201 try {
202 policyFile.close();
203 } catch (IOException e) {
204 //omit
205 }
Robert Craigd3f8d032013-03-25 06:33:03 -0400206 }
Robert Craig99a626c2013-12-02 10:24:23 -0500207
208 flushInstallPolicy();
209 sSigSeinfo = sigSeinfo;
210 sDefaultSeinfo = defaultSeinfo;
211
Robert Craigd3f8d032013-03-25 06:33:03 -0400212 return true;
213 }
214
Robert Craig99a626c2013-12-02 10:24:23 -0500215 private static Policy readPolicyTags(XmlPullParser parser) throws
216 IOException, XmlPullParserException {
217
218 int type;
219 int outerDepth = parser.getDepth();
220 Policy policy = new Policy();
221 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
222 && (type != XmlPullParser.END_TAG
223 || parser.getDepth() > outerDepth)) {
224 if (type == XmlPullParser.END_TAG
225 || type == XmlPullParser.TEXT) {
226 continue;
227 }
228
229 String tagName = parser.getName();
230 if ("seinfo".equals(tagName)) {
231 String seinfo = parseSeinfo(parser);
232 if (seinfo != null) {
233 policy.putSeinfo(seinfo);
234 }
235 XmlUtils.skipCurrentTag(parser);
236 } else if ("package".equals(tagName)) {
237 String pkg = parser.getAttributeValue(null, "name");
238 if (!validatePackageName(pkg)) {
239 Slog.w(TAG, "<package> without valid name at "
240 + parser.getPositionDescription());
241 XmlUtils.skipCurrentTag(parser);
242 continue;
243 }
244
245 String seinfo = readSeinfoTag(parser);
246 if (seinfo != null) {
247 policy.putPkg(pkg, seinfo);
248 }
249 } else {
250 XmlUtils.skipCurrentTag(parser);
251 }
252 }
253 return policy;
254 }
255
Robert Craigd3f8d032013-03-25 06:33:03 -0400256 private static String readSeinfoTag(XmlPullParser parser) throws
257 IOException, XmlPullParserException {
258
259 int type;
260 int outerDepth = parser.getDepth();
261 String seinfo = null;
262 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
263 && (type != XmlPullParser.END_TAG
264 || parser.getDepth() > outerDepth)) {
265 if (type == XmlPullParser.END_TAG
266 || type == XmlPullParser.TEXT) {
267 continue;
268 }
269
270 String tagName = parser.getName();
271 if ("seinfo".equals(tagName)) {
Robert Craig99a626c2013-12-02 10:24:23 -0500272 seinfo = parseSeinfo(parser);
Robert Craigd3f8d032013-03-25 06:33:03 -0400273 }
274 XmlUtils.skipCurrentTag(parser);
275 }
276 return seinfo;
277 }
278
Robert Craig99a626c2013-12-02 10:24:23 -0500279 private static String parseSeinfo(XmlPullParser parser) {
280
281 String seinfoValue = parser.getAttributeValue(null, "value");
282 if (!validateValue(seinfoValue)) {
283 Slog.w(TAG, "<seinfo> without valid value at "
284 + parser.getPositionDescription());
285 seinfoValue = null;
286 }
287 return seinfoValue;
288 }
289
290 /**
291 * General validation routine for package names.
292 * Returns a boolean indicating if the passed string
293 * is a valid android package name.
294 */
295 private static boolean validatePackageName(String name) {
296 if (name == null)
297 return false;
298
299 final int N = name.length();
300 boolean hasSep = false;
301 boolean front = true;
302 for (int i=0; i<N; i++) {
303 final char c = name.charAt(i);
304 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
305 front = false;
306 continue;
307 }
308 if (!front) {
309 if ((c >= '0' && c <= '9') || c == '_') {
310 continue;
311 }
312 }
313 if (c == '.') {
314 hasSep = true;
315 front = true;
316 continue;
317 }
318 return false;
319 }
320 return hasSep;
321 }
322
Robert Craigd3f8d032013-03-25 06:33:03 -0400323 /**
Robert Craigd417ab02013-03-28 06:22:12 -0400324 * General validation routine for tag values.
325 * Returns a boolean indicating if the passed string
326 * contains only letters or underscores.
327 */
328 private static boolean validateValue(String name) {
329 if (name == null)
330 return false;
331
332 final int N = name.length();
333 if (N == 0)
334 return false;
335
336 for (int i = 0; i < N; i++) {
337 final char c = name.charAt(i);
338 if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c != '_')) {
339 return false;
340 }
341 }
342 return true;
343 }
344
345 /**
Robert Craigd3f8d032013-03-25 06:33:03 -0400346 * Labels a package based on an seinfo tag from install policy.
347 * The label is attached to the ApplicationInfo instance of the package.
348 * @param PackageParser.Package object representing the package
349 * to labeled.
Robert Craig99a626c2013-12-02 10:24:23 -0500350 * @return boolean which determines whether a non null seinfo label
351 * was assigned to the package. A null value simply meaning that
352 * no policy matched.
Robert Craigd3f8d032013-03-25 06:33:03 -0400353 */
Robert Craig99a626c2013-12-02 10:24:23 -0500354 public static boolean assignSeinfoValue(PackageParser.Package pkg) {
Robert Craigd3f8d032013-03-25 06:33:03 -0400355
356 /*
357 * Non system installed apps should be treated the same. This
358 * means that any post-loaded apk will be assigned the default
359 * tag, if one exists in the policy, else null, without respect
360 * to the signing key.
361 */
362 if (((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) ||
363 ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) {
364
365 // We just want one of the signatures to match.
366 for (Signature s : pkg.mSignatures) {
367 if (s == null)
368 continue;
369
Robert Craig99a626c2013-12-02 10:24:23 -0500370 Policy policy = sSigSeinfo.get(s);
371 if (policy != null) {
372 String seinfo = policy.checkPolicy(pkg.packageName);
373 if (seinfo != null) {
374 pkg.applicationInfo.seinfo = seinfo;
375 if (DEBUG_POLICY_INSTALL)
376 Slog.i(TAG, "package (" + pkg.packageName +
377 ") labeled with seinfo=" + seinfo);
Robert Craigd3f8d032013-03-25 06:33:03 -0400378
Robert Craig99a626c2013-12-02 10:24:23 -0500379 return true;
380 }
Robert Craigd3f8d032013-03-25 06:33:03 -0400381 }
382 }
Robert Craigd3f8d032013-03-25 06:33:03 -0400383 }
384
385 // If we have a default seinfo value then great, otherwise
386 // we set a null object and that is what we started with.
Robert Craig99a626c2013-12-02 10:24:23 -0500387 pkg.applicationInfo.seinfo = sDefaultSeinfo;
Robert Craigd3f8d032013-03-25 06:33:03 -0400388 if (DEBUG_POLICY_INSTALL)
Robert Craig99a626c2013-12-02 10:24:23 -0500389 Slog.i(TAG, "package (" + pkg.packageName + ") labeled with seinfo="
390 + (sDefaultSeinfo == null ? "null" : sDefaultSeinfo));
391
392 return (sDefaultSeinfo != null);
Robert Craigd3f8d032013-03-25 06:33:03 -0400393 }
394}