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