SELinuxMMAC.java revision 9158825f9c41869689d6b1786d7c7aa8bdd524ce
1/* 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. 51 private static HashMap<Signature, Policy> sSigSeinfo = 52 new HashMap<Signature, Policy>(); 53 54 // Default seinfo read from policy. 55 private static String sDefaultSeinfo = null; 56 57 // Locations of potential install policy files. 58 private static final File[] INSTALL_POLICY_FILE = { 59 new File(Environment.getDataDirectory(), "security/mac_permissions.xml"), 60 new File(Environment.getRootDirectory(), "etc/security/mac_permissions.xml"), 61 null}; 62 63 // 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 99 private static void flushInstallPolicy() { 100 sSigSeinfo.clear(); 101 sDefaultSeinfo = null; 102 } 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) { 125 // 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; 129 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 149 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 } 178 Policy policy = readPolicyTags(parser); 179 if (policy.isValid()) { 180 sigSeinfo.put(signature, policy); 181 } 182 } else if ("default".equals(tagName)) { 183 // 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); 187 188 } else { 189 XmlUtils.skipCurrentTag(parser); 190 } 191 } 192 } catch (XmlPullParserException e) { 193 // 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; 197 } catch (IOException e) { 198 Slog.w(TAG, "Got exception parsing ", e); 199 return false; 200 } finally { 201 try { 202 policyFile.close(); 203 } catch (IOException e) { 204 //omit 205 } 206 } 207 208 flushInstallPolicy(); 209 sSigSeinfo = sigSeinfo; 210 sDefaultSeinfo = defaultSeinfo; 211 212 return true; 213 } 214 215 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 256 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)) { 272 seinfo = parseSeinfo(parser); 273 } 274 XmlUtils.skipCurrentTag(parser); 275 } 276 return seinfo; 277 } 278 279 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 323 /** 324 * 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 /** 346 * 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. 350 * @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. 353 */ 354 public static boolean assignSeinfoValue(PackageParser.Package pkg) { 355 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 370 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); 378 379 return true; 380 } 381 } 382 } 383 } 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. 387 pkg.applicationInfo.seinfo = sDefaultSeinfo; 388 if (DEBUG_POLICY_INSTALL) 389 Slog.i(TAG, "package (" + pkg.packageName + ") labeled with seinfo=" 390 + (sDefaultSeinfo == null ? "null" : sDefaultSeinfo)); 391 392 return (sDefaultSeinfo != null); 393 } 394} 395