Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 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 | |
| 17 | package com.android.server.pm; |
| 18 | |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 19 | import android.content.pm.PackageParser; |
| 20 | import android.content.pm.Signature; |
| 21 | import android.os.Environment; |
| 22 | import android.util.Slog; |
| 23 | import android.util.Xml; |
| 24 | |
Robert Craig | 4385343 | 2014-03-04 11:57:23 -0500 | [diff] [blame] | 25 | import libcore.io.IoUtils; |
| 26 | |
Jeff Sharkey | 0e62384c | 2016-01-13 18:52:55 -0700 | [diff] [blame] | 27 | import org.xmlpull.v1.XmlPullParser; |
| 28 | import org.xmlpull.v1.XmlPullParserException; |
| 29 | |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 30 | import java.io.File; |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 31 | import java.io.FileReader; |
| 32 | import java.io.IOException; |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 33 | import java.util.ArrayList; |
| 34 | import java.util.Collections; |
Robert Craig | 4caa6b1 | 2015-04-10 11:02:33 -0400 | [diff] [blame] | 35 | import java.util.Comparator; |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 36 | import java.util.HashMap; |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 37 | import java.util.HashSet; |
| 38 | import java.util.List; |
| 39 | import java.util.Map; |
| 40 | import java.util.Set; |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 41 | |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 42 | /** |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 43 | * Centralized access to SELinux MMAC (middleware MAC) implementation. This |
| 44 | * class is responsible for loading the appropriate mac_permissions.xml file |
| 45 | * as well as providing an interface for assigning seinfo values to apks. |
| 46 | * |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 47 | * {@hide} |
| 48 | */ |
| 49 | public final class SELinuxMMAC { |
| 50 | |
Robert Craig | 4caa6b1 | 2015-04-10 11:02:33 -0400 | [diff] [blame] | 51 | static final String TAG = "SELinuxMMAC"; |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 52 | |
| 53 | private static final boolean DEBUG_POLICY = false; |
| 54 | private static final boolean DEBUG_POLICY_INSTALL = DEBUG_POLICY || false; |
Robert Craig | 4caa6b1 | 2015-04-10 11:02:33 -0400 | [diff] [blame] | 55 | private static final boolean DEBUG_POLICY_ORDER = DEBUG_POLICY || false; |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 56 | |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 57 | // All policy stanzas read from mac_permissions.xml. This is also the lock |
| 58 | // to synchronize access during policy load and access attempts. |
Robert Craig | 4caa6b1 | 2015-04-10 11:02:33 -0400 | [diff] [blame] | 59 | private static List<Policy> sPolicies = new ArrayList<>(); |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 60 | |
Jeff Sharkey | 0e62384c | 2016-01-13 18:52:55 -0700 | [diff] [blame] | 61 | /** Path to MAC permissions on system image */ |
dcashman | b1cc4f8 | 2016-12-14 13:46:05 -0800 | [diff] [blame] | 62 | private static final File[] MAC_PERMISSIONS = |
| 63 | { new File(Environment.getRootDirectory(), "/etc/security/plat_mac_permissions.xml"), |
| 64 | new File(Environment.getRootDirectory(), "/etc/security/nonplat_mac_permissions.xml") }; |
Robert Craig | f877829 | 2014-03-14 11:09:09 -0400 | [diff] [blame] | 65 | |
Jeff Vander Stoep | 0985937 | 2015-10-12 08:28:56 -0700 | [diff] [blame] | 66 | // Append privapp to existing seinfo label |
| 67 | private static final String PRIVILEGED_APP_STR = ":privapp"; |
| 68 | |
Jeff Vander Stoep | a4407bf | 2015-10-30 13:15:58 -0700 | [diff] [blame] | 69 | // Append autoplay to existing seinfo label |
| 70 | private static final String AUTOPLAY_APP_STR = ":autoplayapp"; |
| 71 | |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 72 | /** |
| 73 | * Load the mac_permissions.xml file containing all seinfo assignments used to |
| 74 | * label apps. The loaded mac_permissions.xml file is determined by the |
| 75 | * MAC_PERMISSIONS class variable which is set at class load time which itself |
| 76 | * is based on the USE_OVERRIDE_POLICY class variable. For further guidance on |
| 77 | * the proper structure of a mac_permissions.xml file consult the source code |
Nick Kralevich | e91dba0 | 2016-04-13 07:49:28 -0700 | [diff] [blame] | 78 | * located at system/sepolicy/mac_permissions.xml. |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 79 | * |
| 80 | * @return boolean indicating if policy was correctly loaded. A value of false |
| 81 | * typically indicates a structural problem with the xml or incorrectly |
| 82 | * constructed policy stanzas. A value of true means that all stanzas |
| 83 | * were loaded successfully; no partial loading is possible. |
| 84 | */ |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 85 | public static boolean readInstallPolicy() { |
Robert Craig | 4caa6b1 | 2015-04-10 11:02:33 -0400 | [diff] [blame] | 86 | // Temp structure to hold the rules while we parse the xml file |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 87 | List<Policy> policies = new ArrayList<>(); |
Robert Craig | 9989115 | 2014-09-02 07:16:52 -0400 | [diff] [blame] | 88 | |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 89 | FileReader policyFile = null; |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 90 | XmlPullParser parser = Xml.newPullParser(); |
dcashman | b1cc4f8 | 2016-12-14 13:46:05 -0800 | [diff] [blame] | 91 | for (int i = 0; i < MAC_PERMISSIONS.length; i++) { |
| 92 | try { |
| 93 | policyFile = new FileReader(MAC_PERMISSIONS[i]); |
| 94 | Slog.d(TAG, "Using policy file " + MAC_PERMISSIONS[i]); |
Robert Craig | f877829 | 2014-03-14 11:09:09 -0400 | [diff] [blame] | 95 | |
dcashman | b1cc4f8 | 2016-12-14 13:46:05 -0800 | [diff] [blame] | 96 | parser.setInput(policyFile); |
| 97 | parser.nextTag(); |
| 98 | parser.require(XmlPullParser.START_TAG, null, "policy"); |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 99 | |
dcashman | b1cc4f8 | 2016-12-14 13:46:05 -0800 | [diff] [blame] | 100 | while (parser.next() != XmlPullParser.END_TAG) { |
| 101 | if (parser.getEventType() != XmlPullParser.START_TAG) { |
| 102 | continue; |
| 103 | } |
| 104 | |
| 105 | switch (parser.getName()) { |
| 106 | case "signer": |
| 107 | policies.add(readSignerOrThrow(parser)); |
| 108 | break; |
| 109 | default: |
| 110 | skip(parser); |
| 111 | } |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 112 | } |
dcashman | b1cc4f8 | 2016-12-14 13:46:05 -0800 | [diff] [blame] | 113 | } catch (IllegalStateException | IllegalArgumentException | |
| 114 | XmlPullParserException ex) { |
| 115 | StringBuilder sb = new StringBuilder("Exception @"); |
| 116 | sb.append(parser.getPositionDescription()); |
| 117 | sb.append(" while parsing "); |
| 118 | sb.append(MAC_PERMISSIONS[i]); |
| 119 | sb.append(":"); |
| 120 | sb.append(ex); |
| 121 | Slog.w(TAG, sb.toString()); |
| 122 | return false; |
| 123 | } catch (IOException ioe) { |
| 124 | Slog.w(TAG, "Exception parsing " + MAC_PERMISSIONS[i], ioe); |
| 125 | return false; |
| 126 | } finally { |
| 127 | IoUtils.closeQuietly(policyFile); |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 128 | } |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 129 | } |
Robert Craig | 99a626c | 2013-12-02 10:24:23 -0500 | [diff] [blame] | 130 | |
Robert Craig | 4caa6b1 | 2015-04-10 11:02:33 -0400 | [diff] [blame] | 131 | // Now sort the policy stanzas |
| 132 | PolicyComparator policySort = new PolicyComparator(); |
| 133 | Collections.sort(policies, policySort); |
| 134 | if (policySort.foundDuplicate()) { |
dcashman | b1cc4f8 | 2016-12-14 13:46:05 -0800 | [diff] [blame] | 135 | Slog.w(TAG, "ERROR! Duplicate entries found parsing mac_permissions.xml files"); |
Robert Craig | 4caa6b1 | 2015-04-10 11:02:33 -0400 | [diff] [blame] | 136 | return false; |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 137 | } |
| 138 | |
| 139 | synchronized (sPolicies) { |
Robert Craig | 4caa6b1 | 2015-04-10 11:02:33 -0400 | [diff] [blame] | 140 | sPolicies = policies; |
| 141 | |
| 142 | if (DEBUG_POLICY_ORDER) { |
| 143 | for (Policy policy : sPolicies) { |
| 144 | Slog.d(TAG, "Policy: " + policy.toString()); |
| 145 | } |
| 146 | } |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 147 | } |
Robert Craig | 99a626c | 2013-12-02 10:24:23 -0500 | [diff] [blame] | 148 | |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 149 | return true; |
| 150 | } |
| 151 | |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 152 | /** |
| 153 | * Loop over a signer tag looking for seinfo, package and cert tags. A {@link Policy} |
| 154 | * instance will be created and returned in the process. During the pass all other |
| 155 | * tag elements will be skipped. |
| 156 | * |
| 157 | * @param parser an XmlPullParser object representing a signer element. |
| 158 | * @return the constructed {@link Policy} instance |
| 159 | * @throws IOException |
| 160 | * @throws XmlPullParserException |
| 161 | * @throws IllegalArgumentException if any of the validation checks fail while |
| 162 | * parsing tag values. |
| 163 | * @throws IllegalStateException if any of the invariants fail when constructing |
| 164 | * the {@link Policy} instance. |
| 165 | */ |
| 166 | private static Policy readSignerOrThrow(XmlPullParser parser) throws IOException, |
| 167 | XmlPullParserException { |
Robert Craig | 99a626c | 2013-12-02 10:24:23 -0500 | [diff] [blame] | 168 | |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 169 | parser.require(XmlPullParser.START_TAG, null, "signer"); |
| 170 | Policy.PolicyBuilder pb = new Policy.PolicyBuilder(); |
| 171 | |
| 172 | // Check for a cert attached to the signer tag. We allow a signature |
| 173 | // to appear as an attribute as well as those attached to cert tags. |
| 174 | String cert = parser.getAttributeValue(null, "signature"); |
| 175 | if (cert != null) { |
| 176 | pb.addSignature(cert); |
| 177 | } |
| 178 | |
| 179 | while (parser.next() != XmlPullParser.END_TAG) { |
| 180 | if (parser.getEventType() != XmlPullParser.START_TAG) { |
Robert Craig | 99a626c | 2013-12-02 10:24:23 -0500 | [diff] [blame] | 181 | continue; |
| 182 | } |
| 183 | |
| 184 | String tagName = parser.getName(); |
| 185 | if ("seinfo".equals(tagName)) { |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 186 | String seinfo = parser.getAttributeValue(null, "value"); |
| 187 | pb.setGlobalSeinfoOrThrow(seinfo); |
| 188 | readSeinfo(parser); |
Robert Craig | 99a626c | 2013-12-02 10:24:23 -0500 | [diff] [blame] | 189 | } else if ("package".equals(tagName)) { |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 190 | readPackageOrThrow(parser, pb); |
| 191 | } else if ("cert".equals(tagName)) { |
| 192 | String sig = parser.getAttributeValue(null, "signature"); |
| 193 | pb.addSignature(sig); |
| 194 | readCert(parser); |
Robert Craig | 99a626c | 2013-12-02 10:24:23 -0500 | [diff] [blame] | 195 | } else { |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 196 | skip(parser); |
Robert Craig | 99a626c | 2013-12-02 10:24:23 -0500 | [diff] [blame] | 197 | } |
| 198 | } |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 199 | |
| 200 | return pb.build(); |
Robert Craig | 99a626c | 2013-12-02 10:24:23 -0500 | [diff] [blame] | 201 | } |
| 202 | |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 203 | /** |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 204 | * Loop over a package element looking for seinfo child tags. If found return the |
| 205 | * value attribute of the seinfo tag, otherwise return null. All other tags encountered |
| 206 | * will be skipped. |
| 207 | * |
| 208 | * @param parser an XmlPullParser object representing a package element. |
| 209 | * @param pb a Policy.PolicyBuilder instance to build |
| 210 | * @throws IOException |
| 211 | * @throws XmlPullParserException |
| 212 | * @throws IllegalArgumentException if any of the validation checks fail while |
| 213 | * parsing tag values. |
| 214 | * @throws IllegalStateException if there is a duplicate seinfo tag for the current |
| 215 | * package tag. |
Robert Craig | 99a626c | 2013-12-02 10:24:23 -0500 | [diff] [blame] | 216 | */ |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 217 | private static void readPackageOrThrow(XmlPullParser parser, Policy.PolicyBuilder pb) throws |
| 218 | IOException, XmlPullParserException { |
| 219 | parser.require(XmlPullParser.START_TAG, null, "package"); |
| 220 | String pkgName = parser.getAttributeValue(null, "name"); |
Robert Craig | 99a626c | 2013-12-02 10:24:23 -0500 | [diff] [blame] | 221 | |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 222 | while (parser.next() != XmlPullParser.END_TAG) { |
| 223 | if (parser.getEventType() != XmlPullParser.START_TAG) { |
Robert Craig | 99a626c | 2013-12-02 10:24:23 -0500 | [diff] [blame] | 224 | continue; |
| 225 | } |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 226 | |
| 227 | String tagName = parser.getName(); |
| 228 | if ("seinfo".equals(tagName)) { |
| 229 | String seinfo = parser.getAttributeValue(null, "value"); |
| 230 | pb.addInnerPackageMapOrThrow(pkgName, seinfo); |
| 231 | readSeinfo(parser); |
| 232 | } else { |
| 233 | skip(parser); |
Robert Craig | 99a626c | 2013-12-02 10:24:23 -0500 | [diff] [blame] | 234 | } |
Robert Craig | 99a626c | 2013-12-02 10:24:23 -0500 | [diff] [blame] | 235 | } |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 236 | } |
| 237 | |
| 238 | private static void readCert(XmlPullParser parser) throws IOException, |
| 239 | XmlPullParserException { |
| 240 | parser.require(XmlPullParser.START_TAG, null, "cert"); |
| 241 | parser.nextTag(); |
| 242 | } |
| 243 | |
| 244 | private static void readSeinfo(XmlPullParser parser) throws IOException, |
| 245 | XmlPullParserException { |
| 246 | parser.require(XmlPullParser.START_TAG, null, "seinfo"); |
| 247 | parser.nextTag(); |
| 248 | } |
| 249 | |
| 250 | private static void skip(XmlPullParser p) throws IOException, XmlPullParserException { |
| 251 | if (p.getEventType() != XmlPullParser.START_TAG) { |
| 252 | throw new IllegalStateException(); |
| 253 | } |
| 254 | int depth = 1; |
| 255 | while (depth != 0) { |
| 256 | switch (p.next()) { |
| 257 | case XmlPullParser.END_TAG: |
| 258 | depth--; |
| 259 | break; |
| 260 | case XmlPullParser.START_TAG: |
| 261 | depth++; |
| 262 | break; |
| 263 | } |
| 264 | } |
Robert Craig | 99a626c | 2013-12-02 10:24:23 -0500 | [diff] [blame] | 265 | } |
| 266 | |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 267 | /** |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 268 | * Applies a security label to a package based on an seinfo tag taken from a matched |
Robert Craig | 5e16bc5 | 2015-08-28 12:11:41 -0400 | [diff] [blame] | 269 | * policy. All signature based policy stanzas are consulted and, if no match is |
| 270 | * found, the default seinfo label of 'default' (set in ApplicationInfo object) is |
| 271 | * used. The security label is attached to the ApplicationInfo instance of the package |
| 272 | * in the event that a matching policy was found. |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 273 | * |
Dianne Hackborn | 0aa5163 | 2014-03-19 17:49:09 -0700 | [diff] [blame] | 274 | * @param pkg object representing the package to be labeled. |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 275 | */ |
Robert Craig | 5e16bc5 | 2015-08-28 12:11:41 -0400 | [diff] [blame] | 276 | public static void assignSeinfoValue(PackageParser.Package pkg) { |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 277 | synchronized (sPolicies) { |
| 278 | for (Policy policy : sPolicies) { |
| 279 | String seinfo = policy.getMatchedSeinfo(pkg); |
Robert Craig | 83b54ec | 2014-07-01 13:53:11 -0700 | [diff] [blame] | 280 | if (seinfo != null) { |
| 281 | pkg.applicationInfo.seinfo = seinfo; |
Robert Craig | 5e16bc5 | 2015-08-28 12:11:41 -0400 | [diff] [blame] | 282 | break; |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 283 | } |
| 284 | } |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 285 | } |
| 286 | |
Jeff Vander Stoep | a4407bf | 2015-10-30 13:15:58 -0700 | [diff] [blame] | 287 | if (pkg.applicationInfo.isAutoPlayApp()) |
| 288 | pkg.applicationInfo.seinfo += AUTOPLAY_APP_STR; |
| 289 | |
Jeff Vander Stoep | 0985937 | 2015-10-12 08:28:56 -0700 | [diff] [blame] | 290 | if (pkg.applicationInfo.isPrivilegedApp()) |
| 291 | pkg.applicationInfo.seinfo += PRIVILEGED_APP_STR; |
| 292 | |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 293 | if (DEBUG_POLICY_INSTALL) { |
Robert Craig | 5e16bc5 | 2015-08-28 12:11:41 -0400 | [diff] [blame] | 294 | Slog.i(TAG, "package (" + pkg.packageName + ") labeled with " + |
| 295 | "seinfo=" + pkg.applicationInfo.seinfo); |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 296 | } |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 297 | } |
Robert Craig | d3f8d03 | 2013-03-25 06:33:03 -0400 | [diff] [blame] | 298 | } |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 299 | |
| 300 | /** |
| 301 | * Holds valid policy representations of individual stanzas from a mac_permissions.xml |
| 302 | * file. Each instance can further be used to assign seinfo values to apks using the |
| 303 | * {@link Policy#getMatchedSeinfo} method. To create an instance of this use the |
| 304 | * {@link PolicyBuilder} pattern class, where each instance is validated against a set |
| 305 | * of invariants before being built and returned. Each instance can be guaranteed to |
Nick Kralevich | e91dba0 | 2016-04-13 07:49:28 -0700 | [diff] [blame] | 306 | * hold one valid policy stanza as outlined in the system/sepolicy/mac_permissions.xml |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 307 | * file. |
Robert Craig | 4caa6b1 | 2015-04-10 11:02:33 -0400 | [diff] [blame] | 308 | * <p> |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 309 | * The following is an example of how to use {@link Policy.PolicyBuilder} to create a |
Robert Craig | 4caa6b1 | 2015-04-10 11:02:33 -0400 | [diff] [blame] | 310 | * signer based Policy instance with only inner package name refinements. |
| 311 | * </p> |
| 312 | * <pre> |
| 313 | * {@code |
| 314 | * Policy policy = new Policy.PolicyBuilder() |
| 315 | * .addSignature("308204a8...") |
| 316 | * .addSignature("483538c8...") |
| 317 | * .addInnerPackageMapOrThrow("com.foo.", "bar") |
| 318 | * .addInnerPackageMapOrThrow("com.foo.other", "bar") |
| 319 | * .build(); |
| 320 | * } |
| 321 | * </pre> |
| 322 | * <p> |
| 323 | * The following is an example of how to use {@link Policy.PolicyBuilder} to create a |
| 324 | * signer based Policy instance with only a global seinfo tag. |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 325 | * </p> |
| 326 | * <pre> |
| 327 | * {@code |
| 328 | * Policy policy = new Policy.PolicyBuilder() |
| 329 | * .addSignature("308204a8...") |
| 330 | * .addSignature("483538c8...") |
| 331 | * .setGlobalSeinfoOrThrow("paltform") |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 332 | * .build(); |
| 333 | * } |
| 334 | * </pre> |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 335 | */ |
| 336 | final class Policy { |
| 337 | |
| 338 | private final String mSeinfo; |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 339 | private final Set<Signature> mCerts; |
| 340 | private final Map<String, String> mPkgMap; |
| 341 | |
| 342 | // Use the PolicyBuilder pattern to instantiate |
| 343 | private Policy(PolicyBuilder builder) { |
| 344 | mSeinfo = builder.mSeinfo; |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 345 | mCerts = Collections.unmodifiableSet(builder.mCerts); |
| 346 | mPkgMap = Collections.unmodifiableMap(builder.mPkgMap); |
| 347 | } |
| 348 | |
| 349 | /** |
| 350 | * Return all the certs stored with this policy stanza. |
| 351 | * |
| 352 | * @return A set of Signature objects representing all the certs stored |
| 353 | * with the policy. |
| 354 | */ |
| 355 | public Set<Signature> getSignatures() { |
| 356 | return mCerts; |
| 357 | } |
| 358 | |
| 359 | /** |
Robert Craig | 4caa6b1 | 2015-04-10 11:02:33 -0400 | [diff] [blame] | 360 | * Return whether this policy object contains package name mapping refinements. |
| 361 | * |
| 362 | * @return A boolean indicating if this object has inner package name mappings. |
| 363 | */ |
| 364 | public boolean hasInnerPackages() { |
| 365 | return !mPkgMap.isEmpty(); |
| 366 | } |
| 367 | |
| 368 | /** |
| 369 | * Return the mapping of all package name refinements. |
| 370 | * |
| 371 | * @return A Map object whose keys are the package names and whose values are |
| 372 | * the seinfo assignments. |
| 373 | */ |
| 374 | public Map<String, String> getInnerPackages() { |
| 375 | return mPkgMap; |
| 376 | } |
| 377 | |
| 378 | /** |
| 379 | * Return whether the policy object has a global seinfo tag attached. |
| 380 | * |
| 381 | * @return A boolean indicating if this stanza has a global seinfo tag. |
| 382 | */ |
| 383 | public boolean hasGlobalSeinfo() { |
| 384 | return mSeinfo != null; |
| 385 | } |
| 386 | |
| 387 | @Override |
| 388 | public String toString() { |
| 389 | StringBuilder sb = new StringBuilder(); |
Robert Craig | 4caa6b1 | 2015-04-10 11:02:33 -0400 | [diff] [blame] | 390 | for (Signature cert : mCerts) { |
| 391 | sb.append("cert=" + cert.toCharsString().substring(0, 11) + "... "); |
| 392 | } |
| 393 | |
| 394 | if (mSeinfo != null) { |
| 395 | sb.append("seinfo=" + mSeinfo); |
| 396 | } |
| 397 | |
| 398 | for (String name : mPkgMap.keySet()) { |
| 399 | sb.append(" " + name + "=" + mPkgMap.get(name)); |
| 400 | } |
| 401 | |
| 402 | return sb.toString(); |
| 403 | } |
| 404 | |
| 405 | /** |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 406 | * <p> |
| 407 | * Determine the seinfo value to assign to an apk. The appropriate seinfo value |
| 408 | * is determined using the following steps: |
| 409 | * </p> |
| 410 | * <ul> |
Robert Craig | 5e16bc5 | 2015-08-28 12:11:41 -0400 | [diff] [blame] | 411 | * <li> All certs used to sign the apk and all certs stored with this policy |
| 412 | * instance are tested for set equality. If this fails then null is returned. |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 413 | * </li> |
Robert Craig | 5e16bc5 | 2015-08-28 12:11:41 -0400 | [diff] [blame] | 414 | * <li> If all certs match then an appropriate inner package stanza is |
| 415 | * searched based on package name alone. If matched, the stored seinfo |
| 416 | * value for that mapping is returned. |
| 417 | * </li> |
| 418 | * <li> If all certs matched and no inner package stanza matches then return |
| 419 | * the global seinfo value. The returned value can be null in this case. |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 420 | * </li> |
| 421 | * </ul> |
| 422 | * <p> |
| 423 | * In all cases, a return value of null should be interpreted as the apk failing |
| 424 | * to match this Policy instance; i.e. failing this policy stanza. |
| 425 | * </p> |
| 426 | * @param pkg the apk to check given as a PackageParser.Package object |
| 427 | * @return A string representing the seinfo matched during policy lookup. |
| 428 | * A value of null can also be returned if no match occured. |
| 429 | */ |
| 430 | public String getMatchedSeinfo(PackageParser.Package pkg) { |
Robert Craig | 5e16bc5 | 2015-08-28 12:11:41 -0400 | [diff] [blame] | 431 | // Check for exact signature matches across all certs. |
| 432 | Signature[] certs = mCerts.toArray(new Signature[0]); |
| 433 | if (!Signature.areExactMatch(certs, pkg.mSignatures)) { |
| 434 | return null; |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 435 | } |
| 436 | |
Robert Craig | 5e16bc5 | 2015-08-28 12:11:41 -0400 | [diff] [blame] | 437 | // Check for inner package name matches given that the |
| 438 | // signature checks already passed. |
| 439 | String seinfoValue = mPkgMap.get(pkg.packageName); |
| 440 | if (seinfoValue != null) { |
| 441 | return seinfoValue; |
| 442 | } |
| 443 | |
| 444 | // Return the global seinfo value. |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 445 | return mSeinfo; |
| 446 | } |
| 447 | |
| 448 | /** |
| 449 | * A nested builder class to create {@link Policy} instances. A {@link Policy} |
| 450 | * class instance represents one valid policy stanza found in a mac_permissions.xml |
Robert Craig | 5e16bc5 | 2015-08-28 12:11:41 -0400 | [diff] [blame] | 451 | * file. A valid policy stanza is defined to be a signer stanza which obeys the rules |
Nick Kralevich | e91dba0 | 2016-04-13 07:49:28 -0700 | [diff] [blame] | 452 | * outlined in system/sepolicy/mac_permissions.xml. The {@link #build} method |
Robert Craig | 5e16bc5 | 2015-08-28 12:11:41 -0400 | [diff] [blame] | 453 | * ensures a set of invariants are upheld enforcing the correct stanza structure |
| 454 | * before returning a valid Policy object. |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 455 | */ |
| 456 | public static final class PolicyBuilder { |
| 457 | |
| 458 | private String mSeinfo; |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 459 | private final Set<Signature> mCerts; |
| 460 | private final Map<String, String> mPkgMap; |
| 461 | |
| 462 | public PolicyBuilder() { |
| 463 | mCerts = new HashSet<Signature>(2); |
| 464 | mPkgMap = new HashMap<String, String>(2); |
| 465 | } |
| 466 | |
| 467 | /** |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 468 | * Adds a signature to the set of certs used for validation checks. The purpose |
| 469 | * being that all contained certs will need to be matched against all certs |
| 470 | * contained with an apk. |
| 471 | * |
| 472 | * @param cert the signature to add given as a String. |
| 473 | * @return The reference to this PolicyBuilder. |
| 474 | * @throws IllegalArgumentException if the cert value fails validation; |
| 475 | * null or is an invalid hex-encoded ASCII string. |
| 476 | */ |
| 477 | public PolicyBuilder addSignature(String cert) { |
| 478 | if (cert == null) { |
| 479 | String err = "Invalid signature value " + cert; |
| 480 | throw new IllegalArgumentException(err); |
| 481 | } |
| 482 | |
| 483 | mCerts.add(new Signature(cert)); |
| 484 | return this; |
| 485 | } |
| 486 | |
| 487 | /** |
| 488 | * Set the global seinfo tag for this policy stanza. The global seinfo tag |
Robert Craig | 5e16bc5 | 2015-08-28 12:11:41 -0400 | [diff] [blame] | 489 | * when attached to a signer tag represents the assignment when there isn't a |
| 490 | * further inner package refinement in policy. |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 491 | * |
| 492 | * @param seinfo the seinfo value given as a String. |
| 493 | * @return The reference to this PolicyBuilder. |
| 494 | * @throws IllegalArgumentException if the seinfo value fails validation; |
| 495 | * null, zero length or contains non-valid characters [^a-zA-Z_\._0-9]. |
| 496 | * @throws IllegalStateException if an seinfo value has already been found |
| 497 | */ |
| 498 | public PolicyBuilder setGlobalSeinfoOrThrow(String seinfo) { |
| 499 | if (!validateValue(seinfo)) { |
| 500 | String err = "Invalid seinfo value " + seinfo; |
| 501 | throw new IllegalArgumentException(err); |
| 502 | } |
| 503 | |
| 504 | if (mSeinfo != null && !mSeinfo.equals(seinfo)) { |
| 505 | String err = "Duplicate seinfo tag found"; |
| 506 | throw new IllegalStateException(err); |
| 507 | } |
| 508 | |
| 509 | mSeinfo = seinfo; |
| 510 | return this; |
| 511 | } |
| 512 | |
| 513 | /** |
| 514 | * Create a package name to seinfo value mapping. Each mapping represents |
| 515 | * the seinfo value that will be assigned to the described package name. |
Robert Craig | 5e16bc5 | 2015-08-28 12:11:41 -0400 | [diff] [blame] | 516 | * These localized mappings allow the global seinfo to be overriden. |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 517 | * |
| 518 | * @param pkgName the android package name given to the app |
| 519 | * @param seinfo the seinfo value that will be assigned to the passed pkgName |
| 520 | * @return The reference to this PolicyBuilder. |
| 521 | * @throws IllegalArgumentException if the seinfo value fails validation; |
| 522 | * null, zero length or contains non-valid characters [^a-zA-Z_\.0-9]. |
| 523 | * Or, if the package name isn't a valid android package name. |
| 524 | * @throws IllegalStateException if trying to reset a package mapping with a |
| 525 | * different seinfo value. |
| 526 | */ |
| 527 | public PolicyBuilder addInnerPackageMapOrThrow(String pkgName, String seinfo) { |
| 528 | if (!validateValue(pkgName)) { |
| 529 | String err = "Invalid package name " + pkgName; |
| 530 | throw new IllegalArgumentException(err); |
| 531 | } |
| 532 | if (!validateValue(seinfo)) { |
| 533 | String err = "Invalid seinfo value " + seinfo; |
| 534 | throw new IllegalArgumentException(err); |
| 535 | } |
| 536 | |
| 537 | String pkgValue = mPkgMap.get(pkgName); |
| 538 | if (pkgValue != null && !pkgValue.equals(seinfo)) { |
| 539 | String err = "Conflicting seinfo value found"; |
| 540 | throw new IllegalStateException(err); |
| 541 | } |
| 542 | |
| 543 | mPkgMap.put(pkgName, seinfo); |
| 544 | return this; |
| 545 | } |
| 546 | |
| 547 | /** |
| 548 | * General validation routine for the attribute strings of an element. Checks |
| 549 | * if the string is non-null, positive length and only contains [a-zA-Z_\.0-9]. |
| 550 | * |
| 551 | * @param name the string to validate. |
| 552 | * @return boolean indicating if the string was valid. |
| 553 | */ |
| 554 | private boolean validateValue(String name) { |
| 555 | if (name == null) |
| 556 | return false; |
| 557 | |
| 558 | // Want to match on [0-9a-zA-Z_.] |
| 559 | if (!name.matches("\\A[\\.\\w]+\\z")) { |
| 560 | return false; |
| 561 | } |
| 562 | |
| 563 | return true; |
| 564 | } |
| 565 | |
| 566 | /** |
| 567 | * <p> |
| 568 | * Create a {@link Policy} instance based on the current configuration. This |
| 569 | * method checks for certain policy invariants used to enforce certain guarantees |
| 570 | * about the expected structure of a policy stanza. |
| 571 | * Those invariants are: |
| 572 | * </p> |
Robert Craig | 5e16bc5 | 2015-08-28 12:11:41 -0400 | [diff] [blame] | 573 | * <ul> |
| 574 | * <li> at least one cert must be found </li> |
| 575 | * <li> either a global seinfo value is present OR at least one |
| 576 | * inner package mapping must be present BUT not both. </li> |
| 577 | * </ul> |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 578 | * @return an instance of {@link Policy} with the options set from this builder |
| 579 | * @throws IllegalStateException if an invariant is violated. |
| 580 | */ |
| 581 | public Policy build() { |
| 582 | Policy p = new Policy(this); |
| 583 | |
Robert Craig | 5e16bc5 | 2015-08-28 12:11:41 -0400 | [diff] [blame] | 584 | if (p.mCerts.isEmpty()) { |
| 585 | String err = "Missing certs with signer tag. Expecting at least one."; |
| 586 | throw new IllegalStateException(err); |
| 587 | } |
| 588 | if (!(p.mSeinfo == null ^ p.mPkgMap.isEmpty())) { |
| 589 | String err = "Only seinfo tag XOR package tags are allowed within " + |
| 590 | "a signer stanza."; |
| 591 | throw new IllegalStateException(err); |
Robert Craig | 2e1f052 | 2014-11-19 13:56:10 -0500 | [diff] [blame] | 592 | } |
| 593 | |
| 594 | return p; |
| 595 | } |
| 596 | } |
| 597 | } |
Robert Craig | 4caa6b1 | 2015-04-10 11:02:33 -0400 | [diff] [blame] | 598 | |
| 599 | /** |
| 600 | * Comparision imposing an ordering on Policy objects. It is understood that Policy |
| 601 | * objects can only take one of three forms and ordered according to the following |
| 602 | * set of rules most specific to least. |
| 603 | * <ul> |
| 604 | * <li> signer stanzas with inner package mappings </li> |
| 605 | * <li> signer stanzas with global seinfo tags </li> |
Robert Craig | 4caa6b1 | 2015-04-10 11:02:33 -0400 | [diff] [blame] | 606 | * </ul> |
| 607 | * This comparison also checks for duplicate entries on the input selectors. Any |
| 608 | * found duplicates will be flagged and can be checked with {@link #foundDuplicate}. |
| 609 | */ |
| 610 | |
| 611 | final class PolicyComparator implements Comparator<Policy> { |
| 612 | |
| 613 | private boolean duplicateFound = false; |
| 614 | |
| 615 | public boolean foundDuplicate() { |
| 616 | return duplicateFound; |
| 617 | } |
| 618 | |
| 619 | @Override |
| 620 | public int compare(Policy p1, Policy p2) { |
| 621 | |
Robert Craig | 4caa6b1 | 2015-04-10 11:02:33 -0400 | [diff] [blame] | 622 | // Give precedence to stanzas with inner package mappings |
| 623 | if (p1.hasInnerPackages() != p2.hasInnerPackages()) { |
| 624 | return p1.hasInnerPackages() ? -1 : 1; |
| 625 | } |
| 626 | |
| 627 | // Check for duplicate entries |
| 628 | if (p1.getSignatures().equals(p2.getSignatures())) { |
Robert Craig | 5e16bc5 | 2015-08-28 12:11:41 -0400 | [diff] [blame] | 629 | // Checks if signer w/o inner package names |
Robert Craig | 4caa6b1 | 2015-04-10 11:02:33 -0400 | [diff] [blame] | 630 | if (p1.hasGlobalSeinfo()) { |
| 631 | duplicateFound = true; |
| 632 | Slog.e(SELinuxMMAC.TAG, "Duplicate policy entry: " + p1.toString()); |
| 633 | } |
| 634 | |
| 635 | // Look for common inner package name mappings |
| 636 | final Map<String, String> p1Packages = p1.getInnerPackages(); |
| 637 | final Map<String, String> p2Packages = p2.getInnerPackages(); |
| 638 | if (!Collections.disjoint(p1Packages.keySet(), p2Packages.keySet())) { |
| 639 | duplicateFound = true; |
| 640 | Slog.e(SELinuxMMAC.TAG, "Duplicate policy entry: " + p1.toString()); |
| 641 | } |
| 642 | } |
| 643 | |
| 644 | return 0; |
| 645 | } |
| 646 | } |