blob: 81302b9e2169f3ef0bdc005ba4d4c0d0d5eb9718 [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
Robert Craig43853432014-03-04 11:57:23 -050028import libcore.io.IoUtils;
29
Robert Craigd3f8d032013-03-25 06:33:03 -040030import java.io.File;
Robert Craigd3f8d032013-03-25 06:33:03 -040031import java.io.FileNotFoundException;
Robert Craig43853432014-03-04 11:57:23 -050032import java.io.FileOutputStream;
Robert Craigd3f8d032013-03-25 06:33:03 -040033import java.io.FileReader;
34import java.io.IOException;
Robert Craig43853432014-03-04 11:57:23 -050035import java.security.MessageDigest;
36import java.security.NoSuchAlgorithmException;
Robert Craigd3f8d032013-03-25 06:33:03 -040037
38import java.util.HashMap;
39
40import org.xmlpull.v1.XmlPullParser;
41import org.xmlpull.v1.XmlPullParserException;
42
43/**
44 * Centralized access to SELinux MMAC (middleware MAC) implementation.
45 * {@hide}
46 */
47public final class SELinuxMMAC {
48
49 private static final String TAG = "SELinuxMMAC";
50
51 private static final boolean DEBUG_POLICY = false;
52 private static final boolean DEBUG_POLICY_INSTALL = DEBUG_POLICY || false;
53
54 // Signature seinfo values read from policy.
Robert Craigf8778292014-03-14 11:09:09 -040055 private static HashMap<Signature, Policy> sSigSeinfo = new HashMap<Signature, Policy>();
Robert Craigd3f8d032013-03-25 06:33:03 -040056
Robert Craig99a626c2013-12-02 10:24:23 -050057 // Default seinfo read from policy.
58 private static String sDefaultSeinfo = null;
Robert Craigd3f8d032013-03-25 06:33:03 -040059
Robert Craigf8778292014-03-14 11:09:09 -040060 // Data policy override version file.
61 private static final String DATA_VERSION_FILE =
62 Environment.getDataDirectory() + "/security/current/selinux_version";
Robert Craigd3f8d032013-03-25 06:33:03 -040063
Robert Craigf8778292014-03-14 11:09:09 -040064 // Base policy version file.
65 private static final String BASE_VERSION_FILE = "/selinux_version";
66
67 // Whether override security policies should be loaded.
68 private static final boolean USE_OVERRIDE_POLICY = useOverridePolicy();
69
70 // Data override mac_permissions.xml policy file.
71 private static final String DATA_MAC_PERMISSIONS =
72 Environment.getDataDirectory() + "/security/current/mac_permissions.xml";
73
74 // Base mac_permissions.xml policy file.
75 private static final String BASE_MAC_PERMISSIONS =
76 Environment.getRootDirectory() + "/etc/security/mac_permissions.xml";
77
78 // Determine which mac_permissions.xml file to use.
79 private static final String MAC_PERMISSIONS = USE_OVERRIDE_POLICY ?
80 DATA_MAC_PERMISSIONS : BASE_MAC_PERMISSIONS;
81
82 // Data override seapp_contexts policy file.
83 private static final String DATA_SEAPP_CONTEXTS =
84 Environment.getDataDirectory() + "/security/current/seapp_contexts";
85
86 // Base seapp_contexts policy file.
87 private static final String BASE_SEAPP_CONTEXTS = "/seapp_contexts";
88
89 // Determine which seapp_contexts file to use.
90 private static final String SEAPP_CONTEXTS = USE_OVERRIDE_POLICY ?
91 DATA_SEAPP_CONTEXTS : BASE_SEAPP_CONTEXTS;
Robert Craig43853432014-03-04 11:57:23 -050092
93 // Stores the hash of the last used seapp_contexts file.
94 private static final String SEAPP_HASH_FILE =
95 Environment.getDataDirectory().toString() + "/system/seapp_hash";
96
Robert Craigf8778292014-03-14 11:09:09 -040097
Robert Craig99a626c2013-12-02 10:24:23 -050098 // Signature policy stanzas
99 static class Policy {
100 private String seinfo;
101 private final HashMap<String, String> pkgMap;
102
103 Policy() {
104 seinfo = null;
105 pkgMap = new HashMap<String, String>();
106 }
107
108 void putSeinfo(String seinfoValue) {
109 seinfo = seinfoValue;
110 }
111
112 void putPkg(String pkg, String seinfoValue) {
113 pkgMap.put(pkg, seinfoValue);
114 }
115
116 // Valid policy stanza means there exists a global
117 // seinfo value or at least one package policy.
118 boolean isValid() {
119 return (seinfo != null) || (!pkgMap.isEmpty());
120 }
121
122 String checkPolicy(String pkgName) {
123 // Check for package name seinfo value first.
124 String seinfoValue = pkgMap.get(pkgName);
125 if (seinfoValue != null) {
126 return seinfoValue;
127 }
128
129 // Return the global seinfo value.
130 return seinfo;
131 }
132 }
133
Robert Craigd3f8d032013-03-25 06:33:03 -0400134 private static void flushInstallPolicy() {
135 sSigSeinfo.clear();
Robert Craig99a626c2013-12-02 10:24:23 -0500136 sDefaultSeinfo = null;
Robert Craigd3f8d032013-03-25 06:33:03 -0400137 }
138
Robert Craigd3f8d032013-03-25 06:33:03 -0400139 public static boolean readInstallPolicy() {
Robert Craig99a626c2013-12-02 10:24:23 -0500140 // Temp structures to hold the rules while we parse the xml file.
141 // We add all the rules together once we know there's no structural problems.
142 HashMap<Signature, Policy> sigSeinfo = new HashMap<Signature, Policy>();
143 String defaultSeinfo = null;
Robert Craigd3f8d032013-03-25 06:33:03 -0400144
145 FileReader policyFile = null;
Robert Craigd3f8d032013-03-25 06:33:03 -0400146 try {
Robert Craigf8778292014-03-14 11:09:09 -0400147 policyFile = new FileReader(MAC_PERMISSIONS);
148 Slog.d(TAG, "Using policy file " + MAC_PERMISSIONS);
149
Robert Craigd3f8d032013-03-25 06:33:03 -0400150 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 }
Robert Craigf8778292014-03-14 11:09:09 -0400192 } catch (XmlPullParserException xpe) {
193 Slog.w(TAG, "Got exception parsing " + MAC_PERMISSIONS, xpe);
Robert Craig99a626c2013-12-02 10:24:23 -0500194 return false;
Robert Craigf8778292014-03-14 11:09:09 -0400195 } catch (IOException ioe) {
196 Slog.w(TAG, "Got exception parsing " + MAC_PERMISSIONS, ioe);
Robert Craig99a626c2013-12-02 10:24:23 -0500197 return false;
198 } finally {
Robert Craigf8778292014-03-14 11:09:09 -0400199 IoUtils.closeQuietly(policyFile);
Robert Craigd3f8d032013-03-25 06:33:03 -0400200 }
Robert Craig99a626c2013-12-02 10:24:23 -0500201
202 flushInstallPolicy();
203 sSigSeinfo = sigSeinfo;
204 sDefaultSeinfo = defaultSeinfo;
205
Robert Craigd3f8d032013-03-25 06:33:03 -0400206 return true;
207 }
208
Robert Craig99a626c2013-12-02 10:24:23 -0500209 private static Policy readPolicyTags(XmlPullParser parser) throws
210 IOException, XmlPullParserException {
211
212 int type;
213 int outerDepth = parser.getDepth();
214 Policy policy = new Policy();
215 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
216 && (type != XmlPullParser.END_TAG
217 || parser.getDepth() > outerDepth)) {
218 if (type == XmlPullParser.END_TAG
219 || type == XmlPullParser.TEXT) {
220 continue;
221 }
222
223 String tagName = parser.getName();
224 if ("seinfo".equals(tagName)) {
225 String seinfo = parseSeinfo(parser);
226 if (seinfo != null) {
227 policy.putSeinfo(seinfo);
228 }
229 XmlUtils.skipCurrentTag(parser);
230 } else if ("package".equals(tagName)) {
231 String pkg = parser.getAttributeValue(null, "name");
232 if (!validatePackageName(pkg)) {
233 Slog.w(TAG, "<package> without valid name at "
234 + parser.getPositionDescription());
235 XmlUtils.skipCurrentTag(parser);
236 continue;
237 }
238
239 String seinfo = readSeinfoTag(parser);
240 if (seinfo != null) {
241 policy.putPkg(pkg, seinfo);
242 }
243 } else {
244 XmlUtils.skipCurrentTag(parser);
245 }
246 }
247 return policy;
248 }
249
Robert Craigd3f8d032013-03-25 06:33:03 -0400250 private static String readSeinfoTag(XmlPullParser parser) throws
251 IOException, XmlPullParserException {
252
253 int type;
254 int outerDepth = parser.getDepth();
255 String seinfo = null;
256 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
257 && (type != XmlPullParser.END_TAG
258 || parser.getDepth() > outerDepth)) {
259 if (type == XmlPullParser.END_TAG
260 || type == XmlPullParser.TEXT) {
261 continue;
262 }
263
264 String tagName = parser.getName();
265 if ("seinfo".equals(tagName)) {
Robert Craig99a626c2013-12-02 10:24:23 -0500266 seinfo = parseSeinfo(parser);
Robert Craigd3f8d032013-03-25 06:33:03 -0400267 }
268 XmlUtils.skipCurrentTag(parser);
269 }
270 return seinfo;
271 }
272
Robert Craig99a626c2013-12-02 10:24:23 -0500273 private static String parseSeinfo(XmlPullParser parser) {
274
275 String seinfoValue = parser.getAttributeValue(null, "value");
276 if (!validateValue(seinfoValue)) {
277 Slog.w(TAG, "<seinfo> without valid value at "
278 + parser.getPositionDescription());
279 seinfoValue = null;
280 }
281 return seinfoValue;
282 }
283
284 /**
285 * General validation routine for package names.
286 * Returns a boolean indicating if the passed string
287 * is a valid android package name.
288 */
289 private static boolean validatePackageName(String name) {
290 if (name == null)
291 return false;
292
293 final int N = name.length();
294 boolean hasSep = false;
295 boolean front = true;
296 for (int i=0; i<N; i++) {
297 final char c = name.charAt(i);
298 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
299 front = false;
300 continue;
301 }
302 if (!front) {
303 if ((c >= '0' && c <= '9') || c == '_') {
304 continue;
305 }
306 }
307 if (c == '.') {
308 hasSep = true;
309 front = true;
310 continue;
311 }
312 return false;
313 }
314 return hasSep;
315 }
316
Robert Craigd3f8d032013-03-25 06:33:03 -0400317 /**
Robert Craigd417ab02013-03-28 06:22:12 -0400318 * General validation routine for tag values.
319 * Returns a boolean indicating if the passed string
320 * contains only letters or underscores.
321 */
322 private static boolean validateValue(String name) {
323 if (name == null)
324 return false;
325
326 final int N = name.length();
327 if (N == 0)
328 return false;
329
330 for (int i = 0; i < N; i++) {
331 final char c = name.charAt(i);
332 if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c != '_')) {
333 return false;
334 }
335 }
336 return true;
337 }
338
339 /**
Robert Craigd3f8d032013-03-25 06:33:03 -0400340 * Labels a package based on an seinfo tag from install policy.
341 * The label is attached to the ApplicationInfo instance of the package.
Dianne Hackborn0aa51632014-03-19 17:49:09 -0700342 * @param pkg object representing the package to be labeled.
Robert Craig99a626c2013-12-02 10:24:23 -0500343 * @return boolean which determines whether a non null seinfo label
344 * was assigned to the package. A null value simply meaning that
345 * no policy matched.
Robert Craigd3f8d032013-03-25 06:33:03 -0400346 */
Robert Craig99a626c2013-12-02 10:24:23 -0500347 public static boolean assignSeinfoValue(PackageParser.Package pkg) {
Robert Craigd3f8d032013-03-25 06:33:03 -0400348
Robert Craig83b54ec2014-07-01 13:53:11 -0700349 // We just want one of the signatures to match.
350 for (Signature s : pkg.mSignatures) {
351 if (s == null)
352 continue;
Robert Craigd3f8d032013-03-25 06:33:03 -0400353
Robert Craig83b54ec2014-07-01 13:53:11 -0700354 Policy policy = sSigSeinfo.get(s);
355 if (policy != null) {
356 String seinfo = policy.checkPolicy(pkg.packageName);
357 if (seinfo != null) {
358 pkg.applicationInfo.seinfo = seinfo;
359 if (DEBUG_POLICY_INSTALL)
360 Slog.i(TAG, "package (" + pkg.packageName +
361 ") labeled with seinfo=" + seinfo);
Robert Craigd3f8d032013-03-25 06:33:03 -0400362
Robert Craig83b54ec2014-07-01 13:53:11 -0700363 return true;
Robert Craigd3f8d032013-03-25 06:33:03 -0400364 }
365 }
Robert Craigd3f8d032013-03-25 06:33:03 -0400366 }
367
368 // If we have a default seinfo value then great, otherwise
369 // we set a null object and that is what we started with.
Robert Craig99a626c2013-12-02 10:24:23 -0500370 pkg.applicationInfo.seinfo = sDefaultSeinfo;
Robert Craigd3f8d032013-03-25 06:33:03 -0400371 if (DEBUG_POLICY_INSTALL)
Robert Craig99a626c2013-12-02 10:24:23 -0500372 Slog.i(TAG, "package (" + pkg.packageName + ") labeled with seinfo="
373 + (sDefaultSeinfo == null ? "null" : sDefaultSeinfo));
374
375 return (sDefaultSeinfo != null);
Robert Craigd3f8d032013-03-25 06:33:03 -0400376 }
Robert Craig43853432014-03-04 11:57:23 -0500377
378 /**
379 * Determines if a recursive restorecon on /data/data and /data/user is needed.
380 * It does this by comparing the SHA-1 of the seapp_contexts file against the
381 * stored hash at /data/system/seapp_hash.
382 *
383 * @return Returns true if the restorecon should occur or false otherwise.
384 */
385 public static boolean shouldRestorecon() {
386 // Any error with the seapp_contexts file should be fatal
387 byte[] currentHash = null;
388 try {
Robert Craigf8778292014-03-14 11:09:09 -0400389 currentHash = returnHash(SEAPP_CONTEXTS);
Robert Craig43853432014-03-04 11:57:23 -0500390 } catch (IOException ioe) {
391 Slog.e(TAG, "Error with hashing seapp_contexts.", ioe);
392 return false;
393 }
394
395 // Push past any error with the stored hash file
396 byte[] storedHash = null;
397 try {
398 storedHash = IoUtils.readFileAsByteArray(SEAPP_HASH_FILE);
399 } catch (IOException ioe) {
Dianne Hackborn0aa51632014-03-19 17:49:09 -0700400 Slog.w(TAG, "Error opening " + SEAPP_HASH_FILE + ". Assuming first boot.");
Robert Craig43853432014-03-04 11:57:23 -0500401 }
402
403 return (storedHash == null || !MessageDigest.isEqual(storedHash, currentHash));
404 }
405
406 /**
407 * Stores the SHA-1 of the seapp_contexts to /data/system/seapp_hash.
408 */
409 public static void setRestoreconDone() {
410 try {
Robert Craigf8778292014-03-14 11:09:09 -0400411 final byte[] currentHash = returnHash(SEAPP_CONTEXTS);
Robert Craig43853432014-03-04 11:57:23 -0500412 dumpHash(new File(SEAPP_HASH_FILE), currentHash);
413 } catch (IOException ioe) {
414 Slog.e(TAG, "Error with saving hash to " + SEAPP_HASH_FILE, ioe);
415 }
416 }
417
418 /**
419 * Dump the contents of a byte array to a specified file.
420 *
421 * @param file The file that receives the byte array content.
422 * @param content A byte array that will be written to the specified file.
423 * @throws IOException if any failed I/O operation occured.
424 * Included is the failure to atomically rename the tmp
425 * file used in the process.
426 */
427 private static void dumpHash(File file, byte[] content) throws IOException {
428 FileOutputStream fos = null;
429 File tmp = null;
430 try {
431 tmp = File.createTempFile("seapp_hash", ".journal", file.getParentFile());
432 tmp.setReadable(true);
433 fos = new FileOutputStream(tmp);
434 fos.write(content);
435 fos.getFD().sync();
436 if (!tmp.renameTo(file)) {
437 throw new IOException("Failure renaming " + file.getCanonicalPath());
438 }
439 } finally {
440 if (tmp != null) {
441 tmp.delete();
442 }
443 IoUtils.closeQuietly(fos);
444 }
445 }
446
447 /**
448 * Return the SHA-1 of a file.
449 *
450 * @param file The path to the file given as a string.
451 * @return Returns the SHA-1 of the file as a byte array.
452 * @throws IOException if any failed I/O operations occured.
453 */
454 private static byte[] returnHash(String file) throws IOException {
455 try {
456 final byte[] contents = IoUtils.readFileAsByteArray(file);
457 return MessageDigest.getInstance("SHA-1").digest(contents);
458 } catch (NoSuchAlgorithmException nsae) {
459 throw new RuntimeException(nsae); // impossible
460 }
461 }
Robert Craigf8778292014-03-14 11:09:09 -0400462
463 private static boolean useOverridePolicy() {
464 try {
465 final String overrideVersion = IoUtils.readFileAsString(DATA_VERSION_FILE);
466 final String baseVersion = IoUtils.readFileAsString(BASE_VERSION_FILE);
467 if (overrideVersion.equals(baseVersion)) {
468 return true;
469 }
470 Slog.e(TAG, "Override policy version '" + overrideVersion + "' doesn't match " +
471 "base version '" + baseVersion + "'. Skipping override policy files.");
472 } catch (FileNotFoundException fnfe) {
473 // Override version file doesn't have to exist so silently ignore.
474 } catch (IOException ioe) {
475 Slog.w(TAG, "Skipping override policy files.", ioe);
476 }
477 return false;
478 }
Robert Craigd3f8d032013-03-25 06:33:03 -0400479}